Subversion Repositories Applications.papyrus

Rev

Details | Last modification | View Log | RSS feed

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