blob: 8a1c2e67c46aca7fdd65a30861474c9318c5c5ba [file] [log] [blame]
<?php
//=======================================================================
// File: JPGRAPH.PHP
// Description: PHP Graph Plotting library. Base module.
// Created: 2001-01-08
// Author: Johan Persson (johanp@aditus.nu)
// Ver: $Id$
//
// Copyright (c) Aditus Consulting. All rights reserved.
//========================================================================
require_once('jpg-config.inc');
require_once('gd_image.inc.php');
// Version info
DEFINE('JPG_VERSION','1.20.3');
// Minimum required PHP version
DEFINE('MIN_PHPVERSION','4.3.1');
// For internal use only
DEFINE("_JPG_DEBUG",false);
DEFINE("_FORCE_IMGTOFILE",false);
DEFINE("_FORCE_IMGDIR",'/tmp/jpgimg/');
//------------------------------------------------------------------------
// Automatic settings of path for cache and font directory
// if they have not been previously specified
//------------------------------------------------------------------------
if(USE_CACHE) {
if (!defined('CACHE_DIR')) {
if ( strstr( PHP_OS, 'WIN') ) {
if( empty($_SERVER['TEMP']) ) {
$t = new ErrMsgText();
$msg = $t->Get(11,$file,$lineno);
die($msg);
}
else {
DEFINE('CACHE_DIR', $_SERVER['TEMP'] . '/');
}
} else {
DEFINE('CACHE_DIR','/tmp/jpgraph_cache/');
}
}
}
elseif( !defined('CACHE_DIR') ) {
DEFINE('CACHE_DIR', '');
}
if (!defined('TTF_DIR')) {
if (strstr( PHP_OS, 'WIN') ) {
$sroot = getenv('SystemRoot');
if( empty($sroot) ) {
$t = new ErrMsgText();
$msg = $t->Get(12,$file,$lineno);
die($msg);
}
else {
DEFINE('TTF_DIR', $sroot.'/fonts/');
}
} else {
DEFINE('TTF_DIR','/usr/X11R6/lib/X11/fonts/truetype/');
}
}
//------------------------------------------------------------------
// Constants which are used as parameters for the method calls
//------------------------------------------------------------------
// 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);
DEFINE("MARK_FLAG1",14);
DEFINE("MARK_FLAG2",15);
DEFINE("MARK_FLAG3",16);
DEFINE("MARK_FLAG4",17);
// 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);
// 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);
// 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);
// Style for background gradient fills
DEFINE('BGRAD_FRAME',1);
DEFINE('BGRAD_MARGIN',2);
DEFINE('BGRAD_PLOT',3);
// Width of tab titles
DEFINE('TABTITLE_WIDTHFIT',0);
DEFINE('TABTITLE_WIDTHFULL',-1);
// Defines for 3D skew directions
DEFINE('SKEW3D_UP',0);
DEFINE('SKEW3D_DOWN',1);
DEFINE('SKEW3D_LEFT',2);
DEFINE('SKEW3D_RIGHT',3);
//
// Get hold of gradient class (In Version 2.x)
// A client of the library has to manually include this
//
require_once 'jpgraph_gradient.php';
GLOBAL $__jpg_err;
GLOBAL $__jpg_err_locale ;
$__jpg_err_locale = DEFAULT_ERR_LOCALE;
class ErrMsgText {
var $lt=NULL;
function ErrMsgText() {
GLOBAL $__jpg_err_locale;
$file = 'lang/'.$__jpg_err_locale.'.inc.php';
// If the chosen locale doesn't exist try english
if( !file_exists(dirname(__FILE__).'/'.$file) ) {
$__jpg_err_locale = 'en';
}
$file = 'lang/'.$__jpg_err_locale.'.inc.php';
if( !file_exists(dirname(__FILE__).'/'.$file) ) {
die('Internal error: Chosen locale file for error messages does not exist.');
}
require_once($file);
$this->lt = $_jpg_messages;
}
function Get($errnbr,$a1=null,$a2=null,$a3=null,$a4=null,$a5=null) {
GLOBAL $__jpg_err_locale;
if( !isset($this->lt[$errnbr]) ) {
return 'Internal error: The specified error message ('.$errnbr.') does not exist in the chosen locale ('.$__jpg_err_locale.')';
}
$ea = $this->lt[$errnbr];
$j=0;
if( $a1 !== null ) {
$argv[$j++] = $a1;
if( $a2 !== null ) {
$argv[$j++] = $a2;
if( $a3 !== null ) {
$argv[$j++] = $a3;
if( $a4 !== null ) {
$argv[$j++] = $a4;
if( $a5 !== null ) {
$argv[$j++] = $a5;
}
}
}
}
}
$numargs = $j;
if( $ea[1] != $numargs ) {
// Error message argument count do not match.
// Just return the error message without arguments.
return $ea[0];
}
switch( $numargs ) {
case 1:
$msg = sprintf($ea[0],$argv[0]);
break;
case 2:
$msg = sprintf($ea[0],$argv[0],$argv[1]);
break;
case 3:
$msg = sprintf($ea[0],$argv[0],$argv[1],$argv[2]);
break;
case 4:
$msg = sprintf($ea[0],$argv[0],$argv[1],$argv[2],$argv[3]);
break;
case 5:
$msg = sprintf($ea[0],$argv[0],$argv[1],$argv[2],$argv[3],$argv[4]);
break;
case 0:
default:
$msg = sprintf($ea[0]);
break;
}
return $msg;
}
}
//
// 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 SetErrLocale($aLoc) {
GLOBAL $__jpg_err_locale ;
$__jpg_err_locale = $aLoc;
}
function Raise($aMsg,$aHalt=true){
GLOBAL $__jpg_err;
$tmp = new $__jpg_err;
$tmp->Raise($aMsg,$aHalt);
}
function RaiseL($errnbr,$a1=null,$a2=null,$a3=null,$a4=null,$a5=null) {
GLOBAL $__jpg_err;
$t = new ErrMsgText();
$msg = $t->Get($errnbr,$a1,$a2,$a3,$a4,$a5);
$tmp = new $__jpg_err;
$tmp->Raise($msg);
}
}
//
// ... and install the default error handler
//
if( USE_IMAGE_ERROR_HANDLER ) {
$__jpg_err = "JpGraphErrObjectImg";
}
else {
$__jpg_err = "JpGraphErrObject";
}
function CheckPHPVersion($aMinVersion)
{
list($majorC, $minorC, $editC) = split('[/.-]', PHP_VERSION);
list($majorR, $minorR, $editR) = split('[/.-]', $aMinVersion);
if ($majorC > $majorR) return true;
if ($majorC < $majorR) return false;
// same major - check ninor
if ($minorC > $minorR) return true;
if ($minorC < $minorR) return false;
// and same minor
if ($editC >= $editR) return true;
return true;
}
//
// Make GD sanity check
//
if( !function_exists("imagetypes") || !function_exists('imagecreatefromstring') ) {
JpGraphError::RaiseL(25001);
//("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)");
}
//
// Routine to determine if GD1 or GD2 is installed
//
function CheckGDVersion() {
$GDfuncList = get_extension_funcs('gd');
if( !$GDfuncList ) return 0 ;
else {
if( in_array('imagegd2',$GDfuncList) &&
in_array('imagecreatetruecolor',$GDfuncList))
return 2;
else
return 1;
}
}
//
// Check what version of the GD library is installed.
//
$GLOBALS['gd2'] = false;
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::RaiseL(25002);
//(" 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';
}
//
// Make sure PHP version is high enough
//
if( !CheckPHPVersion(MIN_PHPVERSION) ) {
JpGraphError::RaiseL(13,PHP_VERSION,MIN_PHPVERSION);
}
//
// First of all set up a default error handler
//
//=============================================================
// The default trivial text error handler.
//=============================================================
class JpGraphErrObject {
var $iTitle = "JpGraph Error";
var $iDest = false;
function JpGraphErrObject() {
// Empty. Reserved for future use
}
function SetTitle($aTitle) {
$this->iTitle = $aTitle;
}
function SetStrokeDest($aDest) {
$this->iDest = $aDest;
}
// If aHalt is true then execution can't continue. Typical used for fatal errors.
function Raise($aMsg,$aHalt=true) {
$aMsg = $this->iTitle.' '.$aMsg;
if ($this->iDest) {
$f = @fopen($this->iDest,'a');
if( $f ) {
@fwrite($f,$aMsg);
@fclose($f);
}
}
else {
echo $aMsg;
}
if( $aHalt )
die();
}
}
//==============================================================
// An image based error handler
//==============================================================
class JpGraphErrObjectImg extends JpGraphErrObject {
function Raise($aMsg,$aHalt=true) {
$img_iconerror =
'iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAMAAAC7IEhfAAAAaV'.
'BMVEX//////2Xy8mLl5V/Z2VvMzFi/v1WyslKlpU+ZmUyMjEh/'.
'f0VyckJlZT9YWDxMTDjAwMDy8sLl5bnY2K/MzKW/v5yyspKlpY'.
'iYmH+MjHY/PzV/f2xycmJlZVlZWU9MTEXY2Ms/PzwyMjLFTjea'.
'AAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACx'.
'IAAAsSAdLdfvwAAAAHdElNRQfTBgISOCqusfs5AAABLUlEQVR4'.
'2tWV3XKCMBBGWfkranCIVClKLd/7P2Q3QsgCxjDTq+6FE2cPH+'.
'xJ0Ogn2lQbsT+Wrs+buAZAV4W5T6Bs0YXBBwpKgEuIu+JERAX6'.
'wM2rHjmDdEITmsQEEmWADgZm6rAjhXsoMGY9B/NZBwJzBvn+e3'.
'wHntCAJdGu9SviwIwoZVDxPB9+Rc0TSEbQr0j3SA1gwdSn6Db0'.
'6Tm1KfV6yzWGQO7zdpvyKLKBDmRFjzeB3LYgK7r6A/noDAfjtS'.
'IXaIzbJSv6WgUebTMV4EoRB8a2mQiQjgtF91HdKDKZ1gtFtQjk'.
'YcWaR5OKOhkYt+ZsTFdJRfPAApOpQYJTNHvCRSJR6SJngQadfc'.
'vd69OLMddVOPCGVnmrFD8bVYd3JXfxXPtLR/+mtv59/ALWiiMx'.
'qL72fwAAAABJRU5ErkJggg==' ;
if( function_exists("imagetypes") )
$supported = imagetypes();
else
$supported = 0;
if( !function_exists('imagecreatefromstring') )
$supported = 0;
if( ob_get_length() || headers_sent() || !($supported & IMG_PNG) ) {
// Special case for headers already sent or that the installation doesn't support
// the PNG format (which the error icon is encoded in).
// Dont return an image since it can't be displayed
die($this->iTitle.' '.$aMsg);
}
$aMsg = wordwrap($aMsg,55);
$lines = substr_count($aMsg,"\n");
// Create the error icon GD
$erricon = Image::CreateFromString(base64_decode($img_iconerror));
// Create an image that contains the error text.
$w=400;
$h=100 + 15*max(0,$lines-3);
$img = new Image($w,$h);
// Drop shadow
$img->SetColor("gray");
$img->FilledRectangle(5,5,$w-1,$h-1,10);
$img->SetColor("gray:0.7");
$img->FilledRectangle(5,5,$w-3,$h-3,10);
// Window background
$img->SetColor("lightblue");
$img->FilledRectangle(1,1,$w-5,$h-5);
$img->CopyCanvasH($img->img,$erricon,5,30,0,0,40,40);
// Window border
$img->SetColor("black");
$img->Rectangle(1,1,$w-5,$h-5);
$img->Rectangle(0,0,$w-4,$h-4);
// Window top row
$img->SetColor("darkred");
for($y=3; $y < 18; $y += 2 )
$img->Line(1,$y,$w-6,$y);
// "White shadow"
$img->SetColor("white");
// Left window edge
$img->Line(2,2,2,$h-5);
$img->Line(2,2,$w-6,2);
// "Gray button shadow"
$img->SetColor("darkgray");
// Gray window shadow
$img->Line(2,$h-6,$w-5,$h-6);
$img->Line(3,$h-7,$w-5,$h-7);
// Window title
$m = floor($w/2-5);
$l = 100;
$img->SetColor("lightgray:1.3");
$img->FilledRectangle($m-$l,2,$m+$l,16);
// Stroke text
$img->SetColor("darkred");
$img->SetFont(FF_FONT2,FS_BOLD);
$img->StrokeText($m-50,15,$this->iTitle);
$img->SetColor("black");
$img->SetFont(FF_FONT1,FS_NORMAL);
$txt = new Text($aMsg,52,25);
$txt->Align("left","top");
$txt->Stroke($img);
if ($this->iDest) {
$img->Stream($this->iDest);
} else {
$img->Headers();
$img->Stream();
}
if( $aHalt )
die();
}
}
//
// Setup PHP error handler
//
function _phpErrorHandler($errno,$errmsg,$filename, $linenum, $vars) {
// Respect current error level
if( $errno & error_reporting() ) {
JpGraphError::RaiseL(25003,basename($filename),$linenum,$errmsg);
}
}
if( INSTALL_PHP_ERR_HANDLER ) {
set_error_handler("_phpErrorHandler");
}
//
//Check if there were any warnings, perhaps some wrong includes by the user
//
if( isset($GLOBALS['php_errormsg']) && CATCH_PHPERRMSG &&
!preg_match('|Deprecated|', $GLOBALS['php_errormsg'])) {
JpGraphError::RaiseL(25004,$GLOBALS['php_errormsg']);
}
// Useful 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 $_SERVER;
// Determine what format we should use when we save the images
$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($_SERVER['PHP_SELF']) )
JpGraphError::RaiseL(25005);
//(" 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($_SERVER['PHP_SELF']);
if( !empty($_SERVER['QUERY_STRING']) ) {
$q = @$_SERVER['QUERY_STRING'];
$fname .= '?'.preg_replace("/\W/", "_", $q).'.'.$img_format;
}
else {
$fname = substr($fname,0,strlen($fname)-4).'.'.$img_format;
}
return $fname;
}
class LanguageConv {
var $g2312 = null ;
function Convert($aTxt,$aFF) {
if( LANGUAGE_GREEK ) {
if( GREEK_FROM_WINDOWS ) {
$unistring = LanguageConv::gr_win2uni($aTxt);
} else {
$unistring = LanguageConv::gr_iso2uni($aTxt);
}
return $unistring;
} elseif( LANGUAGE_CYRILLIC ) {
if( CYRILLIC_FROM_WINDOWS && (!defined('LANGUAGE_CHARSET') || stristr(LANGUAGE_CHARSET, 'windows-1251')) ) {
$aTxt = convert_cyr_string($aTxt, "w", "k");
}
if( !defined('LANGUAGE_CHARSET') || stristr(LANGUAGE_CHARSET, 'koi8-r') || stristr(LANGUAGE_CHARSET, 'windows-1251')) {
$isostring = convert_cyr_string($aTxt, "k", "i");
$unistring = LanguageConv::iso2uni($isostring);
}
else {
$unistring = $aTxt;
}
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);
}
elseif( $aFF === FF_CHINESE ) {
if( !function_exists('iconv') ) {
JpGraphError::RaiseL(25006);
//('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).');
}
return iconv('BIG5','UTF-8',$aTxt);
}
elseif( ASSUME_EUCJP_ENCODING &&
($aFF == FF_MINCHO || $aFF == FF_GOTHIC || $aFF == FF_PMINCHO || $aFF == FF_PGOTHIC) ) {
if( !function_exists('mb_convert_encoding') ) {
JpGraphError::RaiseL(25127);
}
return mb_convert_encoding($aTxt, 'UTF-8','EUC-JP');
}
else
return $aTxt;
}
// Translate iso encoding to unicode
function iso2uni ($isoline){
$uniline='';
for ($i=0; $i < strlen($isoline); $i++){
$thischar=substr($isoline,$i,1);
$charcode=ord($thischar);
$uniline.=($charcode>175) ? "&#" . (1040+($charcode-176)). ";" : $thischar;
}
return $uniline;
}
// Translate greek iso encoding to unicode
function gr_iso2uni ($isoline) {
$uniline='';
for ($i=0; $i < strlen($isoline); $i++) {
$thischar=substr($isoline,$i,1);
$charcode=ord($thischar);
$uniline.=($charcode>179 && $charcode!=183 && $charcode!=187 && $charcode!=189) ? "&#" . (900+($charcode-180)). ";" : $thischar;
}
return $uniline;
}
// Translate greek win encoding to unicode
function gr_win2uni ($winline) {
$uniline='';
for ($i=0; $i < strlen($winline); $i++) {
$thischar=substr($winline,$i,1);
$charcode=ord($thischar);
if ($charcode==161 || $charcode==162) {
$uniline.="&#" . (740+$charcode). ";";
}
else {
$uniline.=(($charcode>183 && $charcode!=187 && $charcode!=189) || $charcode==180) ? "&#" . (900+($charcode-180)). ";" : $thischar;
}
}
return $uniline;
}
}
//===================================================
// CLASS JpgTimer
// Description: General timing utility class to handle
// time measurement of generating graphs. Multiple
// timers can be started.
//===================================================
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
//===================================================
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::RaiseL(25007,$aLocale);
//("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);
}
// Return to original locale
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 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);
}
}
//===================================================
// 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 $ynplots=array();
var $xscale=null; // X Scale object (could be instance of LinearScale or LogScale
var $yscale=null,$y2scale=null, $ynscale=array();
var $iIcons = array(); // Array of Icons to add to
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;
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, $ynaxis=array(); // 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, $y2texts=null; // Text object to ge shown in the graph
var $lines=null, $y2lines=null;
var $bands=null, $y2bands=null;
var $text_scale_off=0, $text_scale_abscenteroff=-1; // Text scale offset in fractions and for centering bars in absolute pixels
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='white@0.4', $framebevelcolor2='black@0.4';
var $background_image_mix=100;
var $background_cflag = '';
var $background_cflag_type = BGIMG_FILLPLOT;
var $background_cflag_mix = 100;
var $iImgTrans=false,
$iImgTransHorizon = 100,$iImgTransSkewDist=150,
$iImgTransDirection = 1, $iImgTransMinSize = true,
$iImgTransFillColor='white',$iImgTransHighQ=false,
$iImgTransBorder=false,$iImgTransHorizonPos=0.5;
var $iYAxisDeltaPos=50;
var $iIconDepth=DEPTH_BACK;
var $iAxisLblBgType = 0,
$iXAxisLblBgFillColor = 'lightgray', $iXAxisLblBgColor = 'black',
$iYAxisLblBgFillColor = 'lightgray', $iYAxisLblBgColor = 'black';
var $iTables=NULL;
//---------------
// 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();
}
if( !is_numeric($aWidth) || !is_numeric($aHeight) ) {
JpGraphError::RaiseL(25008);//('Image width/height argument in Graph::Graph() must be numeric');
}
// 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->title->SetAlign('center');
$this->subtitle = new Text();
$this->subtitle->ParagraphAlign('center');
$this->subtitle->SetMargin(2);
$this->subtitle->SetAlign('center');
$this->subsubtitle = new Text();
$this->subsubtitle->ParagraphAlign('center');
$this->subsubtitle->SetMargin(2);
$this->subsubtitle->SetAlign('center');
$this->legend = new Legend();
$this->footer = new Footer();
// Window doesn't like '?' in the file name so replace it with an '_'
$aCachedName = str_replace("?","_",$aCachedName);
// 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
// Enable final image perspective transformation
function Set3DPerspective($aDir=1,$aHorizon=100,$aSkewDist=120,$aQuality=false,$aFillColor='#FFFFFF',$aBorder=false,$aMinSize=true,$aHorizonPos=0.5) {
$this->iImgTrans = true;
$this->iImgTransHorizon = $aHorizon;
$this->iImgTransSkewDist= $aSkewDist;
$this->iImgTransDirection = $aDir;
$this->iImgTransMinSize = $aMinSize;
$this->iImgTransFillColor=$aFillColor;
$this->iImgTransHighQ=$aQuality;
$this->iImgTransBorder=$aBorder;
$this->iImgTransHorizonPos=$aHorizonPos;
}
// Set Image format and optional quality
function SetImgFormat($aFormat,$aQuality=75) {
$this->img->SetImgFormat($aFormat,$aQuality);
}
// Should the grid be in front or back of the plot?
function SetGridDepth($aDepth) {
$this->grid_depth=$aDepth;
}
function SetIconDepth($aDepth) {
$this->iIconDepth=$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);
if( empty($this->yaxis) || empty($this->xaxis) ) {
JpgraphError::RaiseL(25009);//('You must specify what scale to use with a call to Graph::SetScale()');
}
$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::RaiseL(25010);//("Graph::Add() You tried to add a null plot to the graph.");
if( is_array($aPlot) && count($aPlot) > 0 )
$cl = $aPlot[0];
else
$cl = $aPlot;
if( is_a($cl,'Text') )
$this->AddText($aPlot);
elseif( is_a($cl,'PlotLine') )
$this->AddLine($aPlot);
elseif( is_a($cl,'PlotBand') )
$this->AddBand($aPlot);
elseif( is_a($cl,'IconPlot') )
$this->AddIcon($aPlot);
elseif( is_a($cl,'GTextTable') )
$this->AddTable($aPlot);
else
$this->plots[] = &$aPlot;
}
function AddTable(&$aTable) {
if( is_array($aTable) ) {
for($i=0; $i < count($aTable); ++$i )
$this->iTables[]=&$aTable[$i];
}
else {
$this->iTables[] = &$aTable ;
}
}
function AddIcon(&$aIcon) {
if( is_array($aIcon) ) {
for($i=0; $i < count($aIcon); ++$i )
$this->iIcons[]=&$aIcon[$i];
}
else {
$this->iIcons[] = &$aIcon ;
}
}
// Add plot to second Y-scale
function AddY2(&$aPlot) {
if( $aPlot == null )
JpGraphError::RaiseL(25011);//("Graph::AddY2() You tried to add a null plot to the graph.");
if( is_array($aPlot) && count($aPlot) > 0 )
$cl = $aPlot[0];
else
$cl = $aPlot;
if( is_a($cl,'Text') )
$this->AddText($aPlot,true);
elseif( is_a($cl,'PlotLine') )
$this->AddLine($aPlot,true);
elseif( is_a($cl,'PlotBand') )
$this->AddBand($aPlot,true);
else
$this->y2plots[] = &$aPlot;
}
// Add plot to second Y-scale
function AddY($aN,&$aPlot) {
if( $aPlot == null )
JpGraphError::RaiseL(25012);//("Graph::AddYN() You tried to add a null plot to the graph.");
if( is_array($aPlot) && count($aPlot) > 0 )
$cl = $aPlot[0];
else
$cl = $aPlot;
if( is_a($cl,'Text') || is_a($cl,'PlotLine') || is_a($cl,'PlotBand') )
JpGraph::RaiseL(25013);//('You can only add standard plots to multiple Y-axis');
else
$this->ynplots[$aN][] = &$aPlot;
}
// Add text object to the graph
function AddText(&$aTxt,$aToY2=false) {
if( $aTxt == null )
JpGraphError::RaiseL(25014);//("Graph::AddText() You tried to add a null text to the graph.");
if( $aToY2 ) {
if( is_array($aTxt) ) {
for($i=0; $i < count($aTxt); ++$i )
$this->y2texts[]=&$aTxt[$i];
}
else
$this->y2texts[] = &$aTxt;
}
else {
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,$aToY2=false) {
if( $aLine == null )
JpGraphError::RaiseL(25015);//("Graph::AddLine() You tried to add a null line to the graph.");
if( $aToY2 ) {
if( is_array($aLine) ) {
for($i=0; $i < count($aLine); ++$i )
$this->y2lines[]=&$aLine[$i];
}
else
$this->y2lines[] = &$aLine;
}
else {
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,$aToY2=false) {
if( $aBand == null )
JpGraphError::RaiseL(25016);//(" Graph::AddBand() You tried to add a null band to the graph.");
if( $aToY2 ) {
if( is_array($aBand) ) {
for($i=0; $i < count($aBand); ++$i )
$this->y2bands[] = &$aBand[$i];
}
else
$this->y2bands[] = &$aBand;
}
else {
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=2,$aStyle=BGRAD_FRAME) {
$this->bkg_gradtype=$aGradType;
$this->bkg_gradstyle=$aStyle;
$this->bkg_gradfrom = $aFrom;
$this->bkg_gradto = $aTo;
}
// Set a country flag in the background
function SetBackgroundCFlag($aName,$aBgType=BGIMG_FILLPLOT,$aMix=100) {
$this->background_cflag = $aName;
$this->background_cflag_type = $aBgType;
$this->background_cflag_mix = $aMix;
}
// Alias for the above method
function SetBackgroundCountryFlag($aName,$aBgType=BGIMG_FILLPLOT,$aMix=100) {
$this->background_cflag = $aName;
$this->background_cflag_type = $aBgType;
$this->background_cflag_mix = $aMix;
}
// Specify a background image
function SetBackgroundImage($aFileName,$aBgType=BGIMG_FILLPLOT,$aImgFormat="auto") {
if( $GLOBALS['gd2'] && !USE_TRUECOLOR ) {
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.");
}
// Get extension to determine image type
if( $aImgFormat == "auto" ) {
$e = explode('.',$aFileName);
if( !$e ) {
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');
}
$valid_formats = array('png', 'jpg', 'gif');
$aImgFormat = strtolower($e[count($e)-1]);
if ($aImgFormat == 'jpeg') {
$aImgFormat = 'jpg';
}
elseif (!in_array($aImgFormat, $valid_formats) ) {
JpGraphError::RaiseL(25019,$aImgFormat);//('Unknown file extension ($aImgFormat) in Graph::SetBackgroundImage() for filename: '.$aFileName);
}
}
$this->background_image = $aFileName;
$this->background_image_type=$aBgType;
$this->background_image_format=$aImgFormat;
}
function SetBackgroundImageMix($aMix) {
$this->background_image_mix = $aMix ;
}
// 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='white@0.4',$aColor2='darkgray@0.4',$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::RaiseL(25020);//('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::RaiseL(25021,$aAxisType);//("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 == "dat" ) {
$this->xscale = new DateScale($aXMin,$aXMax,"x");
}
elseif( $xt == "log" )
$this->xscale = new LogScale($aXMin,$aXMax,"x");
else
JpGraphError::RaiseL(25022,$aAxisType);//(" Unknown scale specification for X-scale. ($aAxisType)");
$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::RaiseL(25023,$aAxisType);//("JpGraph: Unsupported Y2 axis type: $aAxisType\nMust be one of (lin,log,int)");
$this->y2axis = new Axis($this->img,$this->y2scale);
$this->y2axis->scale->ticks->SetDirection(SIDE_LEFT);
$this->y2axis->SetLabelSide(SIDE_RIGHT);
$this->y2axis->SetPos('max');
$this->y2axis->SetTitleSide(SIDE_RIGHT);
// Deafult position is the max x-value
$this->y2grid = new Grid($this->y2axis);
}
// Set the delta position (in pixels) between the multiple Y-axis
function SetYDeltaDist($aDist) {
$this->iYAxisDeltaPos = $aDist;
}
// Specify secondary Y scale
function SetYScale($aN,$aAxisType="lin",$aYMin=1,$aYMax=1) {
if( $aAxisType=="lin" )
$this->ynscale[$aN] = new LinearScale($aYMin,$aYMax);
elseif( $aAxisType == "int" ) {
$this->ynscale[$aN] = new LinearScale($aYMin,$aYMax);
$this->ynscale[$aN]->SetIntScale();
}
elseif( $aAxisType=="log" ) {
$this->ynscale[$aN] = new LogScale($aYMin,$aYMax);
}
else JpGraphError::RaiseL(25024,$aAxisType);//("JpGraph: Unsupported Y axis type: $aAxisType\nMust be one of (lin,log,int)");
$this->ynaxis[$aN] = new Axis($this->img,$this->ynscale[$aN]);
$this->ynaxis[$aN]->scale->ticks->SetDirection(SIDE_LEFT);
$this->ynaxis[$aN]->SetLabelSide(SIDE_RIGHT);
}
// 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::RaiseL(25025,$densy);//("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::RaiseL(25025,$densx);//("JpGraph: Unsupported Tick density: $densx");
}
}
// Get a string of all image map areas
function GetCSIMareas() {
if( !$this->iHasStroked )
$this->Stroke(_CSIM_SPECIALFILE);
$csim = $this->title->GetCSIMAreas();
$csim .= $this->subtitle->GetCSIMAreas();
$csim .= $this->subsubtitle->GetCSIMAreas();
$csim .= $this->legend->GetCSIMAreas();
if( $this->y2axis != NULL ) {
$csim .= $this->y2axis->title->GetCSIMAreas();
}
if( $this->texts != null ) {
$n = count($this->texts);
for($i=0; $i < $n; ++$i ) {
$csim .= $this->texts[$i]->GetCSIMAreas();
}
}
if( $this->y2texts != null && $this->y2scale != null ) {
$n = count($this->y2texts);
for($i=0; $i < $n; ++$i ) {
$csim .= $this->y2texts[$i]->GetCSIMAreas();
}
}
if( $this->yaxis != null && $this->xaxis != null ) {
$csim .= $this->yaxis->title->GetCSIMAreas();
$csim .= $this->xaxis->title->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();
$n = count($this->ynaxis);
for( $i=0; $i < $n; ++$i ) {
$m = count($this->ynplots[$i]);
for($j=0; $j < $m; ++$j ) {
$csim .= $this->ynplots[$i][$j]->GetCSIMareas();
}
}
$n = count($this->iTables);
for( $i=0; $i < $n; ++$i ) {
$csim .= $this->iTables[$i]->GetCSIMareas();
}
return $csim;
}
// Get a complete <MAP>..</MAP> tag for the final image map
function GetHTMLImageMap($aMapName) {
$im = "<map name=\"$aMapName\" id=\"$aMapName\" >\n";
$im .= $this->GetCSIMareas();
$im .= "</map>";
return $im;
}
function CheckCSIMCache($aCacheName,$aTimeOut=60) {
global $_SERVER;
if( $aCacheName=='auto' )
$aCacheName=basename($_SERVER['PHP_SELF']);
$urlarg = $this->GetURLArguments();
$this->csimcachename = CSIMCACHE_DIR.$aCacheName.$urlarg;
$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.'?'.$urlarg.'_csim_.html';
$baseimg = $dir.'/'.$base.'?'.$urlarg.'.'.$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);
return true;
}
else
JpGraphError::RaiseL(25027,$basecsim);//(" Can't open cached CSIM \"$basecsim\" for reading.");
}
}
}
return false;
}
// Build the argument string to be used with the csim images
function GetURLArguments() {
// This is a JPGRAPH internal defined that prevents
// us from recursively coming here again
$urlarg = _CSIM_DISPLAY.'=1';
// Now reconstruct any user URL argument
reset($_GET);
while( list($key,$value) = each($_GET) ) {
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);
}
}
// It's not ideal to convert POST argument to GET arguments
// but there is little else we can do. One idea for the
// future might be recreate the POST header in case.
reset($_POST);
while( list($key,$value) = each($_POST) ) {
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);
}
}
return $urlarg;
}
function StrokeCSIM($aScriptName='auto',$aCSIMName='',$aBorder=0) {
if( $aCSIMName=='' ) {
// create a random map name
srand ((double) microtime() * 1000000);
$r = rand(0,100000);
$aCSIMName='__mapname'.$r.'__';
}
if( $aScriptName=='auto' )
$aScriptName=basename($_SERVER['PHP_SELF']);
$urlarg = $this->GetURLArguments();
if( empty($_GET[_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.'?'.$urlarg.'_csim_.html';
$baseimg = $base.'?'.$urlarg.'.'.$this->img->img_format;
// Check that apache can write to directory specified
if( file_exists($dir) && !is_writeable($dir) ) {
JpgraphError::RaiseL(25028,$dir);//('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
// In the src URL we must replace the '?' with its encoding to prevent the arguments
// to be converted to real arguments.
$tmp = str_replace('?','%3f',$baseimg);
$htmlwrap = $this->GetHTMLImageMap($aCSIMName)."\n".
'<img src="'.CSIMCACHE_HTTP_DIR.$tmp.'" ismap="ismap" usemap="#'.$aCSIMName.'" border="'.$aBorder.'" width="'.$this->img->width.'" height="'.$this->img->height."\" alt=\"\" />\n";
if($fh = @fopen($basecsim,'w') ) {
fwrite($fh,$htmlwrap);
fclose($fh);
echo $htmlwrap;
}
else
JpGraphError::RaiseL(25029,$basecsim);//(" Can't write CSIM \"$basecsim\" for writing. Check free space and permissions.");
}
else {
if( $aScriptName=='' ) {
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().');
}
echo $this->GetHTMLImageMap($aCSIMName);
echo "<img src=\"".$aScriptName.'?'.$urlarg."\" ismap=\"ismap\" usemap=\"#".$aCSIMName.'" border="'.$aBorder.'" width="'.$this->img->width.'" height="'.$this->img->height."\" alt=\"\" />\n";
}
}
else {
$this->Stroke();
}
}
function GetTextsYMinMax($aY2=false) {
if( $aY2 )
$txts = $this->y2texts;
else
$txts = $this->texts;
$n = count($txts);
$min=null;
$max=null;
for( $i=0; $i < $n; ++$i ) {
if( $txts[$i]->iScalePosY !== null &&
$txts[$i]->iScalePosX !== null ) {
if( $min === null ) {
$min = $max = $txts[$i]->iScalePosY ;
}
else {
$min = min($min,$txts[$i]->iScalePosY);
$max = max($max,$txts[$i]->iScalePosY);
}
}
}
if( $min !== null ) {
return array($min,$max);
}
else
return null;
}
function GetTextsXMinMax($aY2=false) {
if( $aY2 )
$txts = $this->y2texts;
else
$txts = $this->texts;
$n = count($txts);
$min=null;
$max=null;
for( $i=0; $i < $n; ++$i ) {
if( $txts[$i]->iScalePosY !== null &&
$txts[$i]->iScalePosX !== null ) {
if( $min === null ) {
$min = $max = $txts[$i]->iScalePosX ;
}
else {
$min = min($min,$txts[$i]->iScalePosX);
$max = max($max,$txts[$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);
}
}
$n = count($this->ynaxis);
for( $i=0; $i < $n; ++$i ) {
if( $this->ynaxis[$i] != null) {
foreach( $this->ynplots[$i] 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 != null && !$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="") {
// Fist make a sanity check that user has specified a scale
if( empty($this->yscale) ) {
JpGraphError::RaiseL(25031);//('You must specify what scale to use with a call to Graph::SetScale().');
}
// Start by adjusting the margin so that potential titles will fit.
$this->AdjustMarginsForTitles();
// Setup scale constants
if( $this->yscale ) $this->yscale->InitConstants($this->img);
if( $this->xscale ) $this->xscale->InitConstants($this->img);
if( $this->y2scale ) $this->y2scale->InitConstants($this->img);
$n=count($this->ynscale);
for($i=0; $i < $n; ++$i) {
if( $this->ynscale[$i] ) $this->ynscale[$i]->InitConstants($this->img);
}
// 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);
}
}
// Any plots on the extra Y axises?
$n = count($this->ynaxis);
for($i=0; $i<$n ; ++$i ) {
if( $this->ynplots == null || $this->ynplots[$i] == null) {
JpGraphError::RaiseL(25032,$i);//("No plots for Y-axis nbr:$i");
}
$m = count($this->ynplots[$i]);
for($j=0; $j < $m; ++$j ) {
$this->ynplots[$i][$j]->PreStrokeAdjust($this);
$this->ynplots[$i][$j]->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 = "n=".count($this->y2plots)."\n";
// $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::RaiseL(25026);
}
// Bail out if no plots and no specified X-scale
if( (!$this->xscale->IsSpecified() && count($this->plots)==0 && count($this->y2plots)==0) )
JpGraphError::RaiseL(25034);//("<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( is_array($lres) ) {
list($linmin,$linmax) = $lres ;
$min = min($min,$linmin);
$max = max($max,$linmax);
}
$tres = $this->GetTextsYMinMax();
if( is_array($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);
$lres = $this->GetLinesYMinMax($this->y2lines);
if( is_array($lres) ) {
list($linmin,$linmax) = $lres ;
$min = min($min,$linmin);
$max = max($max,$linmax);
}
$tres = $this->GetTextsYMinMax(true);
if( is_array($tres) ) {
list($tmin,$tmax) = $tres ;
$min = min($min,$tmin);
$max = max($max,$tmax);
}
$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);
}
}
//
// Autoscale the multiple Y-axis
//
$n = count($this->ynaxis);
for( $i=0; $i < $n; ++$i ) {
if( $this->ynscale[$i] != null) {
if( !$this->ynscale[$i]->IsSpecified() && count($this->ynplots[$i])>0 ) {
list($min,$max) = $this->GetPlotsYMinMax($this->ynplots[$i]);
$this->ynscale[$i]->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor);
}
elseif( $this->ynscale[$i]->IsSpecified() &&
( $this->ynscale[$i]->auto_ticks || !$this->ynscale[$i]->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->ynscale[$i]->scale[0];
$max = $this->ynscale[$i]->scale[1];
$this->ynscale[$i]->AutoScale($this->img,$min,$max,
$this->img->plotheight/$this->ytick_factor,
$this->ynscale[$i]->auto_ticks);
}
}
}
//Check if we should autoscale x-axis
if( !$this->xscale->IsSpecified() ) {
if( substr($this->axtype,0,4) == "text" ) {
$max=0;
$n = count($this->plots);
for($i=0; $i < $n; ++$i ) {
$p = $this->plots[$i];
// We need some unfortunate sub class knowledge here in order
// to increase number of data points in case it is a line plot
// which has the barcenter set. If not it could mean that the
// last point of the data is outside the scale since the barcenter
// settings means that we will shift the entire plot half a tick step
// to the right in oder to align with the center of the bars.
if( is_a($p,'BarPlot') || empty($p->barcenter)) {
$max=max($max,$p->numpoints-1);
}
else {
$max=max($max,$p->numpoints);
}
}
$min=0;
if( $this->y2axis != null ) {
foreach( $this->y2plots as $p ) {
$max=max($max,$p->numpoints-1);
}
}
$n = count($this->ynaxis);
for( $i=0; $i < $n; ++$i ) {
if( $this->ynaxis[$i] != null) {
foreach( $this->ynplots[$i] 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);
}
$lres = $this->GetLinesXMinMax($this->y2lines);
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);
}
$tres = $this->GetTextsXMinMax(true);
if( $tres ) {
list($tmin,$tmax) = $tres ;
$min = min($min,$tmin);
$max = max($max,$tmax);
}
$this->xscale->AutoScale($this->img,$min,$max,round($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);
}
$n = count($this->ynaxis);
$nY2adj = $this->y2axis != null ? $this->iYAxisDeltaPos : 0;
for( $i=0; $i < $n; ++$i ) {
if( $this->ynaxis[$i] != null ) {
if( !is_numeric($this->ynaxis[$i]->pos) && !is_string($this->ynaxis[$i]->pos) ) {
$this->ynaxis[$i]->SetPos($this->xscale->GetMaxVal());
$this->ynaxis[$i]->SetPosAbsDelta($i*$this->iYAxisDeltaPos + $nY2adj);
}
$this->ynaxis[$i]->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();
if( $this->iIconDepth == DEPTH_BACK ) {
$this->StrokeIcons();
}
}
$this->StrokeAxis(false);
// 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->y2bands != null && $this->y2scale != null && !$_csim )
for($i=0; $i < count($this->y2bands); ++$i) {
// Stroke all bands that asks to be in the foreground
if( $this->y2bands[$i]->depth == DEPTH_BACK )
$this->y2bands[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
}
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();
}
// Stroke yn-axis
$n = count($this->ynaxis);
for( $i=0; $i < $n; ++$i ) {
$this->ynaxis[$i]->Stroke($this->xscale);
}
$oldoff=$this->xscale->off;
if(substr($this->axtype,0,4)=="text") {
if( $this->text_scale_abscenteroff > -1 ) {
// For a text scale the scale factor is the number of pixel per step.
// Hence we can use the scale factor as a substitute for number of pixels
// per major scale step and use that in order to adjust the offset so that
// an object of width "abscenteroff" becomes centered.
$this->xscale->off += round($this->xscale->scale_factor/2)-round($this->text_scale_abscenteroff/2);
}
else {
$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 Y 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);
}
}
$n = count($this->ynaxis);
for( $i=0; $i < $n; ++$i ) {
$m = count($this->ynplots[$i]);
for( $j=0; $j < $m; ++$j ) {
$this->ynplots[$i][$j]->Stroke($this->img,$this->xscale,$this->ynscale[$i]);
$this->ynplots[$i][$j]->StrokeMargin($this->img);
}
}
if( $this->iIconDepth == DEPTH_FRONT) {
$this->StrokeIcons();
}
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::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.');
}
$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);
}
if( $this->y2bands!= null && $this->y2scale != null )
for($i=0; $i < count($this->y2bands); ++$i) {
// Stroke all bands that asks to be in the foreground
if( $this->y2bands[$i]->depth == DEPTH_FRONT )
$this->y2bands[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
}
// 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);
}
}
if( $this->y2lines != null && $this->y2scale != null ) {
for($i=0; $i < count($this->y2lines); ++$i) {
$this->y2lines[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
}
}
// Finally draw the axis again since some plots may have nagged
// the axis in the edges.However we do no stroke the labels again
// since any user defined callback would be called twice. It also
// enhances performance.
if( !$_csim ) {
$this->StrokeAxis();
}
if( $this->y2scale != null && !$_csim )
$this->y2axis->Stroke($this->xscale,false);
if( !$_csim ) {
$this->StrokePlotBox();
}
// 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);
$this->img->SetAngle($aa);
$this->StrokeTexts();
$this->StrokeTables();
if( !$_csim ) {
$this->img->SetAngle($aa);
// Draw an outline around the image map
if(_JPG_DEBUG) {
$this->DisplayClientSideaImageMapAreas();
}
// Adjust the appearance of the image
$this->AdjustSaturationBrightnessContrast();
// Should we do any final image transformation
if( $this->iImgTrans ) {
if( !class_exists('ImgTrans') ) {
require_once('jpgraph_imgtrans.php');
//JpGraphError::Raise('In order to use image transformation you must include the file jpgraph_imgtrans.php in your script.');
}
$tform = new ImgTrans($this->img->img);
$this->img->img = $tform->Skew3D($this->iImgTransHorizon,$this->iImgTransSkewDist,
$this->iImgTransDirection,$this->iImgTransHighQ,
$this->iImgTransMinSize,$this->iImgTransFillColor,
$this->iImgTransBorder);
}
// 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);
}
}
}
function SetAxisLabelBackground($aType,$aXFColor='lightgray',$aXColor='black',$aYFColor='lightgray',$aYColor='black') {
$this->iAxisLblBgType = $aType;
$this->iXAxisLblBgFillColor = $aXFColor;
$this->iXAxisLblBgColor = $aXColor;
$this->iYAxisLblBgFillColor = $aYFColor;
$this->iYAxisLblBgColor = $aYColor;
}
//---------------
// PRIVATE METHODS
function StrokeAxisLabelBackground() {
// Types
// 0 = No background
// 1 = Only X-labels, length of axis
// 2 = Only Y-labels, length of axis
// 3 = As 1 but extends to width of graph
// 4 = As 2 but extends to height of graph
// 5 = Combination of 3 & 4
// 6 = Combination of 1 & 2
$t = $this->iAxisLblBgType ;
if( $t < 1 ) return;
// Stroke optional X-axis label background color
if( $t == 1 || $t == 3 || $t == 5 || $t == 6 ) {
$this->img->PushColor($this->iXAxisLblBgFillColor);
if( $t == 1 || $t == 6 ) {
$xl = $this->img->left_margin;
$yu = $this->img->height - $this->img->bottom_margin + 1;
$xr = $this->img->width - $this->img->right_margin ;
$yl = $this->img->height-1-$this->frame_weight;
}
else { // t==3 || t==5
$xl = $this->frame_weight;
$yu = $this->img->height - $this->img->bottom_margin + 1;
$xr = $this->img->width - 1 - $this->frame_weight;
$yl = $this->img->height-1-$this->frame_weight;
}
$this->img->FilledRectangle($xl,$yu,$xr,$yl);
$this->img->PopColor();
// Check if we should add the vertical lines at left and right edge
if( $this->iXAxisLblBgColor !== '' ) {
$this->img->PushColor($this->iXAxisLblBgColor);
if( $t == 1 || $t == 6 ) {
$this->img->Line($xl,$yu,$xl,$yl);
$this->img->Line($xr,$yu,$xr,$yl);
}
else {
$xl = $this->img->width - $this->img->right_margin ;
$this->img->Line($xl,$yu-1,$xr,$yu-1);
}
$this->img->PopColor();
}
}
if( $t == 2 || $t == 4 || $t == 5 || $t == 6 ) {
$this->img->PushColor($this->iYAxisLblBgFillColor);
if( $t == 2 || $t == 6 ) {
$xl = $this->frame_weight;
$yu = $this->frame_weight+$this->img->top_margin;
$xr = $this->img->left_margin - 1;
$yl = $this->img->height - $this->img->bottom_margin + 1;
}
else {
$xl = $this->frame_weight;
$yu = $this->frame_weight;
$xr = $this->img->left_margin - 1;
$yl = $this->img->height-1-$this->frame_weight;
}
$this->img->FilledRectangle($xl,$yu,$xr,$yl);
$this->img->PopColor();
// Check if we should add the vertical lines at left and right edge
if( $this->iXAxisLblBgColor !== '' ) {
$this->img->PushColor($this->iXAxisLblBgColor);
if( $t == 2 || $t == 6 ) {
$this->img->Line($xl,$yu-1,$xr,$yu-1);
$this->img->Line($xl,$yl-1,$xr,$yl-1);
}
else {
$this->img->Line($xr+1,$yu,$xr+1,$this->img->top_margin);
}
$this->img->PopColor();
}
}
}
function StrokeAxis($aStrokeLabels=true) {
if( $aStrokeLabels ) {
$this->StrokeAxisLabelBackground();
}
// 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::RaiseL(25036,$this->iAxisStyle); //('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,$aStrokeLabels);
$this->yaxis->Stroke($this->xscale,$aStrokeLabels);
}
}
// Private helper function for backgound image
function LoadBkgImage($aImgFormat='',$aFile='',$aImgStr='') {
if( $aImgStr != '' ) {
return Image::CreateFromString($aImgStr);
}
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;
$supported = imagetypes();
if( ( $ext == 'jpg' && !($supported & IMG_JPG) ) ||
( $ext == 'gif' && !($supported & IMG_GIF) ) ||
( $ext == 'png' && !($supported & IMG_PNG) ) ) {
JpGraphError::RaiseL(25037,$aFile);//('The image format of your background image ('.$aFile.') is not supported in your system configuration. ');
}
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. Specified: '".$aImgFormat."'File: '".$aFile."'";
JpGraphError::RaiseL(25038, $aImgFormat, $aFile);
}
$img = @$f($aFile);
if( !$img ) {
JpGraphError::RaiseL(25039,$aFile);//(" 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+1 ;
$yb = $yt + $this->img->plotheight ;
$grad->FilledRectangle($xl,$yt,$xr,$yb,$this->bkg_gradfrom,$this->bkg_gradto,$this->bkg_gradtype);
}
else {
$xl = 0;
$yt = 0;
$xr = $xl + $this->img->width - 1;
$yb = $yt + $this->img->height;
if( $this->doshadow ) {
$xr -= $this->shadow_width;
$yb -= $this->shadow_width;
}
if( $this->doframe ) {
$yt += $this->frame_weight;
$yb -= $this->frame_weight;
$xl += $this->frame_weight;
$xr -= $this->frame_weight;
}
$aa = $this->img->SetAngle(0);
$grad->FilledRectangle($xl,$yt,$xr,$yb,$this->bkg_gradfrom,$this->bkg_gradto,$this->bkg_gradtype);
$aa = $this->img->SetAngle($aa);
}
}
function StrokeFrameBackground() {
if( $this->background_image != "" && $this->background_cflag != "" ) {
JpGraphError::RaiseL(25040);//('It is not possible to specify both a background image and a background country flag.');
}
if( $this->background_image != "" ) {
$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);
}
elseif( $this->background_cflag != "" ) {
if( ! class_exists('FlagImages') ) {
JpGraphError::RaiseL(25041);//('In order to use Country flags as backgrounds you must include the "jpgraph_flags.php" file.');
}
$fobj = new FlagImages(FLAGSIZE4);
$dummy='';
$bkgimg = $fobj->GetImgByName($this->background_cflag,$dummy);
$this->background_image_mix = $this->background_cflag_mix;
$this->background_image_type = $this->background_cflag_type;
}
else {
return ;
}
$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->FillMarginArea();
$this->StrokeFrame();
$this->FillPlotArea();
$this->img->CopyMerge($bkgimg,
$this->img->left_margin,$this->img->top_margin,
0,0,$this->img->plotwidth+1,$this->img->plotheight,
$bw,$bh,$this->background_image_mix);
break;
case BGIMG_FILLFRAME: // Fill the whole area from upper left corner, resize to just fit
$hadj=0; $vadj=0;
if( $this->doshadow ) {
$hadj = $this->shadow_width;
$vadj = $this->shadow_width;
}
$this->FillMarginArea();
$this->FillPlotArea();
$this->img->CopyMerge($bkgimg,0,0,0,0,$this->img->width-$hadj,$this->img->height-$vadj,
$bw,$bh,$this->background_image_mix);
$this->StrokeFrame();
break;
case BGIMG_COPY: // Just copy the image from left corner, no resizing
$this->FillMarginArea();
$this->FillPlotArea();
$this->img->CopyMerge($bkgimg,0,0,0,0,$bw,$bh,
$bw,$bh,$this->background_image_mix);
$this->StrokeFrame();
break;
case BGIMG_CENTER: // Center original image in the plot area
$this->FillMarginArea();
$this->FillPlotArea();
$centerx = round($this->img->plotwidth/2+$this->img->left_margin-$bw/2);
$centery = round($this->img->plotheight/2+$this->img->top_margin-$bh/2);
$this->img->CopyMerge($bkgimg,$centerx,$centery,0,0,$bw,$bh,
$bw,$bh,$this->background_image_mix);
$this->StrokeFrame();
break;
default:
JpGraphError::RaiseL(25042);//(" 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);
}
}
function FillMarginArea() {
$hadj=0; $vadj=0;
if( $this->doshadow ) {
$hadj = $this->shadow_width;
$vadj = $this->shadow_width;
}
$this->img->SetColor($this->margin_color);
// $this->img->FilledRectangle(0,0,$this->img->width-1-$hadj,$this->img->height-1-$vadj);
$this->img->FilledRectangle(0,0,$this->img->width-1-$hadj,$this->img->top_margin);