Subversion Repositories Sites.obs-saisons.fr

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 aurelien 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 1076 2008-09-19 15:43:22Z ljp $
7
//
8
// Copyright 2006 (c) Aditus Consulting. All rights reserved.
9
//========================================================================
10
 
11
require_once('jpg-config.inc.php');
12
require_once('jpgraph_errhandler.inc.php');
13
require_once('gd_image.inc.php');
14
require_once('jpgraph_ttf.inc.php');
15
require_once 'jpgraph_gradient.php';
16
 
17
// Version info
18
DEFINE('JPG_VERSION','1.26.1-dev');
19
 
20
// Minimum required PHP version
21
DEFINE('MIN_PHPVERSION','4.3.1');
22
 
23
//------------------------------------------------------------------------
24
// Automatic settings of path for cache and font directory
25
// if they have not been previously specified
26
//------------------------------------------------------------------------
27
if(USE_CACHE) {
28
    if (!defined('CACHE_DIR')) {
29
	if ( strstr( PHP_OS, 'WIN') ) {
30
	    if( empty($_SERVER['TEMP']) ) {
31
		$t = new ErrMsgText();
32
		$msg = $t->Get(11,$file,$lineno);
33
		die($msg);
34
	    }
35
	    else {
36
		DEFINE('CACHE_DIR', $_SERVER['TEMP'] . '/');
37
	    }
38
	} else {
39
	    DEFINE('CACHE_DIR','/tmp/jpgraph_cache/');
40
	}
41
    }
42
}
43
elseif( !defined('CACHE_DIR') ) {
44
    DEFINE('CACHE_DIR', '');
45
}
46
 
47
if (!defined('TTF_DIR')) {
48
    if (strstr( PHP_OS, 'WIN') ) {
49
	$sroot = getenv('SystemRoot');
50
        if( empty($sroot) ) {
51
	    $t = new ErrMsgText();
52
	    $msg = $t->Get(12,$file,$lineno);
53
	    die($msg);
54
        }
55
	else {
56
	  DEFINE('TTF_DIR', $sroot.'/fonts/');
57
        }
58
    } else {
59
	DEFINE('TTF_DIR','/usr/X11R6/lib/X11/fonts/truetype/');
60
    }
61
}
62
 
63
if (!defined('MBTTF_DIR')) {
64
    if (strstr( PHP_OS, 'WIN') ) {
65
	$sroot = getenv('SystemRoot');
66
        if( empty($sroot) ) {
67
	    $t = new ErrMsgText();
68
	    $msg = $t->Get(12,$file,$lineno);
69
	    die($msg);
70
        }
71
	else {
72
	  DEFINE('TTF_DIR', $sroot.'/fonts/');
73
        }
74
    } else {
75
	DEFINE('MBTTF_DIR','/usr/share/fonts/ja/TrueType/');
76
    }
77
}
78
 
79
//------------------------------------------------------------------
80
// Constants which are used as parameters for the method calls
81
//------------------------------------------------------------------
82
 
83
// Tick density
84
DEFINE("TICKD_DENSE",1);
85
DEFINE("TICKD_NORMAL",2);
86
DEFINE("TICKD_SPARSE",3);
87
DEFINE("TICKD_VERYSPARSE",4);
88
 
89
// Side for ticks and labels.
90
DEFINE("SIDE_LEFT",-1);
91
DEFINE("SIDE_RIGHT",1);
92
DEFINE("SIDE_DOWN",-1);
93
DEFINE("SIDE_BOTTOM",-1);
94
DEFINE("SIDE_UP",1);
95
DEFINE("SIDE_TOP",1);
96
 
97
// Legend type stacked vertical or horizontal
98
DEFINE("LEGEND_VERT",0);
99
DEFINE("LEGEND_HOR",1);
100
 
101
// Mark types for plot marks
102
DEFINE("MARK_SQUARE",1);
103
DEFINE("MARK_UTRIANGLE",2);
104
DEFINE("MARK_DTRIANGLE",3);
105
DEFINE("MARK_DIAMOND",4);
106
DEFINE("MARK_CIRCLE",5);
107
DEFINE("MARK_FILLEDCIRCLE",6);
108
DEFINE("MARK_CROSS",7);
109
DEFINE("MARK_STAR",8);
110
DEFINE("MARK_X",9);
111
DEFINE("MARK_LEFTTRIANGLE",10);
112
DEFINE("MARK_RIGHTTRIANGLE",11);
113
DEFINE("MARK_FLASH",12);
114
DEFINE("MARK_IMG",13);
115
DEFINE("MARK_FLAG1",14);
116
DEFINE("MARK_FLAG2",15);
117
DEFINE("MARK_FLAG3",16);
118
DEFINE("MARK_FLAG4",17);
119
 
120
// Builtin images
121
DEFINE("MARK_IMG_PUSHPIN",50);
122
DEFINE("MARK_IMG_SPUSHPIN",50);
123
DEFINE("MARK_IMG_LPUSHPIN",51);
124
DEFINE("MARK_IMG_DIAMOND",52);
125
DEFINE("MARK_IMG_SQUARE",53);
126
DEFINE("MARK_IMG_STAR",54);
127
DEFINE("MARK_IMG_BALL",55);
128
DEFINE("MARK_IMG_SBALL",55);
129
DEFINE("MARK_IMG_MBALL",56);
130
DEFINE("MARK_IMG_LBALL",57);
131
DEFINE("MARK_IMG_BEVEL",58);
132
 
133
// Inline defines
134
DEFINE("INLINE_YES",1);
135
DEFINE("INLINE_NO",0);
136
 
137
// Format for background images
138
DEFINE("BGIMG_FILLPLOT",1);
139
DEFINE("BGIMG_FILLFRAME",2);
140
DEFINE("BGIMG_COPY",3);
141
DEFINE("BGIMG_CENTER",4);
142
 
143
// Depth of objects
144
DEFINE("DEPTH_BACK",0);
145
DEFINE("DEPTH_FRONT",1);
146
 
147
// Direction
148
DEFINE("VERTICAL",1);
149
DEFINE("HORIZONTAL",0);
150
 
151
// Axis styles for scientific style axis
152
DEFINE('AXSTYLE_SIMPLE',1);
153
DEFINE('AXSTYLE_BOXIN',2);
154
DEFINE('AXSTYLE_BOXOUT',3);
155
DEFINE('AXSTYLE_YBOXIN',4);
156
DEFINE('AXSTYLE_YBOXOUT',5);
157
 
158
// Style for title backgrounds
159
DEFINE('TITLEBKG_STYLE1',1);
160
DEFINE('TITLEBKG_STYLE2',2);
161
DEFINE('TITLEBKG_STYLE3',3);
162
DEFINE('TITLEBKG_FRAME_NONE',0);
163
DEFINE('TITLEBKG_FRAME_FULL',1);
164
DEFINE('TITLEBKG_FRAME_BOTTOM',2);
165
DEFINE('TITLEBKG_FRAME_BEVEL',3);
166
DEFINE('TITLEBKG_FILLSTYLE_HSTRIPED',1);
167
DEFINE('TITLEBKG_FILLSTYLE_VSTRIPED',2);
168
DEFINE('TITLEBKG_FILLSTYLE_SOLID',3);
169
 
170
// Style for background gradient fills
171
DEFINE('BGRAD_FRAME',1);
172
DEFINE('BGRAD_MARGIN',2);
173
DEFINE('BGRAD_PLOT',3);
174
 
175
// Width of tab titles
176
DEFINE('TABTITLE_WIDTHFIT',0);
177
DEFINE('TABTITLE_WIDTHFULL',-1);
178
 
179
// Defines for 3D skew directions
180
DEFINE('SKEW3D_UP',0);
181
DEFINE('SKEW3D_DOWN',1);
182
DEFINE('SKEW3D_LEFT',2);
183
DEFINE('SKEW3D_RIGHT',3);
184
 
185
// For internal use only
186
DEFINE("_JPG_DEBUG",false);
187
DEFINE("_FORCE_IMGTOFILE",false);
188
DEFINE("_FORCE_IMGDIR",'/tmp/jpgimg/');
189
 
190
 
191
function CheckPHPVersion($aMinVersion)
192
{
193
    list($majorC, $minorC, $editC) = split('[/.-]', PHP_VERSION);
194
    list($majorR, $minorR, $editR) = split('[/.-]', $aMinVersion);
195
 
196
    if ($majorC > $majorR) return true;
197
    if ($majorC < $majorR) return false;
198
    // same major - check minor
199
    if ($minorC > $minorR) return true;
200
    if ($minorC < $minorR) return false;
201
    // and same minor
202
    if ($editC  >= $editR)  return true;
203
    return true;
204
}
205
 
206
//
207
// Make sure PHP version is high enough
208
//
209
if( !CheckPHPVersion(MIN_PHPVERSION) ) {
210
    JpGraphError::RaiseL(13,PHP_VERSION,MIN_PHPVERSION);
211
}
212
 
213
//
214
// Routine to determine if GD1 or GD2 is installed
215
//
216
function CheckGDVersion() {
217
    if( !function_exists("imagetypes") || !function_exists('imagecreatefromstring') )
218
	return 0;
219
    $GDfuncList = get_extension_funcs('gd');
220
    if( !$GDfuncList )
221
	return 0 ;
222
    else {
223
	if( in_array('imagegd2',$GDfuncList) &&
224
	    in_array('imagecreatetruecolor',$GDfuncList))
225
	    return 2;
226
	else
227
	    return 1;
228
    }
229
}
230
 
231
//
232
// Check what version of the GD library is installed.
233
//
234
$gdversion = CheckGDVersion();
235
if( $gdversion != 2 ) {
236
    JpGraphError::RaiseL(25002);
237
//(" Your PHP installation does not seem to have the required GD 2.x library. Please see the PHP documentation on how to install and enable the GD library.");
238
}
239
 
240
//
241
// Setup PHP error handler
242
//
243
function _phpErrorHandler($errno,$errmsg,$filename, $linenum, $vars) {
244
    // Respect current error level
245
    if( $errno & error_reporting() ) {
246
	JpGraphError::RaiseL(25003,basename($filename),$linenum,$errmsg);
247
    }
248
}
249
 
250
if( INSTALL_PHP_ERR_HANDLER ) {
251
    set_error_handler("_phpErrorHandler");
252
}
253
 
254
//
255
//Check if there were any warnings, perhaps some wrong includes by the user
256
//
257
if( isset($GLOBALS['php_errormsg']) && CATCH_PHPERRMSG &&
258
    !preg_match('/|Deprecated|/i', $GLOBALS['php_errormsg'])) {
259
    JpGraphError::RaiseL(25004,$GLOBALS['php_errormsg']);
260
}
261
 
262
 
263
// Useful mathematical function
264
function sign($a) {return $a >= 0 ? 1 : -1;}
265
 
266
// Utility function to generate an image name based on the filename we
267
// are running from and assuming we use auto detection of graphic format
268
// (top level), i.e it is safe to call this function
269
// from a script that uses JpGraph
270
function GenImgName() {
271
    global $_SERVER;
272
 
273
    // Determine what format we should use when we save the images
274
    $supported = imagetypes();
275
    if( $supported & IMG_PNG )	   $img_format="png";
276
    elseif( $supported & IMG_GIF ) $img_format="gif";
277
    elseif( $supported & IMG_JPG ) $img_format="jpeg";
278
    elseif( $supported & IMG_WBMP ) $img_format="wbmp";
279
    elseif( $supported & IMG_XPM ) $img_format="xpm";
280
 
281
    if( !isset($_SERVER['PHP_SELF']) )
282
	JpGraphError::RaiseL(25005);
283
//(" 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.");
284
    $fname = basename($_SERVER['PHP_SELF']);
285
    if( !empty($_SERVER['QUERY_STRING']) ) {
286
	$q = @$_SERVER['QUERY_STRING'];
287
	$fname .= '_'.preg_replace("/\W/", "_", $q).'.'.$img_format;
288
    }
289
    else {
290
	$fname = substr($fname,0,strlen($fname)-4).'.'.$img_format;
291
    }
292
    return $fname;
293
}
294
 
295
//===================================================
296
// CLASS JpgTimer
297
// Description: General timing utility class to handle
298
// time measurement of generating graphs. Multiple
299
// timers can be started.
300
//===================================================
301
class JpgTimer {
302
    var $start;
303
    var $idx;
304
//---------------
305
// CONSTRUCTOR
306
    function JpgTimer() {
307
	$this->idx=0;
308
    }
309
 
310
//---------------
311
// PUBLIC METHODS
312
 
313
    // Push a new timer start on stack
314
    function Push() {
315
	list($ms,$s)=explode(" ",microtime());
316
	$this->start[$this->idx++]=floor($ms*1000) + 1000*$s;
317
    }
318
 
319
    // Pop the latest timer start and return the diff with the
320
    // current time
321
    function Pop() {
322
	assert($this->idx>0);
323
	list($ms,$s)=explode(" ",microtime());
324
	$etime=floor($ms*1000) + (1000*$s);
325
	$this->idx--;
326
	return $etime-$this->start[$this->idx];
327
    }
328
} // Class
329
 
330
$gJpgBrandTiming = BRAND_TIMING;
331
//===================================================
332
// CLASS DateLocale
333
// Description: Hold localized text used in dates
334
//===================================================
335
class DateLocale {
336
 
337
    var $iLocale = 'C'; // environmental locale be used by default
338
 
339
    var $iDayAbb = null;
340
    var $iShortDay = null;
341
    var $iShortMonth = null;
342
    var $iMonthName = null;
343
 
344
//---------------
345
// CONSTRUCTOR
346
    function DateLocale() {
347
	settype($this->iDayAbb, 'array');
348
	settype($this->iShortDay, 'array');
349
	settype($this->iShortMonth, 'array');
350
	settype($this->iMonthName, 'array');
351
 
352
 
353
	$this->Set('C');
354
    }
355
 
356
//---------------
357
// PUBLIC METHODS
358
    function Set($aLocale) {
359
	if ( in_array($aLocale, array_keys($this->iDayAbb)) ){
360
	    $this->iLocale = $aLocale;
361
	    return TRUE;  // already cached nothing else to do!
362
	}
363
 
364
	$pLocale = setlocale(LC_TIME, 0); // get current locale for LC_TIME
365
 
366
	if (is_array($aLocale)) {
367
	    foreach ($aLocale as $loc) {
368
		$res = @setlocale(LC_TIME, $loc);
369
		if ( $res ) {
370
		    $aLocale = $loc;
371
		    break;
372
		}
373
	    }
374
	}
375
	else {
376
	    $res = @setlocale(LC_TIME, $aLocale);
377
	}
378
	if ( ! $res ){
379
	    JpGraphError::RaiseL(25007,$aLocale);
380
//("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.");
381
	    return FALSE;
382
	}
383
 
384
	$this->iLocale = $aLocale;
385
 
386
	for ( $i = 0, $ofs = 0 - strftime('%w'); $i < 7; $i++, $ofs++ ){
387
	    $day = strftime('%a', strtotime("$ofs day"));
388
	    $day{0} = strtoupper($day{0});
389
	    $this->iDayAbb[$aLocale][]= $day{0};
390
	    $this->iShortDay[$aLocale][]= $day;
391
	}
392
 
393
	for($i=1; $i<=12; ++$i) {
394
	    list($short ,$full) = explode('|', strftime("%b|%B",strtotime("2001-$i-01")));
395
	    $this->iShortMonth[$aLocale][] = ucfirst($short);
396
	    $this->iMonthName [$aLocale][] = ucfirst($full);
397
	}
398
 
399
	// Return to original locale
400
	setlocale(LC_TIME, $pLocale);
401
 
402
	return TRUE;
403
    }
404
 
405
 
406
    function GetDayAbb() {
407
	return $this->iDayAbb[$this->iLocale];
408
    }
409
 
410
    function GetShortDay() {
411
	return $this->iShortDay[$this->iLocale];
412
    }
413
 
414
    function GetShortMonth() {
415
	return $this->iShortMonth[$this->iLocale];
416
    }
417
 
418
    function GetShortMonthName($aNbr) {
419
	return $this->iShortMonth[$this->iLocale][$aNbr];
420
    }
421
 
422
    function GetLongMonthName($aNbr) {
423
	return $this->iMonthName[$this->iLocale][$aNbr];
424
    }
425
 
426
    function GetMonth() {
427
	return $this->iMonthName[$this->iLocale];
428
    }
429
}
430
 
431
$gDateLocale = new DateLocale();
432
$gJpgDateLocale = new DateLocale();
433
 
434
 
435
//=======================================================
436
// CLASS Footer
437
// Description: Encapsulates the footer line in the Graph
438
//=======================================================
439
class Footer {
440
    var $left,$center,$right;
441
    var $iLeftMargin = 3;
442
    var $iRightMargin = 3;
443
    var $iBottomMargin = 3;
444
 
445
    function Footer() {
446
	$this->left = new Text();
447
	$this->left->ParagraphAlign('left');
448
	$this->center = new Text();
449
	$this->center->ParagraphAlign('center');
450
	$this->right = new Text();
451
	$this->right->ParagraphAlign('right');
452
    }
453
 
454
    function Stroke(&$aImg) {
455
	$y = $aImg->height - $this->iBottomMargin;
456
	$x = $this->iLeftMargin;
457
	$this->left->Align('left','bottom');
458
	$this->left->Stroke($aImg,$x,$y);
459
 
460
	$x = ($aImg->width - $this->iLeftMargin - $this->iRightMargin)/2;
461
	$this->center->Align('center','bottom');
462
	$this->center->Stroke($aImg,$x,$y);
463
 
464
	$x = $aImg->width - $this->iRightMargin;
465
	$this->right->Align('right','bottom');
466
	$this->right->Stroke($aImg,$x,$y);
467
    }
468
}
469
 
470
 
471
//===================================================
472
// CLASS Graph
473
// Description: Main class to handle graphs
474
//===================================================
475
class Graph {
476
    var $cache=null;		// Cache object (singleton)
477
    var $img=null;			// Img object (singleton)
478
    var $plots=array();	// Array of all plot object in the graph (for Y 1 axis)
479
    var $y2plots=array();// Array of all plot object in the graph (for Y 2 axis)
480
    var $ynplots=array();
481
    var $xscale=null;		// X Scale object (could be instance of LinearScale or LogScale
482
    var $yscale=null,$y2scale=null, $ynscale=array();
483
    var $iIcons = array();      // Array of Icons to add to
484
    var $cache_name;		// File name to be used for the current graph in the cache directory
485
    var $xgrid=null;		// X Grid object (linear or logarithmic)
486
    var $ygrid=null,$y2grid=null;
487
    var $doframe=true,$frame_color=array(0,0,0), $frame_weight=1;	// Frame around graph
488
    var $boxed=false, $box_color=array(0,0,0), $box_weight=1;		// Box around plot area
489
    var $doshadow=false,$shadow_width=4,$shadow_color=array(102,102,102);	// Shadow for graph
490
    var $xaxis=null;		// X-axis (instane of Axis class)
491
    var $yaxis=null, $y2axis=null, $ynaxis=array();	// Y axis (instance of Axis class)
492
    var $margin_color=array(200,200,200);	// Margin color of graph
493
    var $plotarea_color=array(255,255,255);	// Plot area color
494
    var $title,$subtitle,$subsubtitle; 	// Title and subtitle(s) text object
495
    var $axtype="linlin";		// Type of axis
496
    var $xtick_factor,$ytick_factor;			// Factot to determine the maximum number of ticks depending on the plot with
497
    var $texts=null, $y2texts=null; 	// Text object to ge shown in the graph
498
    var $lines=null, $y2lines=null;
499
    var $bands=null, $y2bands=null;
500
    var $text_scale_off=0, $text_scale_abscenteroff=-1; // Text scale offset in fractions and for centering bars in absolute pixels
501
    var $background_image="",$background_image_type=-1,$background_image_format="png";
502
    var $inline;
503
    var $showcsim=0,$csimcolor="red"; //debug stuff, draw the csim boundaris on the image if <>0
504
    var $grid_depth=DEPTH_BACK;	// Draw grid under all plots as default
505
    var $iAxisStyle = AXSTYLE_SIMPLE;
506
    var $iCSIMdisplay=false,$iHasStroked = false;
507
    var $footer;
508
    var $csimcachename = '', $csimcachetimeout = 0, $iCSIMImgAlt='';
509
    var $iDoClipping = false;
510
    var $y2orderback=true;
511
    var $tabtitle;
512
    var $bkg_gradtype=-1,$bkg_gradstyle=BGRAD_MARGIN;
513
    var $bkg_gradfrom='navy', $bkg_gradto='silver';
514
    var $titlebackground = false;
515
    var	$titlebackground_color = 'lightblue',
516
	$titlebackground_style = 1,
517
	$titlebackground_framecolor = 'blue',
518
	$titlebackground_framestyle = 2,
519
	$titlebackground_frameweight = 1,
520
	$titlebackground_bevelheight = 3 ;
521
    var $titlebkg_fillstyle=TITLEBKG_FILLSTYLE_SOLID;
522
    var $titlebkg_scolor1='black',$titlebkg_scolor2='white';
523
    var $framebevel = false, $framebeveldepth = 2 ;
524
    var $framebevelborder = false, $framebevelbordercolor='black';
525
    var $framebevelcolor1='white@0.4', $framebevelcolor2='black@0.4';
526
    var $background_image_mix=100;
527
    var $background_cflag = '';
528
    var $background_cflag_type = BGIMG_FILLPLOT;
529
    var $background_cflag_mix = 100;
530
    var $iImgTrans=false,
531
	$iImgTransHorizon = 100,$iImgTransSkewDist=150,
532
	$iImgTransDirection = 1, $iImgTransMinSize = true,
533
	$iImgTransFillColor='white',$iImgTransHighQ=false,
534
	$iImgTransBorder=false,$iImgTransHorizonPos=0.5;
535
    var $iYAxisDeltaPos=50;
536
    var $iIconDepth=DEPTH_BACK;
537
    var $iAxisLblBgType = 0,
538
	$iXAxisLblBgFillColor = 'lightgray', $iXAxisLblBgColor = 'black',
539
	$iYAxisLblBgFillColor = 'lightgray', $iYAxisLblBgColor = 'black';
540
    var $iTables=NULL;
541
    var $legend;
542
 
543
//---------------
544
// CONSTRUCTOR
545
 
546
    // aWIdth 		Width in pixels of image
547
    // aHeight  	Height in pixels of image
548
    // aCachedName	Name for image file in cache directory
549
    // aTimeOut		Timeout in minutes for image in cache
550
    // aInline		If true the image is streamed back in the call to Stroke()
551
    //			If false the image is just created in the cache
552
    function Graph($aWidth=300,$aHeight=200,$aCachedName="",$aTimeOut=0,$aInline=true) {
553
	GLOBAL $gJpgBrandTiming;
554
	// If timing is used create a new timing object
555
	if( $gJpgBrandTiming ) {
556
	    global $tim;
557
	    $tim = new JpgTimer();
558
	    $tim->Push();
559
	}
560
 
561
	if( !is_numeric($aWidth) || !is_numeric($aHeight) ) {
562
	    JpGraphError::RaiseL(25008);//('Image width/height argument in Graph::Graph() must be numeric');
563
	}
564
 
565
	// Automatically generate the image file name based on the name of the script that
566
	// generates the graph
567
	if( $aCachedName=="auto" )
568
	    $aCachedName=GenImgName();
569
 
570
	// Should the image be streamed back to the browser or only to the cache?
571
	$this->inline=$aInline;
572
 
573
	$this->img	= new RotImage($aWidth,$aHeight);
574
 
575
	$this->cache 	= new ImgStreamCache($this->img);
576
	$this->cache->SetTimeOut($aTimeOut);
577
 
578
	$this->title = new Text();
579
	$this->title->ParagraphAlign('center');
580
	$this->title->SetFont(FF_FONT2,FS_BOLD);
581
	$this->title->SetMargin(3);
582
	$this->title->SetAlign('center');
583
 
584
	$this->subtitle = new Text();
585
	$this->subtitle->ParagraphAlign('center');
586
	$this->subtitle->SetMargin(2);
587
	$this->subtitle->SetAlign('center');
588
 
589
	$this->subsubtitle = new Text();
590
	$this->subsubtitle->ParagraphAlign('center');
591
	$this->subsubtitle->SetMargin(2);
592
	$this->subsubtitle->SetAlign('center');
593
 
594
	$this->legend = new Legend();
595
	$this->footer = new Footer();
596
 
597
	// Window doesn't like '?' in the file name so replace it with an '_'
598
	$aCachedName = str_replace("?","_",$aCachedName);
599
 
600
	// If the cached version exist just read it directly from the
601
	// cache, stream it back to browser and exit
602
	if( $aCachedName!="" && READ_CACHE && $aInline )
603
	    if( $this->cache->GetAndStream($aCachedName) ) {
604
		exit();
605
	    }
606
 
607
	$this->cache_name = $aCachedName;
608
	$this->SetTickDensity(); // Normal density
609
 
610
	$this->tabtitle = new GraphTabTitle();
611
    }
612
//---------------
613
// PUBLIC METHODS
614
    // Enable final image perspective transformation
615
    function Set3DPerspective($aDir=1,$aHorizon=100,$aSkewDist=120,$aQuality=false,$aFillColor='#FFFFFF',$aBorder=false,$aMinSize=true,$aHorizonPos=0.5) {
616
	$this->iImgTrans = true;
617
	$this->iImgTransHorizon = $aHorizon;
618
	$this->iImgTransSkewDist= $aSkewDist;
619
	$this->iImgTransDirection = $aDir;
620
	$this->iImgTransMinSize = $aMinSize;
621
	$this->iImgTransFillColor=$aFillColor;
622
	$this->iImgTransHighQ=$aQuality;
623
	$this->iImgTransBorder=$aBorder;
624
	$this->iImgTransHorizonPos=$aHorizonPos;
625
    }
626
 
627
    // Set Image format and optional quality
628
    function SetImgFormat($aFormat,$aQuality=75) {
629
	$this->img->SetImgFormat($aFormat,$aQuality);
630
    }
631
 
632
    // Should the grid be in front or back of the plot?
633
    function SetGridDepth($aDepth) {
634
	$this->grid_depth=$aDepth;
635
    }
636
 
637
    function SetIconDepth($aDepth) {
638
	$this->iIconDepth=$aDepth;
639
    }
640
 
641
    // Specify graph angle 0-360 degrees.
642
    function SetAngle($aAngle) {
643
	$this->img->SetAngle($aAngle);
644
    }
645
 
646
    function SetAlphaBlending($aFlg=true) {
647
	$this->img->SetAlphaBlending($aFlg);
648
    }
649
 
650
    // Shortcut to image margin
651
    function SetMargin($lm,$rm,$tm,$bm) {
652
	$this->img->SetMargin($lm,$rm,$tm,$bm);
653
    }
654
 
655
    function SetY2OrderBack($aBack=true) {
656
	$this->y2orderback = $aBack;
657
    }
658
 
659
    // Rotate the graph 90 degrees and set the margin
660
    // when we have done a 90 degree rotation
661
    function Set90AndMargin($lm=0,$rm=0,$tm=0,$bm=0) {
662
	$lm = $lm ==0 ? floor(0.2 * $this->img->width)  : $lm ;
663
	$rm = $rm ==0 ? floor(0.1 * $this->img->width)  : $rm ;
664
	$tm = $tm ==0 ? floor(0.2 * $this->img->height) : $tm ;
665
	$bm = $bm ==0 ? floor(0.1 * $this->img->height) : $bm ;
666
 
667
	$adj = ($this->img->height - $this->img->width)/2;
668
	$this->img->SetMargin($tm-$adj,$bm-$adj,$rm+$adj,$lm+$adj);
669
	$this->img->SetCenter(floor($this->img->width/2),floor($this->img->height/2));
670
	$this->SetAngle(90);
671
	if( empty($this->yaxis) || empty($this->xaxis) ) {
672
	    JpgraphError::RaiseL(25009);//('You must specify what scale to use with a call to Graph::SetScale()');
673
	}
674
	$this->xaxis->SetLabelAlign('right','center');
675
	$this->yaxis->SetLabelAlign('center','bottom');
676
    }
677
 
678
    function SetClipping($aFlg=true) {
679
	$this->iDoClipping = $aFlg ;
680
    }
681
 
682
    // Add a plot object to the graph
683
    function Add(&$aPlot) {
684
	if( $aPlot == null )
685
	    JpGraphError::RaiseL(25010);//("Graph::Add() You tried to add a null plot to the graph.");
686
	if( is_array($aPlot) && count($aPlot) > 0 )
687
	    $cl = $aPlot[0];
688
	else
689
	    $cl = $aPlot;
690
 
691
	if( is_a($cl,'Text') )
692
	    $this->AddText($aPlot);
693
	elseif( is_a($cl,'PlotLine') )
694
	    $this->AddLine($aPlot);
695
	elseif( is_a($cl,'PlotBand') )
696
	    $this->AddBand($aPlot);
697
	elseif( is_a($cl,'IconPlot') )
698
	    $this->AddIcon($aPlot);
699
	elseif( is_a($cl,'GTextTable') )
700
	    $this->AddTable($aPlot);
701
	else
702
	    $this->plots[] = &$aPlot;
703
    }
704
 
705
 
706
    function AddTable(&$aTable) {
707
	if( is_array($aTable) ) {
708
	    for($i=0; $i < count($aTable); ++$i )
709
		$this->iTables[]=&$aTable[$i];
710
	}
711
	else {
712
	    $this->iTables[] = &$aTable ;
713
	}
714
    }
715
 
716
    function AddIcon(&$aIcon) {
717
	if( is_array($aIcon) ) {
718
	    for($i=0; $i < count($aIcon); ++$i )
719
		$this->iIcons[]=&$aIcon[$i];
720
	}
721
	else {
722
	    $this->iIcons[] = &$aIcon ;
723
	}
724
    }
725
 
726
    // Add plot to second Y-scale
727
    function AddY2(&$aPlot) {
728
	if( $aPlot == null )
729
	    JpGraphError::RaiseL(25011);//("Graph::AddY2() You tried to add a null plot to the graph.");
730
 
731
	if( is_array($aPlot) && count($aPlot) > 0 )
732
	    $cl = $aPlot[0];
733
	else
734
	    $cl = $aPlot;
735
 
736
	if( is_a($cl,'Text') )
737
	    $this->AddText($aPlot,true);
738
	elseif( is_a($cl,'PlotLine') )
739
	    $this->AddLine($aPlot,true);
740
	elseif( is_a($cl,'PlotBand') )
741
	    $this->AddBand($aPlot,true);
742
	else
743
	    $this->y2plots[] = &$aPlot;
744
    }
745
 
746
    // Add plot to second Y-scale
747
    function AddY($aN,&$aPlot) {
748
 
749
	if( $aPlot == null )
750
	    JpGraphError::RaiseL(25012);//("Graph::AddYN() You tried to add a null plot to the graph.");
751
 
752
	if( is_array($aPlot) && count($aPlot) > 0 )
753
	    $cl = $aPlot[0];
754
	else
755
	    $cl = $aPlot;
756
 
757
	if( is_a($cl,'Text') || is_a($cl,'PlotLine') || is_a($cl,'PlotBand') )
758
	    JpGraph::RaiseL(25013);//('You can only add standard plots to multiple Y-axis');
759
	else
760
	    $this->ynplots[$aN][] = &$aPlot;
761
    }
762
 
763
    // Add text object to the graph
764
    function AddText(&$aTxt,$aToY2=false) {
765
	if( $aTxt == null )
766
	    JpGraphError::RaiseL(25014);//("Graph::AddText() You tried to add a null text to the graph.");
767
	if( $aToY2 ) {
768
	    if( is_array($aTxt) ) {
769
		for($i=0; $i < count($aTxt); ++$i )
770
		    $this->y2texts[]=&$aTxt[$i];
771
	    }
772
	    else
773
		$this->y2texts[] = &$aTxt;
774
	}
775
	else {
776
	    if( is_array($aTxt) ) {
777
		for($i=0; $i < count($aTxt); ++$i )
778
		    $this->texts[]=&$aTxt[$i];
779
	    }
780
	    else
781
		$this->texts[] = &$aTxt;
782
	}
783
    }
784
 
785
    // Add a line object (class PlotLine) to the graph
786
    function AddLine(&$aLine,$aToY2=false) {
787
	if( $aLine == null )
788
	    JpGraphError::RaiseL(25015);//("Graph::AddLine() You tried to add a null line to the graph.");
789
 
790
	if( $aToY2 ) {
791
 	    if( is_array($aLine) ) {
792
		for($i=0; $i < count($aLine); ++$i )
793
		    $this->y2lines[]=&$aLine[$i];
794
	    }
795
	    else
796
		$this->y2lines[] = &$aLine;
797
	}
798
	else {
799
 	    if( is_array($aLine) ) {
800
		for($i=0; $i < count($aLine); ++$i )
801
		    $this->lines[]=&$aLine[$i];
802
	    }
803
	    else
804
		$this->lines[] = &$aLine;
805
	}
806
    }
807
 
808
    // Add vertical or horizontal band
809
    function AddBand(&$aBand,$aToY2=false) {
810
	if( $aBand == null )
811
	    JpGraphError::RaiseL(25016);//(" Graph::AddBand() You tried to add a null band to the graph.");
812
 
813
	if( $aToY2 ) {
814
	    if( is_array($aBand) ) {
815
		for($i=0; $i < count($aBand); ++$i )
816
		    $this->y2bands[] = &$aBand[$i];
817
	    }
818
	    else
819
		$this->y2bands[] = &$aBand;
820
	}
821
	else {
822
	    if( is_array($aBand) ) {
823
		for($i=0; $i < count($aBand); ++$i )
824
		    $this->bands[] = &$aBand[$i];
825
	    }
826
	    else
827
		$this->bands[] = &$aBand;
828
	}
829
    }
830
 
831
    function SetBackgroundGradient($aFrom='navy',$aTo='silver',$aGradType=2,$aStyle=BGRAD_FRAME) {
832
	$this->bkg_gradtype=$aGradType;
833
	$this->bkg_gradstyle=$aStyle;
834
	$this->bkg_gradfrom = $aFrom;
835
	$this->bkg_gradto = $aTo;
836
    }
837
 
838
    // Set a country flag in the background
839
    function SetBackgroundCFlag($aName,$aBgType=BGIMG_FILLPLOT,$aMix=100) {
840
	$this->background_cflag = $aName;
841
	$this->background_cflag_type = $aBgType;
842
	$this->background_cflag_mix = $aMix;
843
    }
844
 
845
    // Alias for the above method
846
    function SetBackgroundCountryFlag($aName,$aBgType=BGIMG_FILLPLOT,$aMix=100) {
847
	$this->background_cflag = $aName;
848
	$this->background_cflag_type = $aBgType;
849
	$this->background_cflag_mix = $aMix;
850
    }
851
 
852
 
853
    // Specify a background image
854
    function SetBackgroundImage($aFileName,$aBgType=BGIMG_FILLPLOT,$aImgFormat="auto") {
855
 
856
	// Get extension to determine image type
857
	if( $aImgFormat == "auto" ) {
858
	    $e = explode('.',$aFileName);
859
	    if( !$e ) {
860
		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');
861
	    }
862
 
863
	    $valid_formats = array('png', 'jpg', 'gif');
864
	    $aImgFormat = strtolower($e[count($e)-1]);
865
	    if ($aImgFormat == 'jpeg')  {
866
		$aImgFormat = 'jpg';
867
	    }
868
	    elseif (!in_array($aImgFormat, $valid_formats) )  {
869
		JpGraphError::RaiseL(25019,$aImgFormat);//('Unknown file extension ($aImgFormat) in Graph::SetBackgroundImage() for filename: '.$aFileName);
870
	    }
871
	}
872
 
873
	$this->background_image = $aFileName;
874
	$this->background_image_type=$aBgType;
875
	$this->background_image_format=$aImgFormat;
876
    }
877
 
878
    function SetBackgroundImageMix($aMix) {
879
	$this->background_image_mix = $aMix ;
880
    }
881
 
882
    // Specify axis style (boxed or single)
883
    function SetAxisStyle($aStyle) {
884
        $this->iAxisStyle = $aStyle ;
885
    }
886
 
887
    // Set a frame around the plot area
888
    function SetBox($aDrawPlotFrame=true,$aPlotFrameColor=array(0,0,0),$aPlotFrameWeight=1) {
889
	$this->boxed = $aDrawPlotFrame;
890
	$this->box_weight = $aPlotFrameWeight;
891
	$this->box_color = $aPlotFrameColor;
892
    }
893
 
894
    // Specify color for the plotarea (not the margins)
895
    function SetColor($aColor) {
896
	$this->plotarea_color=$aColor;
897
    }
898
 
899
    // Specify color for the margins (all areas outside the plotarea)
900
    function SetMarginColor($aColor) {
901
	$this->margin_color=$aColor;
902
    }
903
 
904
    // Set a frame around the entire image
905
    function SetFrame($aDrawImgFrame=true,$aImgFrameColor=array(0,0,0),$aImgFrameWeight=1) {
906
	$this->doframe = $aDrawImgFrame;
907
	$this->frame_color = $aImgFrameColor;
908
	$this->frame_weight = $aImgFrameWeight;
909
    }
910
 
911
    function SetFrameBevel($aDepth=3,$aBorder=false,$aBorderColor='black',$aColor1='white@0.4',$aColor2='darkgray@0.4',$aFlg=true) {
912
	$this->framebevel = $aFlg ;
913
	$this->framebeveldepth = $aDepth ;
914
	$this->framebevelborder = $aBorder ;
915
	$this->framebevelbordercolor = $aBorderColor ;
916
	$this->framebevelcolor1 = $aColor1 ;
917
	$this->framebevelcolor2 = $aColor2 ;
918
 
919
	$this->doshadow = false ;
920
    }
921
 
922
    // Set the shadow around the whole image
923
    function SetShadow($aShowShadow=true,$aShadowWidth=5,$aShadowColor=array(102,102,102)) {
924
	$this->doshadow = $aShowShadow;
925
	$this->shadow_color = $aShadowColor;
926
	$this->shadow_width = $aShadowWidth;
927
	$this->footer->iBottomMargin += $aShadowWidth;
928
	$this->footer->iRightMargin += $aShadowWidth;
929
    }
930
 
931
    // Specify x,y scale. Note that if you manually specify the scale
932
    // you must also specify the tick distance with a call to Ticks::Set()
933
    function SetScale($aAxisType,$aYMin=1,$aYMax=1,$aXMin=1,$aXMax=1) {
934
	$this->axtype = $aAxisType;
935
 
936
	if( $aYMax < $aYMin || $aXMax < $aXMin )
937
	    JpGraphError::RaiseL(25020);//('Graph::SetScale(): Specified Max value must be larger than the specified Min value.');
938
 
939
	$yt=substr($aAxisType,-3,3);
940
	if( $yt=="lin" )
941
	    $this->yscale = new LinearScale($aYMin,$aYMax);
942
	elseif( $yt == "int" ) {
943
	    $this->yscale = new LinearScale($aYMin,$aYMax);
944
	    $this->yscale->SetIntScale();
945
	}
946
	elseif( $yt=="log" )
947
	    $this->yscale = new LogScale($aYMin,$aYMax);
948
	else
949
	    JpGraphError::RaiseL(25021,$aAxisType);//("Unknown scale specification for Y-scale. ($aAxisType)");
950
 
951
	$xt=substr($aAxisType,0,3);
952
	if( $xt == "lin" || $xt == "tex" ) {
953
	    $this->xscale = new LinearScale($aXMin,$aXMax,"x");
954
	    $this->xscale->textscale = ($xt == "tex");
955
	}
956
	elseif( $xt == "int" ) {
957
	    $this->xscale = new LinearScale($aXMin,$aXMax,"x");
958
	    $this->xscale->SetIntScale();
959
	}
960
	elseif( $xt == "dat" ) {
961
	    $this->xscale = new DateScale($aXMin,$aXMax,"x");
962
	}
963
	elseif( $xt == "log" )
964
	    $this->xscale = new LogScale($aXMin,$aXMax,"x");
965
	else
966
	    JpGraphError::RaiseL(25022,$aAxisType);//(" Unknown scale specification for X-scale. ($aAxisType)");
967
 
968
	$this->xaxis = new Axis($this->img,$this->xscale);
969
	$this->yaxis = new Axis($this->img,$this->yscale);
970
	$this->xgrid = new Grid($this->xaxis);
971
	$this->ygrid = new Grid($this->yaxis);
972
	$this->ygrid->Show();
973
    }
974
 
975
    // Specify secondary Y scale
976
    function SetY2Scale($aAxisType="lin",$aY2Min=1,$aY2Max=1) {
977
	if( $aAxisType=="lin" )
978
	    $this->y2scale = new LinearScale($aY2Min,$aY2Max);
979
	elseif( $aAxisType == "int" ) {
980
	    $this->y2scale = new LinearScale($aY2Min,$aY2Max);
981
	    $this->y2scale->SetIntScale();
982
	}
983
	elseif( $aAxisType=="log" ) {
984
	    $this->y2scale = new LogScale($aY2Min,$aY2Max);
985
	}
986
	else JpGraphError::RaiseL(25023,$aAxisType);//("JpGraph: Unsupported Y2 axis type: $aAxisType\nMust be one of (lin,log,int)");
987
 
988
	$this->y2axis = new Axis($this->img,$this->y2scale);
989
	$this->y2axis->scale->ticks->SetDirection(SIDE_LEFT);
990
	$this->y2axis->SetLabelSide(SIDE_RIGHT);
991
	$this->y2axis->SetPos('max');
992
	$this->y2axis->SetTitleSide(SIDE_RIGHT);
993
 
994
	// Deafult position is the max x-value
995
	$this->y2grid = new Grid($this->y2axis);
996
    }
997
 
998
    // Set the delta position (in pixels) between the multiple Y-axis
999
    function SetYDeltaDist($aDist) {
1000
	$this->iYAxisDeltaPos = $aDist;
1001
    }
1002
 
1003
    // Specify secondary Y scale
1004
    function SetYScale($aN,$aAxisType="lin",$aYMin=1,$aYMax=1) {
1005
 
1006
	if( $aAxisType=="lin" )
1007
	    $this->ynscale[$aN] = new LinearScale($aYMin,$aYMax);
1008
	elseif( $aAxisType == "int" ) {
1009
	    $this->ynscale[$aN] = new LinearScale($aYMin,$aYMax);
1010
	    $this->ynscale[$aN]->SetIntScale();
1011
	}
1012
	elseif( $aAxisType=="log" ) {
1013
	    $this->ynscale[$aN] = new LogScale($aYMin,$aYMax);
1014
	}
1015
	else JpGraphError::RaiseL(25024,$aAxisType);//("JpGraph: Unsupported Y axis type: $aAxisType\nMust be one of (lin,log,int)");
1016
 
1017
	$this->ynaxis[$aN] = new Axis($this->img,$this->ynscale[$aN]);
1018
	$this->ynaxis[$aN]->scale->ticks->SetDirection(SIDE_LEFT);
1019
	$this->ynaxis[$aN]->SetLabelSide(SIDE_RIGHT);
1020
    }
1021
 
1022
 
1023
    // Specify density of ticks when autoscaling 'normal', 'dense', 'sparse', 'verysparse'
1024
    // The dividing factor have been determined heuristically according to my aesthetic
1025
    // sense (or lack off) y.m.m.v !
1026
    function SetTickDensity($aYDensity=TICKD_NORMAL,$aXDensity=TICKD_NORMAL) {
1027
	$this->xtick_factor=30;
1028
	$this->ytick_factor=25;
1029
	switch( $aYDensity ) {
1030
	    case TICKD_DENSE:
1031
		$this->ytick_factor=12;
1032
		break;
1033
	    case TICKD_NORMAL:
1034
		$this->ytick_factor=25;
1035
		break;
1036
	    case TICKD_SPARSE:
1037
		$this->ytick_factor=40;
1038
		break;
1039
	    case TICKD_VERYSPARSE:
1040
		$this->ytick_factor=100;
1041
		break;
1042
	    default:
1043
		JpGraphError::RaiseL(25025,$densy);//("JpGraph: Unsupported Tick density: $densy");
1044
	}
1045
	switch( $aXDensity ) {
1046
	    case TICKD_DENSE:
1047
		$this->xtick_factor=15;
1048
		break;
1049
	    case TICKD_NORMAL:
1050
		$this->xtick_factor=30;
1051
		break;
1052
	    case TICKD_SPARSE:
1053
		$this->xtick_factor=45;
1054
		break;
1055
	    case TICKD_VERYSPARSE:
1056
		$this->xtick_factor=60;
1057
		break;
1058
	    default:
1059
		JpGraphError::RaiseL(25025,$densx);//("JpGraph: Unsupported Tick density: $densx");
1060
	}
1061
    }
1062
 
1063
 
1064
    // Get a string of all image map areas
1065
    function GetCSIMareas() {
1066
	if( !$this->iHasStroked )
1067
	    $this->Stroke(_CSIM_SPECIALFILE);
1068
 
1069
	$csim = $this->title->GetCSIMAreas();
1070
	$csim .= $this->subtitle->GetCSIMAreas();
1071
	$csim .= $this->subsubtitle->GetCSIMAreas();
1072
	$csim .= $this->legend->GetCSIMAreas();
1073
 
1074
	if( $this->y2axis != NULL ) {
1075
	    $csim .= $this->y2axis->title->GetCSIMAreas();
1076
	}
1077
 
1078
	if( $this->texts != null ) {
1079
	    $n = count($this->texts);
1080
	    for($i=0; $i < $n; ++$i ) {
1081
		$csim .= $this->texts[$i]->GetCSIMAreas();
1082
	    }
1083
	}
1084
 
1085
	if( $this->y2texts != null && $this->y2scale != null ) {
1086
	    $n = count($this->y2texts);
1087
	    for($i=0; $i < $n; ++$i ) {
1088
		$csim .= $this->y2texts[$i]->GetCSIMAreas();
1089
	    }
1090
	}
1091
 
1092
	if( $this->yaxis != null && $this->xaxis != null ) {
1093
	    $csim .= $this->yaxis->title->GetCSIMAreas();
1094
	    $csim .= $this->xaxis->title->GetCSIMAreas();
1095
	}
1096
 
1097
	$n = count($this->plots);
1098
	for( $i=0; $i < $n; ++$i )
1099
	    $csim .= $this->plots[$i]->GetCSIMareas();
1100
 
1101
	$n = count($this->y2plots);
1102
	for( $i=0; $i < $n; ++$i )
1103
	    $csim .= $this->y2plots[$i]->GetCSIMareas();
1104
 
1105
	$n = count($this->ynaxis);
1106
	for( $i=0; $i < $n; ++$i ) {
1107
	    $m = count($this->ynplots[$i]);
1108
	    for($j=0; $j < $m; ++$j ) {
1109
		$csim .= $this->ynplots[$i][$j]->GetCSIMareas();
1110
	    }
1111
	}
1112
 
1113
	$n = count($this->iTables);
1114
	for( $i=0; $i < $n; ++$i ) {
1115
	    $csim .= $this->iTables[$i]->GetCSIMareas();
1116
	}
1117
 
1118
	return $csim;
1119
    }
1120
 
1121
    // Get a complete <MAP>..</MAP> tag for the final image map
1122
    function GetHTMLImageMap($aMapName) {
1123
	$im = "<map name=\"$aMapName\" id=\"$aMapName\" >\n";
1124
	$im .= $this->GetCSIMareas();
1125
	$im .= "</map>";
1126
	return $im;
1127
    }
1128
 
1129
    function CheckCSIMCache($aCacheName,$aTimeOut=60) {
1130
	global $_SERVER;
1131
 
1132
	if( $aCacheName=='auto' )
1133
	    $aCacheName=basename($_SERVER['PHP_SELF']);
1134
 
1135
	$urlarg = $this->GetURLArguments();
1136
	$this->csimcachename = CSIMCACHE_DIR.$aCacheName.$urlarg;
1137
	$this->csimcachetimeout = $aTimeOut;
1138
 
1139
	// First determine if we need to check for a cached version
1140
	// This differs from the standard cache in the sense that the
1141
	// image and CSIM map HTML file is written relative to the directory
1142
	// the script executes in and not the specified cache directory.
1143
	// The reason for this is that the cache directory is not necessarily
1144
	// accessible from the HTTP server.
1145
	if( $this->csimcachename != '' ) {
1146
	    $dir = dirname($this->csimcachename);
1147
	    $base = basename($this->csimcachename);
1148
	    $base = strtok($base,'.');
1149
	    $suffix = strtok('.');
1150
	    $basecsim = $dir.'/'.$base.'?'.$urlarg.'_csim_.html';
1151
	    $baseimg = $dir.'/'.$base.'?'.$urlarg.'.'.$this->img->img_format;
1152
 
1153
	    $timedout=false;
1154
	    // Does it exist at all ?
1155
 
1156
	    if( file_exists($basecsim) && file_exists($baseimg) ) {
1157
		// Check that it hasn't timed out
1158
		$diff=time()-filemtime($basecsim);
1159
		if( $this->csimcachetimeout>0 && ($diff > $this->csimcachetimeout*60) ) {
1160
		    $timedout=true;
1161
		    @unlink($basecsim);
1162
		    @unlink($baseimg);
1163
		}
1164
		else {
1165
		    if ($fh = @fopen($basecsim, "r")) {
1166
			fpassthru($fh);
1167
			return true;
1168
		    }
1169
		    else
1170
			JpGraphError::RaiseL(25027,$basecsim);//(" Can't open cached CSIM \"$basecsim\" for reading.");
1171
		}
1172
	    }
1173
	}
1174
	return false;
1175
    }
1176
 
1177
    // Build the argument string to be used with the csim images
1178
    function GetURLArguments() {
1179
 
1180
	// This is a JPGRAPH internal defined that prevents
1181
	// us from recursively coming here again
1182
	$urlarg = _CSIM_DISPLAY.'=1';
1183
 
1184
	// Now reconstruct any user URL argument
1185
	reset($_GET);
1186
	while( list($key,$value) = each($_GET) ) {
1187
	    if( is_array($value) ) {
1188
		foreach ( $value as $k => $v ) {
1189
		    $urlarg .= '&amp;'.$key.'%5B'.$k.'%5D='.urlencode($v);
1190
		}
1191
	    }
1192
	    else {
1193
		$urlarg .= '&amp;'.$key.'='.urlencode($value);
1194
	    }
1195
	}
1196
 
1197
	// It's not ideal to convert POST argument to GET arguments
1198
	// but there is little else we can do. One idea for the
1199
	// future might be recreate the POST header in case.
1200
	reset($_POST);
1201
	while( list($key,$value) = each($_POST) ) {
1202
	    if( is_array($value) ) {
1203
		foreach ( $value as $k => $v ) {
1204
		    $urlarg .= '&amp;'.$key.'%5B'.$k.'%5D='.urlencode($v);
1205
		}
1206
	    }
1207
	    else {
1208
		$urlarg .= '&amp;'.$key.'='.urlencode($value);
1209
	    }
1210
	}
1211
 
1212
	return $urlarg;
1213
    }
1214
 
1215
    function SetCSIMImgAlt($aAlt) {
1216
	$this->iCSIMImgAlt = $aAlt;
1217
    }
1218
 
1219
    function StrokeCSIM($aScriptName='auto',$aCSIMName='',$aBorder=0) {
1220
	if( $aCSIMName=='' ) {
1221
	    // create a random map name
1222
	    srand ((double) microtime() * 1000000);
1223
	    $r = rand(0,100000);
1224
	    $aCSIMName='__mapname'.$r.'__';
1225
	}
1226
 
1227
	if( $aScriptName=='auto' )
1228
	    $aScriptName=basename($_SERVER['PHP_SELF']);
1229
 
1230
	$urlarg = $this->GetURLArguments();
1231
 
1232
	if( empty($_GET[_CSIM_DISPLAY]) ) {
1233
	    // First determine if we need to check for a cached version
1234
	    // This differs from the standard cache in the sense that the
1235
	    // image and CSIM map HTML file is written relative to the directory
1236
	    // the script executes in and not the specified cache directory.
1237
	    // The reason for this is that the cache directory is not necessarily
1238
	    // accessible from the HTTP server.
1239
	    if( $this->csimcachename != '' ) {
1240
		$dir = dirname($this->csimcachename);
1241
		$base = basename($this->csimcachename);
1242
		$base = strtok($base,'.');
1243
		$suffix = strtok('.');
1244
		$basecsim = $dir.'/'.$base.'?'.$urlarg.'_csim_.html';
1245
		$baseimg = $base.'?'.$urlarg.'.'.$this->img->img_format;
1246
 
1247
		// Check that apache can write to directory specified
1248
 
1249
		if( file_exists($dir) && !is_writeable($dir) ) {
1250
		    JpgraphError::RaiseL(25028,$dir);//('Apache/PHP does not have permission to write to the CSIM cache directory ('.$dir.'). Check permissions.');
1251
		}
1252
 
1253
		// Make sure directory exists
1254
		$this->cache->MakeDirs($dir);
1255
 
1256
		// Write the image file
1257
		$this->Stroke(CSIMCACHE_DIR.$baseimg);
1258
 
1259
		// Construct wrapper HTML and write to file and send it back to browser
1260
 
1261
		// In the src URL we must replace the '?' with its encoding to prevent the arguments
1262
		// to be converted to real arguments.
1263
		$tmp = str_replace('?','%3f',$baseimg);
1264
		$htmlwrap = $this->GetHTMLImageMap($aCSIMName)."\n".
1265
		    '<img src="'.CSIMCACHE_HTTP_DIR.$tmp.'" ismap="ismap" usemap="#'.$aCSIMName.'" border="'.$aBorder.'" width="'.$this->img->width.'" height="'.$this->img->height."\" alt=\"".$this->iCSIMImgAlt."\" />\n";
1266
 
1267
		if($fh =  @fopen($basecsim,'w') ) {
1268
		    fwrite($fh,$htmlwrap);
1269
		    fclose($fh);
1270
		    echo $htmlwrap;
1271
		}
1272
		else
1273
		    JpGraphError::RaiseL(25029,$basecsim);//(" Can't write CSIM \"$basecsim\" for writing. Check free space and permissions.");
1274
	    }
1275
	    else {
1276
 
1277
		if( $aScriptName=='' ) {
1278
		    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().');
1279
		}
1280
		echo $this->GetHTMLImageMap($aCSIMName);
1281
		echo "<img src=\"".$aScriptName.'?'.$urlarg."\" ismap=\"ismap\" usemap=\"#".$aCSIMName.'" border="'.$aBorder.'" width="'.$this->img->width.'" height="'.$this->img->height."\" alt=\"".$this->iCSIMImgAlt."\" />\n";
1282
	    }
1283
	}
1284
	else {
1285
	    $this->Stroke();
1286
	}
1287
    }
1288
 
1289
    function GetTextsYMinMax($aY2=false) {
1290
	if( $aY2 )
1291
	    $txts = $this->y2texts;
1292
	else
1293
	    $txts = $this->texts;
1294
	$n = count($txts);
1295
	$min=null;
1296
	$max=null;
1297
	for( $i=0; $i < $n; ++$i ) {
1298
	    if( $txts[$i]->iScalePosY !== null &&
1299
		$txts[$i]->iScalePosX !== null  ) {
1300
		if( $min === null  ) {
1301
		    $min = $max = $txts[$i]->iScalePosY ;
1302
		}
1303
		else {
1304
		    $min = min($min,$txts[$i]->iScalePosY);
1305
		    $max = max($max,$txts[$i]->iScalePosY);
1306
		}
1307
	    }
1308
	}
1309
	if( $min !== null ) {
1310
	    return array($min,$max);
1311
	}
1312
	else
1313
	    return null;
1314
    }
1315
 
1316
    function GetTextsXMinMax($aY2=false) {
1317
	if( $aY2 )
1318
	    $txts = $this->y2texts;
1319
	else
1320
	    $txts = $this->texts;
1321
	$n = count($txts);
1322
	$min=null;
1323
	$max=null;
1324
	for( $i=0; $i < $n; ++$i ) {
1325
	    if( $txts[$i]->iScalePosY !== null &&
1326
		$txts[$i]->iScalePosX !== null  ) {
1327
		if( $min === null  ) {
1328
		    $min = $max = $txts[$i]->iScalePosX ;
1329
		}
1330
		else {
1331
		    $min = min($min,$txts[$i]->iScalePosX);
1332
		    $max = max($max,$txts[$i]->iScalePosX);
1333
		}
1334
	    }
1335
	}
1336
	if( $min !== null ) {
1337
	    return array($min,$max);
1338
	}
1339
	else
1340
	    return null;
1341
    }
1342
 
1343
    function GetXMinMax() {
1344
	list($min,$ymin) = $this->plots[0]->Min();
1345
	list($max,$ymax) = $this->plots[0]->Max();
1346
	foreach( $this->plots as $p ) {
1347
	    list($xmin,$ymin) = $p->Min();
1348
	    list($xmax,$ymax) = $p->Max();
1349
	    $min = Min($xmin,$min);
1350
	    $max = Max($xmax,$max);
1351
	}
1352
 
1353
	if( $this->y2axis != null ) {
1354
	    foreach( $this->y2plots as $p ) {
1355
		list($xmin,$ymin) = $p->Min();
1356
		list($xmax,$ymax) = $p->Max();
1357
		$min = Min($xmin,$min);
1358
		$max = Max($xmax,$max);
1359
	    }
1360
	}
1361
 
1362
	$n = count($this->ynaxis);
1363
	for( $i=0; $i < $n; ++$i ) {
1364
	    if( $this->ynaxis[$i] != null) {
1365
		foreach( $this->ynplots[$i] as $p ) {
1366
		    list($xmin,$ymin) = $p->Min();
1367
		    list($xmax,$ymax) = $p->Max();
1368
		    $min = Min($xmin,$min);
1369
		    $max = Max($xmax,$max);
1370
		}
1371
	    }
1372
	}
1373
 
1374
	return array($min,$max);
1375
    }
1376
 
1377
    function AdjustMarginsForTitles() {
1378
	$totrequired =
1379
	    ($this->title->t != '' ?
1380
	     $this->title->GetTextHeight($this->img) + $this->title->margin + 5 : 0 ) +
1381
	    ($this->subtitle->t != '' ?
1382
	     $this->subtitle->GetTextHeight($this->img) + $this->subtitle->margin + 5 : 0 ) +
1383
	    ($this->subsubtitle->t != '' ?
1384
	     $this->subsubtitle->GetTextHeight($this->img) + $this->subsubtitle->margin + 5 : 0 ) ;
1385
 
1386
 
1387
	$btotrequired = 0;
1388
	if($this->xaxis != null &&  !$this->xaxis->hide && !$this->xaxis->hide_labels ) {
1389
	    // Minimum bottom margin
1390
	    if( $this->xaxis->title->t != '' ) {
1391
		if( $this->img->a == 90 )
1392
		    $btotrequired = $this->yaxis->title->GetTextHeight($this->img) + 5 ;
1393
		else
1394
		    $btotrequired = $this->xaxis->title->GetTextHeight($this->img) + 5 ;
1395
	    }
1396
	    else
1397
		$btotrequired = 0;
1398
 
1399
	    if( $this->img->a == 90 ) {
1400
		$this->img->SetFont($this->yaxis->font_family,$this->yaxis->font_style,
1401
				    $this->yaxis->font_size);
1402
		$lh = $this->img->GetTextHeight('Mg',$this->yaxis->label_angle);
1403
	    }
1404
	    else {
1405
		$this->img->SetFont($this->xaxis->font_family,$this->xaxis->font_style,
1406
				    $this->xaxis->font_size);
1407
		$lh = $this->img->GetTextHeight('Mg',$this->xaxis->label_angle);
1408
	    }
1409
 
1410
	    $btotrequired += $lh + 5;
1411
	}
1412
 
1413
	if( $this->img->a == 90 ) {
1414
	    // DO Nothing. It gets too messy to do this properly for 90 deg...
1415
	}
1416
	else{
1417
	    if( $this->img->top_margin < $totrequired ) {
1418
		$this->SetMargin($this->img->left_margin,$this->img->right_margin,
1419
				 $totrequired,$this->img->bottom_margin);
1420
	    }
1421
	    if( $this->img->bottom_margin < $btotrequired ) {
1422
		$this->SetMargin($this->img->left_margin,$this->img->right_margin,
1423
				 $this->img->top_margin,$btotrequired);
1424
	    }
1425
	}
1426
    }
1427
 
1428
    // Stroke the graph
1429
    // $aStrokeFileName	If != "" the image will be written to this file and NOT
1430
    // streamed back to the browser
1431
    function Stroke($aStrokeFileName="") {
1432
 
1433
	// Fist make a sanity check that user has specified a scale
1434
	if( empty($this->yscale) ) {
1435
	    JpGraphError::RaiseL(25031);//('You must specify what scale to use with a call to Graph::SetScale().');
1436
	}
1437
 
1438
	// Start by adjusting the margin so that potential titles will fit.
1439
	$this->AdjustMarginsForTitles();
1440
 
1441
	// Setup scale constants
1442
	if( $this->yscale ) $this->yscale->InitConstants($this->img);
1443
	if( $this->xscale ) $this->xscale->InitConstants($this->img);
1444
	if( $this->y2scale ) $this->y2scale->InitConstants($this->img);
1445
 
1446
	$n=count($this->ynscale);
1447
	for($i=0; $i < $n; ++$i) {
1448
	  if( $this->ynscale[$i] ) $this->ynscale[$i]->InitConstants($this->img);
1449
	}
1450
 
1451
	// If the filename is the predefined value = '_csim_special_'
1452
	// we assume that the call to stroke only needs to do enough
1453
	// to correctly generate the CSIM maps.
1454
	// We use this variable to skip things we don't strictly need
1455
	// to do to generate the image map to improve performance
1456
	// a best we can. Therefor you will see a lot of tests !$_csim in the
1457
	// code below.
1458
	$_csim = ($aStrokeFileName===_CSIM_SPECIALFILE);
1459
 
1460
	// We need to know if we have stroked the plot in the
1461
	// GetCSIMareas. Otherwise the CSIM hasn't been generated
1462
	// and in the case of GetCSIM called before stroke to generate
1463
	// CSIM without storing an image to disk GetCSIM must call Stroke.
1464
	$this->iHasStroked = true;
1465
 
1466
	// Do any pre-stroke adjustment that is needed by the different plot types
1467
	// (i.e bar plots want's to add an offset to the x-labels etc)
1468
	for($i=0; $i < count($this->plots) ; ++$i ) {
1469
	    $this->plots[$i]->PreStrokeAdjust($this);
1470
	    $this->plots[$i]->DoLegend($this);
1471
	}
1472
 
1473
	// Any plots on the second Y scale?
1474
	if( $this->y2scale != null ) {
1475
	    for($i=0; $i<count($this->y2plots)	; ++$i ) {
1476
		$this->y2plots[$i]->PreStrokeAdjust($this);
1477
		$this->y2plots[$i]->DoLegend($this);
1478
	    }
1479
	}
1480
 
1481
	// Any plots on the extra Y axises?
1482
	$n = count($this->ynaxis);
1483
	for($i=0; $i<$n	; ++$i ) {
1484
	    if( $this->ynplots == null || $this->ynplots[$i] == null) {
1485
		JpGraphError::RaiseL(25032,$i);//("No plots for Y-axis nbr:$i");
1486
	    }
1487
	    $m = count($this->ynplots[$i]);
1488
	    for($j=0; $j < $m; ++$j ) {
1489
		$this->ynplots[$i][$j]->PreStrokeAdjust($this);
1490
		$this->ynplots[$i][$j]->DoLegend($this);
1491
	    }
1492
	}
1493
 
1494
 
1495
	// Bail out if any of the Y-axis not been specified and
1496
	// has no plots. (This means it is impossible to do autoscaling and
1497
	// no other scale was given so we can't possible draw anything). If you use manual
1498
	// scaling you also have to supply the tick steps as well.
1499
	if( (!$this->yscale->IsSpecified() && count($this->plots)==0) ||
1500
	    ($this->y2scale!=null && !$this->y2scale->IsSpecified() && count($this->y2plots)==0) ) {
1501
	    //$e = "n=".count($this->y2plots)."\n";
1502
	    // $e = "Can't draw unspecified Y-scale.<br>\nYou have either:<br>\n";
1503
	    // $e .= "1. Specified an Y axis for autoscaling but have not supplied any plots<br>\n";
1504
	    // $e .= "2. Specified a scale manually but have forgot to specify the tick steps";
1505
	    JpGraphError::RaiseL(25026);
1506
	}
1507
 
1508
	// Bail out if no plots and no specified X-scale
1509
	if( (!$this->xscale->IsSpecified() && count($this->plots)==0 && count($this->y2plots)==0) )
1510
	    JpGraphError::RaiseL(25034);//("<strong>JpGraph: Can't draw unspecified X-scale.</strong><br>No plots.<br>");
1511
 
1512
	//Check if we should autoscale y-axis
1513
	if( !$this->yscale->IsSpecified() && count($this->plots)>0 ) {
1514
	    list($min,$max) = $this->GetPlotsYMinMax($this->plots);
1515
 	    $lres = $this->GetLinesYMinMax($this->lines);
1516
	    if( is_array($lres) ) {
1517
		list($linmin,$linmax) = $lres ;
1518
		$min = min($min,$linmin);
1519
		$max = max($max,$linmax);
1520
	    }
1521
	    $tres = $this->GetTextsYMinMax();
1522
	    if( is_array($tres) ) {
1523
		list($tmin,$tmax) = $tres ;
1524
		$min = min($min,$tmin);
1525
		$max = max($max,$tmax);
1526
	    }
1527
	    $this->yscale->AutoScale($this->img,$min,$max,
1528
				     $this->img->plotheight/$this->ytick_factor);
1529
	}
1530
	elseif( $this->yscale->IsSpecified() &&
1531
		( $this->yscale->auto_ticks || !$this->yscale->ticks->IsSpecified()) ) {
1532
	    // The tick calculation will use the user suplied min/max values to determine
1533
	    // the ticks. If auto_ticks is false the exact user specifed min and max
1534
	    // values will be used for the scale.
1535
	    // If auto_ticks is true then the scale might be slightly adjusted
1536
	    // so that the min and max values falls on an even major step.
1537
	    $min = $this->yscale->scale[0];
1538
	    $max = $this->yscale->scale[1];
1539
	    $this->yscale->AutoScale($this->img,$min,$max,
1540
				     $this->img->plotheight/$this->ytick_factor,
1541
				     $this->yscale->auto_ticks);
1542
	}
1543
 
1544
	if( $this->y2scale != null) {
1545
 
1546
	    if( !$this->y2scale->IsSpecified() && count($this->y2plots)>0 ) {
1547
		list($min,$max) = $this->GetPlotsYMinMax($this->y2plots);
1548
		$lres = $this->GetLinesYMinMax($this->y2lines);
1549
		if( is_array($lres) ) {
1550
		    list($linmin,$linmax) = $lres ;
1551
		    $min = min($min,$linmin);
1552
		    $max = max($max,$linmax);
1553
		}
1554
		$tres = $this->GetTextsYMinMax(true);
1555
		if( is_array($tres) ) {
1556
		    list($tmin,$tmax) = $tres ;
1557
		    $min = min($min,$tmin);
1558
		    $max = max($max,$tmax);
1559
		}
1560
		$this->y2scale->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor);
1561
	    }
1562
	    elseif( $this->y2scale->IsSpecified() &&
1563
		    ( $this->y2scale->auto_ticks || !$this->y2scale->ticks->IsSpecified()) ) {
1564
		// The tick calculation will use the user suplied min/max values to determine
1565
		// the ticks. If auto_ticks is false the exact user specifed min and max
1566
		// values will be used for the scale.
1567
		// If auto_ticks is true then the scale might be slightly adjusted
1568
		// so that the min and max values falls on an even major step.
1569
		$min = $this->y2scale->scale[0];
1570
		$max = $this->y2scale->scale[1];
1571
		$this->y2scale->AutoScale($this->img,$min,$max,
1572
					  $this->img->plotheight/$this->ytick_factor,
1573
					  $this->y2scale->auto_ticks);
1574
	    }
1575
	}
1576
 
1577
	//
1578
	// Autoscale the multiple Y-axis
1579
	//
1580
	$n = count($this->ynaxis);
1581
	for( $i=0; $i < $n; ++$i ) {
1582
	  if( $this->ynscale[$i] != null) {
1583
	    if( !$this->ynscale[$i]->IsSpecified() && count($this->ynplots[$i])>0 ) {
1584
	      list($min,$max) = $this->GetPlotsYMinMax($this->ynplots[$i]);
1585
	      $this->ynscale[$i]->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor);
1586
	    }
1587
	    elseif( $this->ynscale[$i]->IsSpecified() &&
1588
		    ( $this->ynscale[$i]->auto_ticks || !$this->ynscale[$i]->ticks->IsSpecified()) ) {
1589
		// The tick calculation will use the user suplied min/max values to determine
1590
		// the ticks. If auto_ticks is false the exact user specifed min and max
1591
		// values will be used for the scale.
1592
		// If auto_ticks is true then the scale might be slightly adjusted
1593
		// so that the min and max values falls on an even major step.
1594
	      $min = $this->ynscale[$i]->scale[0];
1595
	      $max = $this->ynscale[$i]->scale[1];
1596
	      $this->ynscale[$i]->AutoScale($this->img,$min,$max,
1597
					    $this->img->plotheight/$this->ytick_factor,
1598
					    $this->ynscale[$i]->auto_ticks);
1599
	    }
1600
	  }
1601
	}
1602
 
1603
 
1604
	//Check if we should autoscale x-axis
1605
	if( !$this->xscale->IsSpecified() ) {
1606
	    if( substr($this->axtype,0,4) == "text" ) {
1607
		$max=0;
1608
		$n = count($this->plots);
1609
		for($i=0; $i < $n; ++$i ) {
1610
		    $p = $this->plots[$i];
1611
		    // We need some unfortunate sub class knowledge here in order
1612
		    // to increase number of data points in case it is a line plot
1613
		    // which has the barcenter set. If not it could mean that the
1614
		    // last point of the data is outside the scale since the barcenter
1615
		    // settings means that we will shift the entire plot half a tick step
1616
		    // to the right in oder to align with the center of the bars.
1617
		    if( is_a($p,'BarPlot') || empty($p->barcenter)) {
1618
			$max=max($max,$p->numpoints-1);
1619
		    }
1620
		    else {
1621
			$max=max($max,$p->numpoints);
1622
		    }
1623
		}
1624
		$min=0;
1625
		if( $this->y2axis != null ) {
1626
		    foreach( $this->y2plots as $p ) {
1627
			$max=max($max,$p->numpoints-1);
1628
		    }
1629
		}
1630
		$n = count($this->ynaxis);
1631
		for( $i=0; $i < $n; ++$i ) {
1632
		    if( $this->ynaxis[$i] != null) {
1633
			foreach( $this->ynplots[$i] as $p ) {
1634
			    $max=max($max,$p->numpoints-1);
1635
			}
1636
		    }
1637
		}
1638
 
1639
		$this->xscale->Update($this->img,$min,$max);
1640
		$this->xscale->ticks->Set($this->xaxis->tick_step,1);
1641
		$this->xscale->ticks->SupressMinorTickMarks();
1642
	    }
1643
	    else {
1644
		list($min,$max) = $this->GetXMinMax();
1645
 
1646
		$lres = $this->GetLinesXMinMax($this->lines);
1647
		if( $lres ) {
1648
		    list($linmin,$linmax) = $lres ;
1649
		    $min = min($min,$linmin);
1650
		    $max = max($max,$linmax);
1651
		}
1652
		$lres = $this->GetLinesXMinMax($this->y2lines);
1653
		if( $lres ) {
1654
		    list($linmin,$linmax) = $lres ;
1655
		    $min = min($min,$linmin);
1656
		    $max = max($max,$linmax);
1657
		}
1658
 
1659
		$tres = $this->GetTextsXMinMax();
1660
		if( $tres ) {
1661
		    list($tmin,$tmax) = $tres ;
1662
		    $min = min($min,$tmin);
1663
		    $max = max($max,$tmax);
1664
		}
1665
 
1666
		$tres = $this->GetTextsXMinMax(true);
1667
		if( $tres ) {
1668
		    list($tmin,$tmax) = $tres ;
1669
		    $min = min($min,$tmin);
1670
		    $max = max($max,$tmax);
1671
		}
1672
 
1673
		$this->xscale->AutoScale($this->img,$min,$max,round($this->img->plotwidth/$this->xtick_factor));
1674
	    }
1675
 
1676
	    //Adjust position of y-axis and y2-axis to minimum/maximum of x-scale
1677
	    if( !is_numeric($this->yaxis->pos) && !is_string($this->yaxis->pos) )
1678
	    	$this->yaxis->SetPos($this->xscale->GetMinVal());
1679
	    if( $this->y2axis != null ) {
1680
		if( !is_numeric($this->y2axis->pos) && !is_string($this->y2axis->pos) )
1681
		    $this->y2axis->SetPos($this->xscale->GetMaxVal());
1682
		$this->y2axis->SetTitleSide(SIDE_RIGHT);
1683
	    }
1684
 
1685
	    $n = count($this->ynaxis);
1686
	    $nY2adj = $this->y2axis != null ? $this->iYAxisDeltaPos : 0;
1687
	    for( $i=0; $i < $n; ++$i ) {
1688
		if( $this->ynaxis[$i] != null ) {
1689
		    if( !is_numeric($this->ynaxis[$i]->pos) && !is_string($this->ynaxis[$i]->pos) ) {
1690
			$this->ynaxis[$i]->SetPos($this->xscale->GetMaxVal());
1691
		  $this->ynaxis[$i]->SetPosAbsDelta($i*$this->iYAxisDeltaPos + $nY2adj);
1692
		    }
1693
		    $this->ynaxis[$i]->SetTitleSide(SIDE_RIGHT);
1694
		}
1695
	    }
1696
	}
1697
	elseif( $this->xscale->IsSpecified() &&
1698
		( $this->xscale->auto_ticks || !$this->xscale->ticks->IsSpecified()) ) {
1699
	    // The tick calculation will use the user suplied min/max values to determine
1700
	    // the ticks. If auto_ticks is false the exact user specifed min and max
1701
	    // values will be used for the scale.
1702
	    // If auto_ticks is true then the scale might be slightly adjusted
1703
	    // so that the min and max values falls on an even major step.
1704
	    $min = $this->xscale->scale[0];
1705
	    $max = $this->xscale->scale[1];
1706
 
1707
 
1708
	    $this->xscale->AutoScale($this->img,$min,$max,
1709
				     $this->img->plotwidth/$this->xtick_factor,
1710
				     false);
1711
 
1712
	    if( $this->y2axis != null ) {
1713
		if( !is_numeric($this->y2axis->pos) && !is_string($this->y2axis->pos) )
1714
		    $this->y2axis->SetPos($this->xscale->GetMaxVal());
1715
		$this->y2axis->SetTitleSide(SIDE_RIGHT);
1716
	    }
1717
 
1718
	}
1719
 
1720
	// If we have a negative values and x-axis position is at 0
1721
	// we need to supress the first and possible the last tick since
1722
	// they will be drawn on top of the y-axis (and possible y2 axis)
1723
	// The test below might seem strange the reasone being that if
1724
	// the user hasn't specified a value for position this will not
1725
	// be set until we do the stroke for the axis so as of now it
1726
	// is undefined.
1727
	// For X-text scale we ignore all this since the tick are usually
1728
	// much further in and not close to the Y-axis. Hence the test
1729
	// for 'text'
1730
 
1731
	if( ($this->yaxis->pos==$this->xscale->GetMinVal() ||
1732
	     (is_string($this->yaxis->pos) && $this->yaxis->pos=='min')) &&
1733
	    !is_numeric($this->xaxis->pos) && $this->yscale->GetMinVal() < 0 &&
1734
	    substr($this->axtype,0,4) != 'text' && $this->xaxis->pos!="min" ) {
1735
 
1736
	    //$this->yscale->ticks->SupressZeroLabel(false);
1737
	    $this->xscale->ticks->SupressFirst();
1738
	    if( $this->y2axis != null ) {
1739
		$this->xscale->ticks->SupressLast();
1740
	    }
1741
	}
1742
	elseif( !is_numeric($this->yaxis->pos) && $this->yaxis->pos=='max' ) {
1743
	    $this->xscale->ticks->SupressLast();
1744
	}
1745
 
1746
 
1747
	if( !$_csim ) {
1748
	    $this->StrokePlotArea();
1749
	    if( $this->iIconDepth == DEPTH_BACK ) {
1750
		$this->StrokeIcons();
1751
	    }
1752
	}
1753
	$this->StrokeAxis(false);
1754
 
1755
	// Stroke bands
1756
	if( $this->bands != null && !$_csim)
1757
	    for($i=0; $i < count($this->bands); ++$i) {
1758
		// Stroke all bands that asks to be in the background
1759
		if( $this->bands[$i]->depth == DEPTH_BACK )
1760
		    $this->bands[$i]->Stroke($this->img,$this->xscale,$this->yscale);
1761
	    }
1762
 
1763
	if( $this->y2bands != null && $this->y2scale != null && !$_csim )
1764
	    for($i=0; $i < count($this->y2bands); ++$i) {
1765
		// Stroke all bands that asks to be in the foreground
1766
		if( $this->y2bands[$i]->depth == DEPTH_BACK )
1767
		    $this->y2bands[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
1768
	    }
1769
 
1770
 
1771
	if( $this->grid_depth == DEPTH_BACK && !$_csim) {
1772
	    $this->ygrid->Stroke();
1773
	    $this->xgrid->Stroke();
1774
	}
1775
 
1776
	// Stroke Y2-axis
1777
	if( $this->y2axis != null && !$_csim) {
1778
	    $this->y2axis->Stroke($this->xscale);
1779
	    $this->y2grid->Stroke();
1780
	}
1781
 
1782
	// Stroke yn-axis
1783
	$n = count($this->ynaxis);
1784
	for( $i=0; $i < $n; ++$i ) {
1785
	    $this->ynaxis[$i]->Stroke($this->xscale);
1786
	}
1787
 
1788
	$oldoff=$this->xscale->off;
1789
	if(substr($this->axtype,0,4)=="text") {
1790
	    if( $this->text_scale_abscenteroff > -1 ) {
1791
		// For a text scale the scale factor is the number of pixel per step.
1792
		// Hence we can use the scale factor as a substitute for number of pixels
1793
		// per major scale step and use that in order to adjust the offset so that
1794
		// an object of width "abscenteroff" becomes centered.
1795
		$this->xscale->off += round($this->xscale->scale_factor/2)-round($this->text_scale_abscenteroff/2);
1796
	    }
1797
	    else {
1798
		$this->xscale->off +=
1799
		    ceil($this->xscale->scale_factor*$this->text_scale_off*$this->xscale->ticks->minor_step);
1800
	    }
1801
	}
1802
 
1803
	if( $this->iDoClipping ) {
1804
	    $oldimage = $this->img->CloneCanvasH();
1805
	}
1806
 
1807
	if( ! $this->y2orderback ) {
1808
	    // Stroke all plots for Y axis
1809
	    for($i=0; $i < count($this->plots); ++$i) {
1810
		$this->plots[$i]->Stroke($this->img,$this->xscale,$this->yscale);
1811
		$this->plots[$i]->StrokeMargin($this->img);
1812
	    }
1813
	}
1814
 
1815
	// Stroke all plots for Y2 axis
1816
	if( $this->y2scale != null )
1817
	    for($i=0; $i< count($this->y2plots); ++$i ) {
1818
		$this->y2plots[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
1819
	    }
1820
 
1821
	if( $this->y2orderback ) {
1822
	    // Stroke all plots for Y1 axis
1823
	    for($i=0; $i < count($this->plots); ++$i) {
1824
		$this->plots[$i]->Stroke($this->img,$this->xscale,$this->yscale);
1825
		$this->plots[$i]->StrokeMargin($this->img);
1826
	    }
1827
	}
1828
 
1829
	$n = count($this->ynaxis);
1830
	for( $i=0; $i < $n; ++$i ) {
1831
	    $m = count($this->ynplots[$i]);
1832
	    for( $j=0; $j < $m; ++$j ) {
1833
		$this->ynplots[$i][$j]->Stroke($this->img,$this->xscale,$this->ynscale[$i]);
1834
		$this->ynplots[$i][$j]->StrokeMargin($this->img);
1835
	    }
1836
	}
1837
 
1838
	if( $this->iIconDepth == DEPTH_FRONT) {
1839
	    $this->StrokeIcons();
1840
	}
1841
 
1842
	if( $this->iDoClipping ) {
1843
	    // Clipping only supports graphs at 0 and 90 degrees
1844
	    if( $this->img->a == 0 ) {
1845
		$this->img->CopyCanvasH($oldimage,$this->img->img,
1846
					$this->img->left_margin,$this->img->top_margin,
1847
					$this->img->left_margin,$this->img->top_margin,
1848
					$this->img->plotwidth+1,$this->img->plotheight);
1849
	    }
1850
	    elseif( $this->img->a == 90 ) {
1851
		$adj = ($this->img->height - $this->img->width)/2;
1852
		$this->img->CopyCanvasH($oldimage,$this->img->img,
1853
					$this->img->bottom_margin-$adj,$this->img->left_margin+$adj,
1854
					$this->img->bottom_margin-$adj,$this->img->left_margin+$adj,
1855
					$this->img->plotheight+1,$this->img->plotwidth);
1856
	    }
1857
	    else {
1858
		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.');
1859
	    }
1860
	    $this->img->Destroy();
1861
	    $this->img->SetCanvasH($oldimage);
1862
	}
1863
 
1864
	$this->xscale->off=$oldoff;
1865
 
1866
	if( $this->grid_depth == DEPTH_FRONT && !$_csim ) {
1867
	    $this->ygrid->Stroke();
1868
	    $this->xgrid->Stroke();
1869
	}
1870
 
1871
	// Stroke bands
1872
	if( $this->bands!= null )
1873
	    for($i=0; $i < count($this->bands); ++$i) {
1874
		// Stroke all bands that asks to be in the foreground
1875
		if( $this->bands[$i]->depth == DEPTH_FRONT )
1876
		    $this->bands[$i]->Stroke($this->img,$this->xscale,$this->yscale);
1877
	    }
1878
 
1879
	if( $this->y2bands!= null && $this->y2scale != null )
1880
	    for($i=0; $i < count($this->y2bands); ++$i) {
1881
		// Stroke all bands that asks to be in the foreground
1882
		if( $this->y2bands[$i]->depth == DEPTH_FRONT )
1883
		    $this->y2bands[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
1884
	    }
1885
 
1886
 
1887
	// Stroke any lines added
1888
	if( $this->lines != null ) {
1889
	    for($i=0; $i < count($this->lines); ++$i) {
1890
		$this->lines[$i]->Stroke($this->img,$this->xscale,$this->yscale);
1891
		$this->lines[$i]->DoLegend($this);
1892
	    }
1893
	}
1894
 
1895
	if( $this->y2lines != null && $this->y2scale != null ) {
1896
	    for($i=0; $i < count($this->y2lines); ++$i) {
1897
		$this->y2lines[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
1898
		$this->y2lines[$i]->DoLegend($this);
1899
	    }
1900
	}
1901
 
1902
	// Finally draw the axis again since some plots may have nagged
1903
	// the axis in the edges.However we do no stroke the labels again
1904
	// since any user defined callback would be called twice. It also
1905
	// enhances performance.
1906
 
1907
	if( !$_csim ) {
1908
	    $this->StrokeAxis();
1909
	}
1910
 
1911
	if( $this->y2scale != null && !$_csim )
1912
	    $this->y2axis->Stroke($this->xscale,false);
1913
 
1914
	if( !$_csim ) {
1915
	    $this->StrokePlotBox();
1916
	}
1917
 
1918
	// The titles and legends never gets rotated so make sure
1919
	// that the angle is 0 before stroking them
1920
	$aa = $this->img->SetAngle(0);
1921
	$this->StrokeTitles();
1922
	$this->footer->Stroke($this->img);
1923
	$this->legend->Stroke($this->img);
1924
	$this->img->SetAngle($aa);
1925
	$this->StrokeTexts();
1926
	$this->StrokeTables();
1927
 
1928
	if( !$_csim ) {
1929
 
1930
	    $this->img->SetAngle($aa);
1931
 
1932
	    // Draw an outline around the image map
1933
	    if(_JPG_DEBUG) {
1934
		$this->DisplayClientSideaImageMapAreas();
1935
	    }
1936
 
1937
	    // Should we do any final image transformation
1938
	    if( $this->iImgTrans ) {
1939
		if( !class_exists('ImgTrans') ) {
1940
		    require_once('jpgraph_imgtrans.php');
1941
		    //JpGraphError::Raise('In order to use image transformation you must include the file jpgraph_imgtrans.php in your script.');
1942
		}
1943
 
1944
		$tform = new ImgTrans($this->img->img);
1945
		$this->img->img = $tform->Skew3D($this->iImgTransHorizon,$this->iImgTransSkewDist,
1946
						 $this->iImgTransDirection,$this->iImgTransHighQ,
1947
						 $this->iImgTransMinSize,$this->iImgTransFillColor,
1948
						 $this->iImgTransBorder);
1949
	    }
1950
 
1951
	    // If the filename is given as the special "__handle"
1952
	    // then the image handler is returned and the image is NOT
1953
	    // streamed back
1954
	    if( $aStrokeFileName == _IMG_HANDLER ) {
1955
		return $this->img->img;
1956
	    }
1957
	    else {
1958
		// Finally stream the generated picture
1959
		$this->cache->PutAndStream($this->img,$this->cache_name,$this->inline,$aStrokeFileName);
1960
	    }
1961
	}
1962
    }
1963
 
1964
    function SetAxisLabelBackground($aType,$aXFColor='lightgray',$aXColor='black',$aYFColor='lightgray',$aYColor='black') {
1965
	$this->iAxisLblBgType = $aType;
1966
	$this->iXAxisLblBgFillColor = $aXFColor;
1967
	$this->iXAxisLblBgColor = $aXColor;
1968
	$this->iYAxisLblBgFillColor = $aYFColor;
1969
	$this->iYAxisLblBgColor = $aYColor;
1970
    }
1971
 
1972
//---------------
1973
// PRIVATE METHODS
1974
 
1975
    function StrokeAxisLabelBackground() {
1976
	// Types
1977
	// 0 = No background
1978
	// 1 = Only X-labels, length of axis
1979
	// 2 = Only Y-labels, length of axis
1980
	// 3 = As 1 but extends to width of graph
1981
	// 4 = As 2 but extends to height of graph
1982
	// 5 = Combination of 3 & 4
1983
	// 6 = Combination of 1 & 2
1984
 
1985
	$t = $this->iAxisLblBgType ;
1986
	if( $t < 1 ) return;
1987
	// Stroke optional X-axis label background color
1988
	if( $t == 1 || $t == 3 || $t == 5 || $t == 6 ) {
1989
	    $this->img->PushColor($this->iXAxisLblBgFillColor);
1990
	    if( $t == 1 || $t == 6 ) {
1991
		$xl = $this->img->left_margin;
1992
		$yu = $this->img->height - $this->img->bottom_margin + 1;
1993
		$xr = $this->img->width - $this->img->right_margin ;
1994
		$yl = $this->img->height-1-$this->frame_weight;
1995
	    }
1996
	    else { // t==3 || t==5
1997
		$xl = $this->frame_weight;
1998
		$yu = $this->img->height - $this->img->bottom_margin + 1;
1999
		$xr = $this->img->width - 1 - $this->frame_weight;
2000
		$yl = $this->img->height-1-$this->frame_weight;
2001
	    }
2002
 
2003
	    $this->img->FilledRectangle($xl,$yu,$xr,$yl);
2004
	    $this->img->PopColor();
2005
 
2006
	    // Check if we should add the vertical lines at left and right edge
2007
	    if( $this->iXAxisLblBgColor !== '' ) {
2008
		$this->img->PushColor($this->iXAxisLblBgColor);
2009
		if( $t == 1 || $t == 6 ) {
2010
		    $this->img->Line($xl,$yu,$xl,$yl);
2011
		    $this->img->Line($xr,$yu,$xr,$yl);
2012
		}
2013
		else {
2014
		    $xl = $this->img->width - $this->img->right_margin ;
2015
		    $this->img->Line($xl,$yu-1,$xr,$yu-1);
2016
		}
2017
		$this->img->PopColor();
2018
	    }
2019
	}
2020
 
2021
	if( $t == 2 || $t == 4 || $t == 5 || $t == 6 ) {
2022
	    $this->img->PushColor($this->iYAxisLblBgFillColor);
2023
	    if( $t == 2 || $t == 6 ) {
2024
		$xl = $this->frame_weight;
2025
		$yu = $this->frame_weight+$this->img->top_margin;
2026
		$xr = $this->img->left_margin - 1;
2027
		$yl = $this->img->height - $this->img->bottom_margin + 1;
2028
	    }
2029
	    else {
2030
		$xl = $this->frame_weight;
2031
		$yu = $this->frame_weight;
2032
		$xr = $this->img->left_margin - 1;
2033
		$yl = $this->img->height-1-$this->frame_weight;
2034
	    }
2035
 
2036
	    $this->img->FilledRectangle($xl,$yu,$xr,$yl);
2037
	    $this->img->PopColor();
2038
 
2039
	    // Check if we should add the vertical lines at left and right edge
2040
	    if( $this->iXAxisLblBgColor !== '' ) {
2041
		$this->img->PushColor($this->iXAxisLblBgColor);
2042
		if( $t == 2 || $t == 6 ) {
2043
		    $this->img->Line($xl,$yu-1,$xr,$yu-1);
2044
		    $this->img->Line($xl,$yl-1,$xr,$yl-1);
2045
		}
2046
		else {
2047
		    $this->img->Line($xr+1,$yu,$xr+1,$this->img->top_margin);
2048
		}
2049
		$this->img->PopColor();
2050
	    }
2051
 
2052
	}
2053
    }
2054
 
2055
    function StrokeAxis($aStrokeLabels=true) {
2056
 
2057
	if( $aStrokeLabels ) {
2058
	    $this->StrokeAxisLabelBackground();
2059
	}
2060
 
2061
	// Stroke axis
2062
	if( $this->iAxisStyle != AXSTYLE_SIMPLE ) {
2063
	    switch( $this->iAxisStyle ) {
2064
	        case AXSTYLE_BOXIN :
2065
	            $toppos = SIDE_DOWN;
2066
		    $bottompos = SIDE_UP;
2067
	            $leftpos = SIDE_RIGHT;
2068
	            $rightpos = SIDE_LEFT;
2069
	            break;
2070
		case AXSTYLE_BOXOUT :
2071
		    $toppos = SIDE_UP;
2072
	            $bottompos = SIDE_DOWN;
2073
	            $leftpos = SIDE_LEFT;
2074
		    $rightpos = SIDE_RIGHT;
2075
	            break;
2076
		case AXSTYLE_YBOXIN:
2077
	            $toppos = -100;
2078
		    $bottompos = SIDE_UP;
2079
	            $leftpos = SIDE_RIGHT;
2080
	            $rightpos = SIDE_LEFT;
2081
		    break;
2082
		case AXSTYLE_YBOXOUT:
2083
		    $toppos = -100;
2084
	            $bottompos = SIDE_DOWN;
2085
	            $leftpos = SIDE_LEFT;
2086
		    $rightpos = SIDE_RIGHT;
2087
		    break;
2088
		default:
2089
	            JpGRaphError::RaiseL(25036,$this->iAxisStyle); //('Unknown AxisStyle() : '.$this->iAxisStyle);
2090
	            break;
2091
	    }
2092
	    $this->xaxis->SetPos('min');
2093
 
2094
	    // By default we hide the first label so it doesn't cross the
2095
	    // Y-axis in case the positon hasn't been set by the user.
2096
	    // However, if we use a box we always want the first value
2097
	    // displayed so we make sure it will be displayed.
2098
	    $this->xscale->ticks->SupressFirst(false);
2099
 
2100
	    $this->xaxis->SetLabelSide(SIDE_DOWN);
2101
	    $this->xaxis->scale->ticks->SetSide($bottompos);
2102
	    $this->xaxis->Stroke($this->yscale);
2103
 
2104
	    if( $toppos != -100 ) {
2105
		// To avoid side effects we work on a new copy
2106
		$maxis = $this->xaxis;
2107
		$maxis->SetPos('max');
2108
		$maxis->SetLabelSide(SIDE_UP);
2109
		$maxis->SetLabelMargin(7);
2110
		$this->xaxis->scale->ticks->SetSide($toppos);
2111
		$maxis->Stroke($this->yscale);
2112
	    }
2113
 
2114
	    $this->yaxis->SetPos('min');
2115
	    $this->yaxis->SetLabelMargin(10);
2116
	    $this->yaxis->SetLabelSide(SIDE_LEFT);
2117
	    $this->yaxis->scale->ticks->SetSide($leftpos);
2118
	    $this->yaxis->Stroke($this->xscale);
2119
 
2120
	    $myaxis = $this->yaxis;
2121
	    $myaxis->SetPos('max');
2122
	    $myaxis->SetLabelMargin(10);
2123
	    $myaxis->SetLabelSide(SIDE_RIGHT);
2124
	    $myaxis->title->Set('');
2125
	    $myaxis->scale->ticks->SetSide($rightpos);
2126
	    $myaxis->Stroke($this->xscale);
2127
 
2128
	}
2129
	else {
2130
	    $this->xaxis->Stroke($this->yscale,$aStrokeLabels);
2131
	    $this->yaxis->Stroke($this->xscale,$aStrokeLabels);
2132
	}
2133
    }
2134
 
2135
 
2136
    // Private helper function for backgound image
2137
    function LoadBkgImage($aImgFormat='',$aFile='',$aImgStr='') {
2138
	if( $aImgStr != '' ) {
2139
	    return Image::CreateFromString($aImgStr);
2140
	}
2141
 
2142
	// Remove case sensitivity and setup appropriate function to create image
2143
	// Get file extension. This should be the LAST '.' separated part of the filename
2144
	$e = explode('.',$aFile);
2145
	$ext = strtolower($e[count($e)-1]);
2146
	if ($ext == "jpeg")  {
2147
	    $ext = "jpg";
2148
	}
2149
 
2150
	if( trim($ext) == '' )
2151
	    $ext = 'png';  // Assume PNG if no extension specified
2152
 
2153
	if( $aImgFormat == '' )
2154
	    $imgtag = $ext;
2155
	else
2156
	    $imgtag = $aImgFormat;
2157
 
2158
	$supported = imagetypes();
2159
	if( ( $ext == 'jpg' && !($supported & IMG_JPG) ) ||
2160
	    ( $ext == 'gif' && !($supported & IMG_GIF) ) ||
2161
	    ( $ext == 'png' && !($supported & IMG_PNG) ) ||
2162
	    ( $ext == 'bmp' && !($supported & IMG_WBMP) ) ||
2163
	    ( $ext == 'xpm' && !($supported & IMG_XPM) ) ) {
2164
	    JpGraphError::RaiseL(25037,$aFile);//('The image format of your background image ('.$aFile.') is not supported in your system configuration. ');
2165
	}
2166
 
2167
 
2168
	if( $imgtag == "jpg" || $imgtag == "jpeg")
2169
	{
2170
	    $f = "imagecreatefromjpeg";
2171
	    $imgtag = "jpg";
2172
	}
2173
	else
2174
	{
2175
	    $f = "imagecreatefrom".$imgtag;
2176
	}
2177
 
2178
	// Compare specified image type and file extension
2179
	if( $imgtag != $ext ) {
2180
	    //$t = "Background image seems to be of different type (has different file extension) than specified imagetype. Specified: '".$aImgFormat."'File: '".$aFile."'";
2181
	    JpGraphError::RaiseL(25038, $aImgFormat, $aFile);
2182
	}
2183
 
2184
	$img = @$f($aFile);
2185
	if( !$img ) {
2186
	    JpGraphError::RaiseL(25039,$aFile);//(" Can't read background image: '".$aFile."'");
2187
	}
2188
	return $img;
2189
    }
2190
 
2191
    function StrokeBackgroundGrad() {
2192
	if( $this->bkg_gradtype < 0  )
2193
	    return;
2194
	$grad = new Gradient($this->img);
2195
	if( $this->bkg_gradstyle == BGRAD_PLOT ) {
2196
	    $xl = $this->img->left_margin;
2197
	    $yt = $this->img->top_margin;
2198
	    $xr = $xl + $this->img->plotwidth+1 ;
2199
	    $yb = $yt + $this->img->plotheight ;
2200
	    $grad->FilledRectangle($xl,$yt,$xr,$yb,$this->bkg_gradfrom,$this->bkg_gradto,$this->bkg_gradtype);
2201
	}
2202
	else {
2203
	    $xl = 0;
2204
	    $yt = 0;
2205
	    $xr = $xl + $this->img->width - 1;
2206
	    $yb = $yt + $this->img->height;
2207
	    if( $this->doshadow  ) {
2208
		$xr -= $this->shadow_width;
2209
		$yb -= $this->shadow_width;
2210
	    }
2211
	    if( $this->doframe ) {
2212
		$yt += $this->frame_weight;
2213
		$yb -= $this->frame_weight;
2214
		$xl += $this->frame_weight;
2215
		$xr -= $this->frame_weight;
2216
	    }
2217
	    $aa = $this->img->SetAngle(0);
2218
	    $grad->FilledRectangle($xl,$yt,$xr,$yb,$this->bkg_gradfrom,$this->bkg_gradto,$this->bkg_gradtype);
2219
	    $aa = $this->img->SetAngle($aa);
2220
	}
2221
    }
2222
 
2223
    function StrokeFrameBackground() {
2224
	if( $this->background_image != "" && $this->background_cflag != "" ) {
2225
	    JpGraphError::RaiseL(25040);//('It is not possible to specify both a background image and a background country flag.');
2226
	}
2227
	if( $this->background_image != "" ) {
2228
	    $bkgimg = $this->LoadBkgImage($this->background_image_format,$this->background_image);
2229
	}
2230
	elseif( $this->background_cflag != "" ) {
2231
	    if( ! class_exists('FlagImages') ) {
2232
		JpGraphError::RaiseL(25041);//('In order to use Country flags as backgrounds you must include the "jpgraph_flags.php" file.');
2233
	    }
2234
	    $fobj = new FlagImages(FLAGSIZE4);
2235
	    $dummy='';
2236
	    $bkgimg = $fobj->GetImgByName($this->background_cflag,$dummy);
2237
	    $this->background_image_mix = $this->background_cflag_mix;
2238
	    $this->background_image_type = $this->background_cflag_type;
2239
	}
2240
	else {
2241
	    return ;
2242
	}
2243
 
2244
	$bw = ImageSX($bkgimg);
2245
	$bh = ImageSY($bkgimg);
2246
 
2247
	// No matter what the angle is we always stroke the image and frame
2248
	// assuming it is 0 degree
2249
	$aa = $this->img->SetAngle(0);
2250
 
2251
	switch( $this->background_image_type ) {
2252
	    case BGIMG_FILLPLOT: // Resize to just fill the plotarea
2253
		$this->FillMarginArea();
2254
		$this->StrokeFrame();
2255
		// Special case to hande 90 degree rotated graph corectly
2256
		if( $aa == 90 ) {
2257
		    $this->img->SetAngle(90);
2258
		    $this->FillPlotArea();
2259
		    $aa = $this->img->SetAngle(0);
2260
		    $adj = ($this->img->height - $this->img->width)/2;
2261
		    $this->img->CopyMerge($bkgimg,
2262
					  $this->img->bottom_margin-$adj,$this->img->left_margin+$adj,
2263
					  0,0,
2264
					  $this->img->plotheight+1,$this->img->plotwidth,
2265
					  $bw,$bh,$this->background_image_mix);
2266
 
2267
		}
2268
		else {
2269
		    $this->FillPlotArea();
2270
		    $this->img->CopyMerge($bkgimg,
2271
					  $this->img->left_margin,$this->img->top_margin,
2272
					  0,0,$this->img->plotwidth+1,$this->img->plotheight,
2273
					  $bw,$bh,$this->background_image_mix);
2274
		}
2275
		break;
2276
	    case BGIMG_FILLFRAME: // Fill the whole area from upper left corner, resize to just fit
2277
		$hadj=0; $vadj=0;
2278
		if( $this->doshadow ) {
2279
		    $hadj = $this->shadow_width;
2280
		    $vadj = $this->shadow_width;
2281
		}
2282
		$this->FillMarginArea();
2283
		$this->FillPlotArea();
2284
		$this->img->CopyMerge($bkgimg,0,0,0,0,$this->img->width-$hadj,$this->img->height-$vadj,
2285
				      $bw,$bh,$this->background_image_mix);
2286
		$this->StrokeFrame();
2287
		break;
2288
	    case BGIMG_COPY: // Just copy the image from left corner, no resizing
2289
		$this->FillMarginArea();
2290
		$this->FillPlotArea();
2291
		$this->img->CopyMerge($bkgimg,0,0,0,0,$bw,$bh,
2292
				      $bw,$bh,$this->background_image_mix);
2293
		$this->StrokeFrame();
2294
		break;
2295
	    case BGIMG_CENTER: // Center original image in the plot area
2296
		$this->FillMarginArea();
2297
		$this->FillPlotArea();
2298
		$centerx = round($this->img->plotwidth/2+$this->img->left_margin-$bw/2);
2299
		$centery = round($this->img->plotheight/2+$this->img->top_margin-$bh/2);
2300
		$this->img->CopyMerge($bkgimg,$centerx,$centery,0,0,$bw,$bh,
2301
				      $bw,$bh,$this->background_image_mix);
2302
		$this->StrokeFrame();
2303
		break;
2304
	    default:
2305
		JpGraphError::RaiseL(25042);//(" Unknown background image layout");
2306
	}
2307
	$this->img->SetAngle($aa);
2308
    }
2309
 
2310
    // Private
2311
    // Draw a frame around the image
2312
    function StrokeFrame() {
2313
	if( !$this->doframe ) return;
2314
 
2315
	if( $this->background_image_type <= 1 &&
2316
	    ($this->bkg_gradtype < 0 || ($this->bkg_gradtype > 0 && $this->bkg_gradstyle==BGRAD_PLOT)) ) {
2317
	    $c = $this->margin_color;
2318
	}
2319
	else {
2320
	    $c = false;
2321
	}
2322
 
2323
	if( $this->doshadow ) {
2324
	    $this->img->SetColor($this->frame_color);
2325
	    $this->img->ShadowRectangle(0,0,$this->img->width,$this->img->height,
2326
					$c,$this->shadow_width,$this->shadow_color);
2327
	}
2328
	elseif( $this->framebevel ) {
2329
	    if( $c ) {
2330
		$this->img->SetColor($this->margin_color);
2331
		$this->img->FilledRectangle(0,0,$this->img->width-1,$this->img->height-1);
2332
	    }
2333
	    $this->img->Bevel(1,1,$this->img->width-2,$this->img->height-2,
2334
			      $this->framebeveldepth,
2335
			      $this->framebevelcolor1,$this->framebevelcolor2);
2336
	    if( $this->framebevelborder ) {
2337
		$this->img->SetColor($this->framebevelbordercolor);
2338
		$this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);
2339
	    }
2340
	}
2341
	else {
2342
	    $this->img->SetLineWeight($this->frame_weight);
2343
	    if( $c ) {
2344
		$this->img->SetColor($this->margin_color);
2345
		$this->img->FilledRectangle(0,0,$this->img->width-1,$this->img->height-1);
2346
	    }
2347
	    $this->img->SetColor($this->frame_color);
2348
	    $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);
2349
	}
2350
    }
2351
 
2352
    function FillMarginArea() {
2353
	$hadj=0; $vadj=0;
2354
	if( $this->doshadow ) {
2355
	    $hadj = $this->shadow_width;
2356
	    $vadj = $this->shadow_width;
2357
	}
2358
 
2359
	$this->img->SetColor($this->margin_color);
2360
//	$this->img->FilledRectangle(0,0,$this->img->width-1-$hadj,$this->img->height-1-$vadj);
2361
 
2362
	$this->img->FilledRectangle(0,0,$this->img->width-1-$hadj,$this->img->top_margin);
2363
	$this->img->FilledRectangle(0,$this->img->top_margin,$this->img->left_margin,$this->img->height-1-$hadj);
2364
	$this->img->FilledRectangle($this->img->left_margin+1,
2365
				    $this->img->height-$this->img->bottom_margin,
2366
				    $this->img->width-1-$hadj,
2367
				    $this->img->height-1-$hadj);
2368
	$this->img->FilledRectangle($this->img->width-$this->img->right_margin,
2369
				    $this->img->top_margin+1,
2370
				    $this->img->width-1-$hadj,
2371
				    $this->img->height-$this->img->bottom_margin-1);
2372
    }
2373
 
2374
    function FillPlotArea() {
2375
	$this->img->PushColor($this->plotarea_color);
2376
	$this->img->FilledRectangle($this->img->left_margin,
2377
				    $this->img->top_margin,
2378
				    $this->img->width-$this->img->right_margin,
2379
				    $this->img->height-$this->img->bottom_margin);
2380
	$this->img->PopColor();
2381
    }
2382
 
2383
    // Stroke the plot area with either a solid color or a background image
2384
    function StrokePlotArea() {
2385
	// Note: To be consistent we really should take a possible shadow
2386
	// into account. However, that causes some problem for the LinearScale class
2387
	// since in the current design it does not have any links to class Graph which
2388
	// means it has no way of compensating for the adjusted plotarea in case of a
2389
	// shadow. So, until I redesign LinearScale we can't compensate for this.
2390
	// So just set the two adjustment parameters to zero for now.
2391
	$boxadj = 0; //$this->doframe ? $this->frame_weight : 0 ;
2392
	$adj = 0; //$this->doshadow ? $this->shadow_width : 0 ;
2393
 
2394
	if( $this->background_image != "" || $this->background_cflag != "" ) {
2395
	    $this->StrokeFrameBackground();
2396
	}
2397
	else {
2398
	    $aa = $this->img->SetAngle(0);
2399
	    $this->StrokeFrame();
2400
	    $aa = $this->img->SetAngle($aa);
2401
	    $this->StrokeBackgroundGrad();
2402
	    if( $this->bkg_gradtype < 0 ||
2403
		($this->bkg_gradtype > 0 && $this->bkg_gradstyle==BGRAD_MARGIN) ) {
2404
		$this->FillPlotArea();
2405
	    }
2406
	}
2407
    }
2408
 
2409
    function StrokeIcons() {
2410
	$n = count($this->iIcons);
2411
	for( $i=0; $i < $n; ++$i ) {
2412
	    $this->iIcons[$i]->StrokeWithScale($this->img,$this->xscale,$this->yscale);
2413
	}
2414
    }
2415
 
2416
    function StrokePlotBox() {
2417
	// Should we draw a box around the plot area?
2418
	if( $this->boxed ) {
2419
	    $this->img->SetLineWeight(1);
2420
	    $this->img->SetLineStyle('solid');
2421
	    $this->img->SetColor($this->box_color);
2422
	    for($i=0; $i < $this->box_weight; ++$i ) {
2423
		$this->img->Rectangle(
2424
		    $this->img->left_margin-$i,$this->img->top_margin-$i,
2425
		    $this->img->width-$this->img->right_margin+$i,
2426
		    $this->img->height-$this->img->bottom_margin+$i);
2427
	    }
2428
	}
2429
    }
2430
 
2431
    function SetTitleBackgroundFillStyle($aStyle,$aColor1='black',$aColor2='white') {
2432
	$this->titlebkg_fillstyle = $aStyle;
2433
	$this->titlebkg_scolor1 = $aColor1;
2434
	$this->titlebkg_scolor2 = $aColor2;
2435
    }
2436
 
2437
    function SetTitleBackground($aBackColor='gray', $aStyle=TITLEBKG_STYLE1, $aFrameStyle=TITLEBKG_FRAME_NONE, $aFrameColor='black', $aFrameWeight=1, $aBevelHeight=3, $aEnable=true) {
2438
	$this->titlebackground = $aEnable;
2439
	$this->titlebackground_color = $aBackColor;
2440
	$this->titlebackground_style = $aStyle;
2441
	$this->titlebackground_framecolor = $aFrameColor;
2442
	$this->titlebackground_framestyle = $aFrameStyle;
2443
	$this->titlebackground_frameweight = $aFrameWeight;
2444
	$this->titlebackground_bevelheight = $aBevelHeight ;
2445
    }
2446
 
2447
 
2448
    function StrokeTitles() {
2449
 
2450
	$margin=3;
2451
 
2452
	if( $this->titlebackground ) {
2453
 
2454
	    // Find out height
2455
	    $this->title->margin += 2 ;
2456
	    $h = $this->title->GetTextHeight($this->img)+$this->title->margin+$margin;
2457
	    if( $this->subtitle->t != "" && !$this->subtitle->hide ) {
2458
		$h += $this->subtitle->GetTextHeight($this->img)+$margin+
2459
		    $this->subtitle->margin;
2460
		$h += 2;
2461
	    }
2462
	    if( $this->subsubtitle->t != "" && !$this->subsubtitle->hide ) {
2463
		$h += $this->subsubtitle->GetTextHeight($this->img)+$margin+
2464
		    $this->subsubtitle->margin;
2465
		$h += 2;
2466
	    }
2467
	    $this->img->PushColor($this->titlebackground_color);
2468
	    if( $this->titlebackground_style === TITLEBKG_STYLE1 ) {
2469
		// Inside the frame
2470
		if( $this->framebevel ) {
2471
		    $x1 = $y1 = $this->framebeveldepth + 1 ;
2472
		    $x2 = $this->img->width - $this->framebeveldepth - 2 ;
2473
		    $this->title->margin += $this->framebeveldepth + 1 ;
2474
		    $h += $y1 ;
2475
		    $h += 2;
2476
		}
2477
		else {
2478
		    $x1 = $y1 = $this->frame_weight;
2479
		    $x2 = $this->img->width - 2*$x1;
2480
		}
2481
	    }
2482
	    elseif( $this->titlebackground_style === TITLEBKG_STYLE2 ) {
2483
		// Cover the frame as well
2484
		$x1 = $y1 = 0;
2485
		$x2 = $this->img->width - 1 ;
2486
	    }
2487
	    elseif( $this->titlebackground_style === TITLEBKG_STYLE3 ) {
2488
		// Cover the frame as well (the difference is that
2489
		// for style==3 a bevel frame border is on top
2490
		// of the title background)
2491
		$x1 = $y1 = 0;
2492
		$x2 = $this->img->width - 1 ;
2493
		$h += $this->framebeveldepth ;
2494
		$this->title->margin += $this->framebeveldepth ;
2495
	    }
2496
	    else {
2497
		JpGraphError::RaiseL(25043);//('Unknown title background style.');
2498
	    }
2499
 
2500
	    if( $this->titlebackground_framestyle === 3 ) {
2501
		$h += $this->titlebackground_bevelheight*2 + 1  ;
2502
		$this->title->margin += $this->titlebackground_bevelheight ;
2503
	    }
2504
 
2505
	    if( $this->doshadow ) {
2506
		$x2 -= $this->shadow_width ;
2507
	    }
2508
 
2509
	    $indent=0;
2510
	    if( $this->titlebackground_framestyle == TITLEBKG_FRAME_BEVEL ) {
2511
		$ind = $this->titlebackground_bevelheight;
2512
	    }
2513
 
2514
	    if( $this->titlebkg_fillstyle==TITLEBKG_FILLSTYLE_HSTRIPED ) {
2515
		$this->img->FilledRectangle2($x1+$ind,$y1+$ind,$x2-$ind,$h-$ind,
2516
					     $this->titlebkg_scolor1,
2517
					     $this->titlebkg_scolor2);
2518
	    }
2519
	    elseif( $this->titlebkg_fillstyle==TITLEBKG_FILLSTYLE_VSTRIPED ) {
2520
		$this->img->FilledRectangle2($x1+$ind,$y1+$ind,$x2-$ind,$h-$ind,
2521
					     $this->titlebkg_scolor1,
2522
					     $this->titlebkg_scolor2,2);
2523
	    }
2524
	    else {
2525
		// Solid fill
2526
		$this->img->FilledRectangle($x1,$y1,$x2,$h);
2527
	    }
2528
	    $this->img->PopColor();
2529
 
2530
	    $this->img->PushColor($this->titlebackground_framecolor);
2531
	    $this->img->SetLineWeight($this->titlebackground_frameweight);
2532
	    if( $this->titlebackground_framestyle == TITLEBKG_FRAME_FULL ) {
2533
		// Frame background
2534
		$this->img->Rectangle($x1,$y1,$x2,$h);
2535
	    }
2536
	    elseif( $this->titlebackground_framestyle == TITLEBKG_FRAME_BOTTOM ) {
2537
		// Bottom line only
2538
		$this->img->Line($x1,$h,$x2,$h);
2539
	    }
2540
	    elseif( $this->titlebackground_framestyle == TITLEBKG_FRAME_BEVEL ) {
2541
		$this->img->Bevel($x1,$y1,$x2,$h,$this->titlebackground_bevelheight);
2542
	    }
2543
	    $this->img->PopColor();
2544
 
2545
	    // This is clumsy. But we neeed to stroke the whole graph frame if it is
2546
	    // set to bevel to get the bevel shading on top of the text background
2547
	    if( $this->framebevel && $this->doframe &&
2548
		$this->titlebackground_style === 3 ) {
2549
		$this->img->Bevel(1,1,$this->img->width-2,$this->img->height-2,
2550
				  $this->framebeveldepth,
2551
				  $this->framebevelcolor1,$this->framebevelcolor2);
2552
		if( $this->framebevelborder ) {
2553
		    $this->img->SetColor($this->framebevelbordercolor);
2554
		    $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);
2555
		}
2556
	    }
2557
	}
2558
 
2559
	// Stroke title
2560
	$y = $this->title->margin;
2561
	if( $this->title->halign == 'center' )
2562
	    $this->title->Center(0,$this->img->width,$y);
2563
	elseif( $this->title->halign == 'left' ) {
2564
	    $this->title->SetPos($this->title->margin+2,$y);
2565
	}
2566
	elseif( $this->title->halign == 'right' ) {
2567
	    $indent = 0;
2568
	    if( $this->doshadow )
2569
		$indent = $this->shadow_width+2;
2570
	    $this->title->SetPos($this->img->width-$this->title->margin-$indent,$y,'right');
2571
	}
2572
	$this->title->Stroke($this->img);
2573
 
2574
	// ... and subtitle
2575
	$y += $this->title->GetTextHeight($this->img) + $margin + $this->subtitle->margin;
2576
	if( $this->subtitle->halign == 'center' )
2577
	    $this->subtitle->Center(0,$this->img->width,$y);
2578
	elseif( $this->subtitle->halign == 'left' ) {
2579
	    $this->subtitle->SetPos($this->subtitle->margin+2,$y);
2580
	}
2581
	elseif( $this->subtitle->halign == 'right' ) {
2582
	    $indent = 0;
2583
	    if( $this->doshadow )
2584
		$indent = $this->shadow_width+2;
2585
	    $this->subtitle->SetPos($this->img->width-$this->subtitle->margin-$indent,$y,'right');
2586
	}
2587
	$this->subtitle->Stroke($this->img);
2588
 
2589
	// ... and subsubtitle
2590
	$y += $this->subtitle->GetTextHeight($this->img) + $margin + $this->subsubtitle->margin;
2591
	if( $this->subsubtitle->halign == 'center' )
2592
	    $this->subsubtitle->Center(0,$this->img->width,$y);
2593
	elseif( $this->subsubtitle->halign == 'left' ) {
2594
	    $this->subsubtitle->SetPos($this->subsubtitle->margin+2,$y);
2595
	}
2596
	elseif( $this->subsubtitle->halign == 'right' ) {
2597
	    $indent = 0;
2598
	    if( $this->doshadow )
2599
		$indent = $this->shadow_width+2;
2600
	    $this->subsubtitle->SetPos($this->img->width-$this->subsubtitle->margin-$indent,$y,'right');
2601
	}
2602
	$this->subsubtitle->Stroke($this->img);
2603
 
2604
	// ... and fancy title
2605
	$this->tabtitle->Stroke($this->img);
2606
 
2607
    }
2608
 
2609
    function StrokeTexts() {
2610
	// Stroke any user added text objects
2611
	if( $this->texts != null ) {
2612
	    for($i=0; $i < count($this->texts); ++$i) {
2613
		$this->texts[$i]->StrokeWithScale($this->img,$this->xscale,$this->yscale);
2614
	    }
2615
	}
2616
 
2617
	if( $this->y2texts != null && $this->y2scale != null ) {
2618
	    for($i=0; $i < count($this->y2texts); ++$i) {
2619
		$this->y2texts[$i]->StrokeWithScale($this->img,$this->xscale,$this->y2scale);
2620
	    }
2621
	}
2622
 
2623
    }
2624
 
2625
    function StrokeTables() {
2626
	if( $this->iTables != null ) {
2627
	    $n = count($this->iTables);
2628
	    for( $i=0; $i < $n; ++$i ) {
2629
		$this->iTables[$i]->StrokeWithScale($this->img,$this->xscale,$this->yscale);
2630
	    }
2631
	}
2632
    }
2633
 
2634
    function DisplayClientSideaImageMapAreas() {
2635
	// Debug stuff - display the outline of the image map areas
2636
	$csim='';
2637
	foreach ($this->plots as $p) {
2638
	    $csim.= $p->GetCSIMareas();
2639
	}
2640
	$csim .= $this->legend->GetCSIMareas();
2641
	if (preg_match_all("/area shape=\"(\w+)\" coords=\"([0-9\, ]+)\"/", $csim, $coords)) {
2642
	    $this->img->SetColor($this->csimcolor);
2643
	    $n = count($coords[0]);
2644
	    for ($i=0; $i < $n; $i++) {
2645
		if ($coords[1][$i]=="poly") {
2646
		    preg_match_all('/\s*([0-9]+)\s*,\s*([0-9]+)\s*,*/',$coords[2][$i],$pts);
2647
		    $this->img->SetStartPoint($pts[1][count($pts[0])-1],$pts[2][count($pts[0])-1]);
2648
		    $m = count($pts[0]);
2649
		    for ($j=0; $j < $m; $j++) {
2650
			$this->img->LineTo($pts[1][$j],$pts[2][$j]);
2651
		    }
2652
		} else if ($coords[1][$i]=="rect") {
2653
		    $pts = preg_split('/,/', $coords[2][$i]);
2654
		    $this->img->SetStartPoint($pts[0],$pts[1]);
2655
		    $this->img->LineTo($pts[2],$pts[1]);
2656
		    $this->img->LineTo($pts[2],$pts[3]);
2657
		    $this->img->LineTo($pts[0],$pts[3]);
2658
		    $this->img->LineTo($pts[0],$pts[1]);
2659
		}
2660
	    }
2661
	}
2662
    }
2663
 
2664
    // Text scale offset in fractions of a major scale step
2665
    function SetTextScaleOff($aOff) {
2666
	$this->text_scale_off = $aOff;
2667
	$this->xscale->text_scale_off = $aOff;
2668
    }
2669
 
2670
    // Text width of bar to be centered in absolute pixels
2671
    function SetTextScaleAbsCenterOff($aOff) {
2672
	$this->text_scale_abscenteroff = $aOff;
2673
    }
2674
 
2675
    // Get Y min and max values for added lines
2676
    function GetLinesYMinMax( $aLines ) {
2677
	$n = count($aLines);
2678
	if( $n == 0 ) return false;
2679
	$min = $aLines[0]->scaleposition ;
2680
	$max = $min ;
2681
	$flg = false;
2682
	for( $i=0; $i < $n; ++$i ) {
2683
	    if( $aLines[$i]->direction == HORIZONTAL ) {
2684
		$flg = true ;
2685
		$v = $aLines[$i]->scaleposition ;
2686
		if( $min > $v ) $min = $v ;
2687
		if( $max < $v ) $max = $v ;
2688
	    }
2689
	}
2690
	return $flg ? array($min,$max) : false ;
2691
    }
2692
 
2693
    // Get X min and max values for added lines
2694
    function GetLinesXMinMax( $aLines ) {
2695
	$n = count($aLines);
2696
	if( $n == 0 ) return false ;
2697
	$min = $aLines[0]->scaleposition ;
2698
	$max = $min ;
2699
	$flg = false;
2700
	for( $i=0; $i < $n; ++$i ) {
2701
	    if( $aLines[$i]->direction == VERTICAL ) {
2702
		$flg = true ;
2703
		$v = $aLines[$i]->scaleposition ;
2704
		if( $min > $v ) $min = $v ;
2705
		if( $max < $v ) $max = $v ;
2706
	    }
2707
	}
2708
	return $flg ? array($min,$max) : false ;
2709
    }
2710
 
2711
    // Get min and max values for all included plots
2712
    function GetPlotsYMinMax(&$aPlots) {
2713
	$n = count($aPlots);
2714
	$i=0;
2715
	do {
2716
	    list($xmax,$max) = $aPlots[$i]->Max();
2717
	} while( ++$i < $n && !is_numeric($max) );
2718
 
2719
	$i=0;
2720
	do {
2721
           list($xmin,$min) = $aPlots[$i]->Min();
2722
       } while( ++$i < $n && !is_numeric($min) );
2723
 
2724
	if( !is_numeric($min) || !is_numeric($max) ) {
2725
	    JpGraphError::RaiseL(25044);//('Cannot use autoscaling since it is impossible to determine a valid min/max value  of the Y-axis (only null values).');
2726
	}
2727
 
2728
	list($xmax,$max) = $aPlots[0]->Max();
2729
	list($xmin,$min) = $aPlots[0]->Min();
2730
	for($i=0; $i < count($aPlots); ++$i ) {
2731
	    list($xmax,$ymax)=$aPlots[$i]->Max();
2732
	    list($xmin,$ymin)=$aPlots[$i]->Min();
2733
	    if (is_numeric($ymax)) $max=max($max,$ymax);
2734
	    if (is_numeric($ymin)) $min=min($min,$ymin);
2735
	}
2736
	if( $min == '' ) $min = 0;
2737
	if( $max == '' ) $max = 0;
2738
	if( $min == 0 && $max == 0 ) {
2739
	    // Special case if all values are 0
2740
	    $min=0;$max=1;
2741
	}
2742
	return array($min,$max);
2743
    }
2744
 
2745
} // Class
2746
 
2747
 
2748
 
2749
//===================================================
2750
// CLASS LineProperty
2751
// Description: Holds properties for a line
2752
//===================================================
2753
class LineProperty {
2754
    var $iWeight=1, $iColor="black",$iStyle="solid";
2755
    var $iShow=true;
2756
 
2757
//---------------
2758
// PUBLIC METHODS
2759
    function SetColor($aColor) {
2760
	$this->iColor = $aColor;
2761
    }
2762
 
2763
    function SetWeight($aWeight) {
2764
	$this->iWeight = $aWeight;
2765
    }
2766
 
2767
    function SetStyle($aStyle) {
2768
	$this->iStyle = $aStyle;
2769
    }
2770
 
2771
    function Show($aShow=true) {
2772
	$this->iShow=$aShow;
2773
    }
2774
 
2775
    function Stroke(&$aImg,$aX1,$aY1,$aX2,$aY2) {
2776
	if( $this->iShow ) {
2777
	    $aImg->PushColor($this->iColor);
2778
	    $oldls = $aImg->line_style;
2779
	    $oldlw = $aImg->line_weight;
2780
	    $aImg->SetLineWeight($this->iWeight);
2781
	    $aImg->SetLineStyle($this->iStyle);
2782
	    $aImg->StyleLine($aX1,$aY1,$aX2,$aY2);
2783
	    $aImg->PopColor($this->iColor);
2784
	    $aImg->line_style = $oldls;
2785
	    $aImg->line_weight = $oldlw;
2786
 
2787
	}
2788
    }
2789
}
2790
 
2791
 
2792
//===================================================
2793
// CLASS Text
2794
// Description: Arbitrary text object that can be added to the graph
2795
//===================================================
2796
class Text {
2797
    var $t,$x=0,$y=0,$halign="left",$valign="top",$color=array(0,0,0);
2798
    var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=10;
2799
    var $hide=false, $dir=0;
2800
    var $boxed=false;	// Should the text be boxed
2801
    var $paragraph_align="left";
2802
    var $margin=0;
2803
    var $icornerradius=0,$ishadowwidth=3;
2804
    var $iScalePosY=null,$iScalePosX=null;
2805
    var $iWordwrap=0;
2806
    var $fcolor='white',$bcolor='black',$shadow=false;
2807
    var $iCSIMarea='',$iCSIMalt='',$iCSIMtarget='',$iCSIMWinTarget='';
2808
 
2809
//---------------
2810
// CONSTRUCTOR
2811
 
2812
    // Create new text at absolute pixel coordinates
2813
    function Text($aTxt='',$aXAbsPos=0,$aYAbsPos=0) {
2814
	if( ! is_string($aTxt) ) {
2815
	    JpGraphError::RaiseL(25050);//('First argument to Text::Text() must be s atring.');
2816
	}
2817
	$this->t = $aTxt;
2818
	$this->x = round($aXAbsPos);
2819
	$this->y = round($aYAbsPos);
2820
	$this->margin = 0;
2821
    }
2822
//---------------
2823
// PUBLIC METHODS
2824
    // Set the string in the text object
2825
    function Set($aTxt) {
2826
	$this->t = $aTxt;
2827
    }
2828
 
2829
    // Alias for Pos()
2830
    function SetPos($aXAbsPos=0,$aYAbsPos=0,$aHAlign="left",$aVAlign="top") {
2831
	$this->Pos($aXAbsPos,$aYAbsPos,$aHAlign,$aVAlign);
2832
    }
2833
 
2834
    // Specify the position and alignment for the text object
2835
    function Pos($aXAbsPos=0,$aYAbsPos=0,$aHAlign="left",$aVAlign="top") {
2836
	$this->x = $aXAbsPos;
2837
	$this->y = $aYAbsPos;
2838
	$this->halign = $aHAlign;
2839
	$this->valign = $aVAlign;
2840
    }
2841
 
2842
    function SetScalePos($aX,$aY) {
2843
	$this->iScalePosX = $aX;
2844
	$this->iScalePosY = $aY;
2845
    }
2846
 
2847
    // Specify alignment for the text
2848
    function Align($aHAlign,$aVAlign="top",$aParagraphAlign="") {
2849
	$this->halign = $aHAlign;
2850
	$this->valign = $aVAlign;
2851
	if( $aParagraphAlign != "" )
2852
	    $this->paragraph_align = $aParagraphAlign;
2853
    }
2854
 
2855
    // Alias
2856
    function SetAlign($aHAlign,$aVAlign="top",$aParagraphAlign="") {
2857
	$this->Align($aHAlign,$aVAlign,$aParagraphAlign);
2858
    }
2859
 
2860
    // Specifies the alignment for a multi line text
2861
    function ParagraphAlign($aAlign) {
2862
	$this->paragraph_align = $aAlign;
2863
    }
2864
 
2865
    // Specifies the alignment for a multi line text
2866
    function SetParagraphAlign($aAlign) {
2867
	$this->paragraph_align = $aAlign;
2868
    }
2869
 
2870
    function SetShadow($aShadowColor='darkgray',$aShadowWidth=3) {
2871
	$this->ishadowwidth=$aShadowWidth;
2872
	$this->shadow=$aShadowColor;
2873
	$this->boxed=true;
2874
    }
2875
 
2876
    function SetWordWrap($aCol) {
2877
	$this->iWordwrap = $aCol ;
2878
    }
2879
 
2880
    // Specify that the text should be boxed. fcolor=frame color, bcolor=border color,
2881
    // $shadow=drop shadow should be added around the text.
2882
    function SetBox($aFrameColor=array(255,255,255),$aBorderColor=array(0,0,0),$aShadowColor=false,$aCornerRadius=4,$aShadowWidth=3) {
2883
	if( $aFrameColor==false )
2884
	    $this->boxed=false;
2885
	else
2886
	    $this->boxed=true;
2887
	$this->fcolor=$aFrameColor;
2888
	$this->bcolor=$aBorderColor;
2889
	// For backwards compatibility when shadow was just true or false
2890
	if( $aShadowColor === true )
2891
	    $aShadowColor = 'gray';
2892
	$this->shadow=$aShadowColor;
2893
	$this->icornerradius=$aCornerRadius;
2894
	$this->ishadowwidth=$aShadowWidth;
2895
    }
2896
 
2897
    // Hide the text
2898
    function Hide($aHide=true) {
2899
	$this->hide=$aHide;
2900
    }
2901
 
2902
    // This looks ugly since it's not a very orthogonal design
2903
    // but I added this "inverse" of Hide() to harmonize
2904
    // with some classes which I designed more recently (especially)
2905
    // jpgraph_gantt
2906
    function Show($aShow=true) {
2907
	$this->hide=!$aShow;
2908
    }
2909
 
2910
    // Specify font
2911
    function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
2912
	$this->font_family=$aFamily;
2913
	$this->font_style=$aStyle;
2914
	$this->font_size=$aSize;
2915
    }
2916
 
2917
    // Center the text between $left and $right coordinates
2918
    function Center($aLeft,$aRight,$aYAbsPos=false) {
2919
	$this->x = $aLeft + ($aRight-$aLeft	)/2;
2920
	$this->halign = "center";
2921
	if( is_numeric($aYAbsPos) )
2922
	    $this->y = $aYAbsPos;
2923
    }
2924
 
2925
    // Set text color
2926
    function SetColor($aColor) {
2927
	$this->color = $aColor;
2928
    }
2929
 
2930
    function SetAngle($aAngle) {
2931
	$this->SetOrientation($aAngle);
2932
    }
2933
 
2934
    // Orientation of text. Note only TTF fonts can have an arbitrary angle
2935
    function SetOrientation($aDirection=0) {
2936
	if( is_numeric($aDirection) )
2937
	    $this->dir=$aDirection;
2938
	elseif( $aDirection=="h" )
2939
	    $this->dir = 0;
2940
	elseif( $aDirection=="v" )
2941
	    $this->dir = 90;
2942
	else JpGraphError::RaiseL(25051);//(" Invalid direction specified for text.");
2943
    }
2944
 
2945
    // Total width of text
2946
    function GetWidth(&$aImg) {
2947
	$aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
2948
	$w = $aImg->GetTextWidth($this->t,$this->dir);
2949
	return $w;
2950
    }
2951
 
2952
    // Hight of font
2953
    function GetFontHeight(&$aImg) {
2954
	$aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
2955
	$h = $aImg->GetFontHeight();
2956
	return $h;
2957
 
2958
    }
2959
 
2960
    function GetTextHeight(&$aImg) {
2961
	$aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
2962
	$h = $aImg->GetTextHeight($this->t,$this->dir);
2963
	return $h;
2964
    }
2965
 
2966
    function GetHeight(&$aImg) {
2967
	// Synonym for GetTextHeight()
2968
	$aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
2969
	$h = $aImg->GetTextHeight($this->t,$this->dir);
2970
	return $h;
2971
    }
2972
 
2973
    // Set the margin which will be interpretated differently depending
2974
    // on the context.
2975
    function SetMargin($aMarg) {
2976
	$this->margin = $aMarg;
2977
    }
2978
 
2979
    function StrokeWithScale(&$aImg,$axscale,$ayscale) {
2980
	if( $this->iScalePosX === null ||
2981
	    $this->iScalePosY === null ) {
2982
	    $this->Stroke($aImg);
2983
	}
2984
	else {
2985
	    $this->Stroke($aImg,
2986
			  round($axscale->Translate($this->iScalePosX)),
2987
			  round($ayscale->Translate($this->iScalePosY)));
2988
	}
2989
    }
2990
 
2991
    function SetCSIMTarget($aURITarget,$aAlt='',$aWinTarget='') {
2992
	$this->iCSIMtarget = $aURITarget;
2993
	$this->iCSIMalt = $aAlt;
2994
	$this->iCSIMWinTarget = $aWinTarget;
2995
    }
2996
 
2997
    function GetCSIMareas() {
2998
	if( $this->iCSIMtarget !== '' )
2999
	    return $this->iCSIMarea;
3000
	else
3001
	    return '';
3002
    }
3003
 
3004
    // Display text in image
3005
    function Stroke(&$aImg,$x=null,$y=null) {
3006
 
3007
	if( !empty($x) ) $this->x = round($x);
3008
	if( !empty($y) ) $this->y = round($y);
3009
 
3010
	// Insert newlines
3011
	if( $this->iWordwrap > 0 ) {
3012
	    $this->t = wordwrap($this->t,$this->iWordwrap,"\n");
3013
	}
3014
 
3015
	// If position been given as a fraction of the image size
3016
	// calculate the absolute position
3017
	if( $this->x < 1 && $this->x > 0 ) $this->x *= $aImg->width;
3018
	if( $this->y < 1 && $this->y > 0 ) $this->y *= $aImg->height;
3019
 
3020
	$aImg->PushColor($this->color);
3021
	$aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
3022
	$aImg->SetTextAlign($this->halign,$this->valign);
3023
	if( $this->boxed ) {
3024
	    if( $this->fcolor=="nofill" )
3025
		$this->fcolor=false;
3026
	    $aImg->SetLineWeight(1);
3027
	    $bbox = $aImg->StrokeBoxedText($this->x,$this->y,$this->t,
3028
				   $this->dir,$this->fcolor,$this->bcolor,$this->shadow,
3029
				   $this->paragraph_align,5,5,$this->icornerradius,
3030
				   $this->ishadowwidth);
3031
	}
3032
	else {
3033
	    $bbox = $aImg->StrokeText($this->x,$this->y,$this->t,$this->dir,$this->paragraph_align);
3034
	}
3035
 
3036
	// Create CSIM targets
3037
	$coords = $bbox[0].','.$bbox[1].','.$bbox[2].','.$bbox[3].','.$bbox[4].','.$bbox[5].','.$bbox[6].','.$bbox[7];
3038
	$this->iCSIMarea = "<area shape=\"poly\" coords=\"$coords\" href=\"".htmlentities($this->iCSIMtarget)."\" ";
3039
	if( trim($this->iCSIMalt) != '' ) {
3040
	    $this->iCSIMarea .= " alt=\"".$this->iCSIMalt."\" ";
3041
	    $this->iCSIMarea .= " title=\"".$this->iCSIMalt."\" ";
3042
	}
3043
	if( trim($this->iCSIMWinTarget) != '' ) {
3044
	    $this->iCSIMarea .= " target=\"".$this->iCSIMWinTarget."\" ";
3045
	}
3046
	$this->iCSIMarea .= " />\n";
3047
 
3048
	$aImg->PopColor($this->color);
3049
 
3050
    }
3051
} // Class
3052
 
3053
class GraphTabTitle extends Text{
3054
    var $corner = 6 , $posx = 7, $posy = 4;
3055
    var $color='darkred',$fillcolor='lightyellow',$bordercolor='black';
3056
    var $align = 'left', $width=TABTITLE_WIDTHFIT;
3057
    function GraphTabTitle() {
3058
	$this->t = '';
3059
	$this->font_style = FS_BOLD;
3060
	$this->hide = true;
3061
    }
3062
 
3063
    function SetColor($aTxtColor,$aFillColor='lightyellow',$aBorderColor='black') {
3064
	$this->color = $aTxtColor;
3065
	$this->fillcolor = $aFillColor;
3066
	$this->bordercolor = $aBorderColor;
3067
    }
3068
 
3069
    function SetFillColor($aFillColor) {
3070
	$this->fillcolor = $aFillColor;
3071
    }
3072
 
3073
    function SetTabAlign($aAlign) {
3074
	// Synonym for SetPos
3075
	$this->align = $aAlign;
3076
    }
3077
 
3078
    function SetPos($aAlign) {
3079
	$this->align = $aAlign;
3080
    }
3081
 
3082
    function SetWidth($aWidth) {
3083
	$this->width = $aWidth ;
3084
    }
3085
 
3086
    function Set($t) {
3087
	$this->t = $t;
3088
	$this->hide = false;
3089
    }
3090
 
3091
    function SetCorner($aD) {
3092
	$this->corner = $aD ;
3093
    }
3094
 
3095
    function Stroke(&$aImg) {
3096
	if( $this->hide )
3097
	    return;
3098
	$this->boxed = false;
3099
	$w = $this->GetWidth($aImg) + 2*$this->posx;
3100
	$h = $this->GetTextHeight($aImg) + 2*$this->posy;
3101
 
3102
	$x = $aImg->left_margin;
3103
	$y = $aImg->top_margin;
3104
 
3105
	if( $this->width === TABTITLE_WIDTHFIT ) {
3106
	    if( $this->align == 'left' ) {
3107
		$p = array($x,                $y,
3108
			   $x,                $y-$h+$this->corner,
3109
			   $x + $this->corner,$y-$h,
3110
			   $x + $w - $this->corner, $y-$h,
3111
			   $x + $w, $y-$h+$this->corner,
3112
			   $x + $w, $y);
3113
	    }
3114
	    elseif( $this->align == 'center' ) {
3115
		$x += round($aImg->plotwidth/2) - round($w/2);
3116
		$p = array($x, $y,
3117
			   $x, $y-$h+$this->corner,
3118
			   $x + $this->corner, $y-$h,
3119
			   $x + $w - $this->corner, $y-$h,
3120
			   $x + $w, $y-$h+$this->corner,
3121
			   $x + $w, $y);
3122
	    }
3123
	    else {
3124
		$x += $aImg->plotwidth -$w;
3125
		$p = array($x, $y,
3126
			   $x, $y-$h+$this->corner,
3127
			   $x + $this->corner,$y-$h,
3128
			   $x + $w - $this->corner, $y-$h,
3129
			   $x + $w, $y-$h+$this->corner,
3130
			   $x + $w, $y);
3131
	    }
3132
	}
3133
	else {
3134
	    if( $this->width === TABTITLE_WIDTHFULL )
3135
		$w = $aImg->plotwidth ;
3136
	    else
3137
		$w = $this->width ;
3138
 
3139
	    // Make the tab fit the width of the plot area
3140
	    $p = array($x,                $y,
3141
		       $x,                $y-$h+$this->corner,
3142
		       $x + $this->corner,$y-$h,
3143
		       $x + $w - $this->corner, $y-$h,
3144
		       $x + $w, $y-$h+$this->corner,
3145
		       $x + $w, $y);
3146
 
3147
	}
3148
	if( $this->halign == 'left' ) {
3149
	    $aImg->SetTextAlign('left','bottom');
3150
	    $x += $this->posx;
3151
	    $y -= $this->posy;
3152
	}
3153
	elseif( $this->halign == 'center' ) {
3154
	    $aImg->SetTextAlign('center','bottom');
3155
	    $x += $w/2;
3156
	    $y -= $this->posy;
3157
	}
3158
	else {
3159
	    $aImg->SetTextAlign('right','bottom');
3160
	    $x += $w - $this->posx;
3161
	    $y -= $this->posy;
3162
	}
3163
 
3164
	$aImg->SetColor($this->fillcolor);
3165
	$aImg->FilledPolygon($p);
3166
 
3167
	$aImg->SetColor($this->bordercolor);
3168
	$aImg->Polygon($p,true);
3169
 
3170
	$aImg->SetColor($this->color);
3171
	$aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
3172
	$aImg->StrokeText($x,$y,$this->t,0,'center');
3173
    }
3174
 
3175
}
3176
 
3177
//===================================================
3178
// CLASS SuperScriptText
3179
// Description: Format a superscript text
3180
//===================================================
3181
class SuperScriptText extends Text {
3182
    var $iSuper="";
3183
    var $sfont_family="",$sfont_style="",$sfont_size=8;
3184
    var $iSuperMargin=2,$iVertOverlap=4,$iSuperScale=0.65;
3185
    var $iSDir=0;
3186
    var $iSimple=false;
3187
 
3188
    function SuperScriptText($aTxt="",$aSuper="",$aXAbsPos=0,$aYAbsPos=0) {
3189
	parent::Text($aTxt,$aXAbsPos,$aYAbsPos);
3190
	$this->iSuper = $aSuper;
3191
    }
3192
 
3193
    function FromReal($aVal,$aPrecision=2) {
3194
	// Convert a floating point number to scientific notation
3195
	$neg=1.0;
3196
	if( $aVal < 0 ) {
3197
	    $neg = -1.0;
3198
	    $aVal = -$aVal;
3199
	}
3200
 
3201
	$l = floor(log10($aVal));
3202
	$a = sprintf("%0.".$aPrecision."f",round($aVal / pow(10,$l),$aPrecision));
3203
	$a *= $neg;
3204
	if( $this->iSimple && ($a == 1 || $a==-1) ) $a = '';
3205
 
3206
	if( $a != '' )
3207
	    $this->t = $a.' * 10';
3208
	else {
3209
	    if( $neg == 1 )
3210
		$this->t = '10';
3211
	    else
3212
		$this->t = '-10';
3213
	}
3214
	$this->iSuper = $l;
3215
    }
3216
 
3217
    function Set($aTxt,$aSuper="") {
3218
	$this->t = $aTxt;
3219
	$this->iSuper = $aSuper;
3220
    }
3221
 
3222
    function SetSuperFont($aFontFam,$aFontStyle=FS_NORMAL,$aFontSize=8) {
3223
	$this->sfont_family = $aFontFam;
3224
	$this->sfont_style = $aFontStyle;
3225
	$this->sfont_size = $aFontSize;
3226
    }
3227
 
3228
    // Total width of text
3229
    function GetWidth(&$aImg) {
3230
	$aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
3231
	$w = $aImg->GetTextWidth($this->t);
3232
	$aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size);
3233
	$w += $aImg->GetTextWidth($this->iSuper);
3234
	$w += $this->iSuperMargin;
3235
	return $w;
3236
    }
3237
 
3238
    // Hight of font (approximate the height of the text)
3239
    function GetFontHeight(&$aImg) {
3240
	$aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
3241
	$h = $aImg->GetFontHeight();
3242
	$aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size);
3243
	$h += $aImg->GetFontHeight();
3244
	return $h;
3245
    }
3246
 
3247
    // Hight of text
3248
    function GetTextHeight(&$aImg) {
3249
	$aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
3250
	$h = $aImg->GetTextHeight($this->t);
3251
	$aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size);
3252
	$h += $aImg->GetTextHeight($this->iSuper);
3253
	return $h;
3254
    }
3255
 
3256
    function Stroke(&$aImg,$ax=-1,$ay=-1) {
3257
 
3258
        // To position the super script correctly we need different
3259
	// cases to handle the alignmewnt specified since that will
3260
	// determine how we can interpret the x,y coordinates
3261
 
3262
	$w = parent::GetWidth($aImg);
3263
	$h = parent::GetTextHeight($aImg);
3264
	switch( $this->valign ) {
3265
	    case 'top':
3266
		$sy = $this->y;
3267
		break;
3268
	    case 'center':
3269
		$sy = $this->y - $h/2;
3270
		break;
3271
	    case 'bottom':
3272
		$sy = $this->y - $h;
3273
		break;
3274
	    default:
3275
		JpGraphError::RaiseL(25052);//('PANIC: Internal error in SuperScript::Stroke(). Unknown vertical alignment for text');
3276
		break;
3277
	}
3278
 
3279
	switch( $this->halign ) {
3280
	    case 'left':
3281
		$sx = $this->x + $w;
3282
		break;
3283
	    case 'center':
3284
		$sx = $this->x + $w/2;
3285
		break;
3286
	    case 'right':
3287
		$sx = $this->x;
3288
		break;
3289
	    default:
3290
		JpGraphError::RaiseL(25053);//('PANIC: Internal error in SuperScript::Stroke(). Unknown horizontal alignment for text');
3291
		break;
3292
	}
3293
 
3294
	$sx += $this->iSuperMargin;
3295
	$sy += $this->iVertOverlap;
3296
 
3297
	// Should we automatically determine the font or
3298
	// has the user specified it explicetly?
3299
	if( $this->sfont_family == "" ) {
3300
	    if( $this->font_family <= FF_FONT2 ) {
3301
		if( $this->font_family == FF_FONT0 ) {
3302
		    $sff = FF_FONT0;
3303
		}
3304
		elseif( $this->font_family == FF_FONT1 ) {
3305
		    if( $this->font_style == FS_NORMAL )
3306
			$sff = FF_FONT0;
3307
		    else
3308
			$sff = FF_FONT1;
3309
		}
3310
		else {
3311
		    $sff = FF_FONT1;
3312
		}
3313
		$sfs = $this->font_style;
3314
		$sfz = $this->font_size;
3315
	    }
3316
	    else {
3317
		// TTF fonts
3318
		$sff = $this->font_family;
3319
		$sfs = $this->font_style;
3320
		$sfz = floor($this->font_size*$this->iSuperScale);
3321
		if( $sfz < 8 ) $sfz = 8;
3322
	    }
3323
	    $this->sfont_family = $sff;
3324
	    $this->sfont_style = $sfs;
3325
	    $this->sfont_size = $sfz;
3326
	}
3327
	else {
3328
	    $sff = $this->sfont_family;
3329
	    $sfs = $this->sfont_style;
3330
	    $sfz = $this->sfont_size;
3331
	}
3332
 
3333
	parent::Stroke($aImg,$ax,$ay);
3334
 
3335
 
3336
	// For the builtin fonts we need to reduce the margins
3337
	// since the bounding bx reported for the builtin fonts
3338
	// are much larger than for the TTF fonts.
3339
	if( $sff <= FF_FONT2 ) {
3340
	    $sx -= 2;
3341
	    $sy += 3;
3342
	}
3343
 
3344
	$aImg->SetTextAlign('left','bottom');
3345
	$aImg->SetFont($sff,$sfs,$sfz);
3346
	$aImg->PushColor($this->color);
3347
	$aImg->StrokeText($sx,$sy,$this->iSuper,$this->iSDir,'left');
3348
	$aImg->PopColor();
3349
    }
3350
}
3351
 
3352
 
3353
//===================================================
3354
// CLASS Grid
3355
// Description: responsible for drawing grid lines in graph
3356
//===================================================
3357
class Grid {
3358
    var $img;
3359
    var $scale;
3360
    var $grid_color='#DDDDDD',$grid_mincolor='#DDDDDD';
3361
    var $type="solid";
3362
    var $show=false, $showMinor=false,$weight=1;
3363
    var $fill=false,$fillcolor=array('#EFEFEF','#BBCCFF');
3364
//---------------
3365
// CONSTRUCTOR
3366
    function Grid(&$aAxis) {
3367
	$this->scale = &$aAxis->scale;
3368
	$this->img = &$aAxis->img;
3369
    }
3370
//---------------
3371
// PUBLIC METHODS
3372
    function SetColor($aMajColor,$aMinColor=false) {
3373
	$this->grid_color=$aMajColor;
3374
	if( $aMinColor === false )
3375
	    $aMinColor = $aMajColor ;
3376
	$this->grid_mincolor = $aMinColor;
3377
    }
3378
 
3379
    function SetWeight($aWeight) {
3380
	$this->weight=$aWeight;
3381
    }
3382
 
3383
    // Specify if grid should be dashed, dotted or solid
3384
    function SetLineStyle($aType) {
3385
	$this->type = $aType;
3386
    }
3387
 
3388
    // Decide if both major and minor grid should be displayed
3389
    function Show($aShowMajor=true,$aShowMinor=false) {
3390
	$this->show=$aShowMajor;
3391
	$this->showMinor=$aShowMinor;
3392
    }
3393
 
3394
    function SetFill($aFlg=true,$aColor1='lightgray',$aColor2='lightblue') {
3395
	$this->fill = $aFlg;
3396
	$this->fillcolor = array( $aColor1, $aColor2 );
3397
    }
3398
 
3399
    // Display the grid
3400
    function Stroke() {
3401
 
3402
	// We do not have minor ticks (or grids) for text scales
3403
	if( $this->showMinor && !$this->scale->textscale ) {
3404
	    $tmp = $this->grid_color;
3405
	    $this->grid_color = $this->grid_mincolor;
3406
	    $this->DoStroke($this->scale->ticks->ticks_pos);
3407
 
3408
	    $this->grid_color = $tmp;
3409
	    $this->DoStroke($this->scale->ticks->maj_ticks_pos);
3410
	}
3411
	else {
3412
	    $this->DoStroke($this->scale->ticks->maj_ticks_pos);
3413
	}
3414
    }
3415
 
3416
//--------------
3417
// Private methods
3418
    // Draw the grid
3419
    function DoStroke(&$aTicksPos) {
3420
	if( !$this->show )
3421
	    return;
3422
	$nbrgrids = count($aTicksPos);
3423
 
3424
	if( $this->scale->type=="y" ) {
3425
	    $xl=$this->img->left_margin;
3426
	    $xr=$this->img->width-$this->img->right_margin;
3427
 
3428
	    if( $this->fill ) {
3429
		// Draw filled areas
3430
		$y2 = $aTicksPos[0];
3431
		$i=1;
3432
		while( $i < $nbrgrids ) {
3433
		    $y1 = $y2;
3434
		    $y2 = $aTicksPos[$i++];
3435
		    $this->img->SetColor($this->fillcolor[$i & 1]);
3436
		    $this->img->FilledRectangle($xl,$y1,$xr,$y2);
3437
		}
3438
	    }
3439
 
3440
	    $this->img->SetColor($this->grid_color);
3441
	    $this->img->SetLineWeight($this->weight);
3442
 
3443
	    // Draw grid lines
3444
	    for($i=0; $i<$nbrgrids; ++$i) {
3445
		$y=$aTicksPos[$i];
3446
		if( $this->type == "solid" )
3447
		    $this->img->Line($xl,$y,$xr,$y);
3448
		elseif( $this->type == "dotted" )
3449
		    $this->img->DashedLine($xl,$y,$xr,$y,1,6);
3450
		elseif( $this->type == "dashed" )
3451
		    $this->img->DashedLine($xl,$y,$xr,$y,2,4);
3452
		elseif( $this->type == "longdashed" )
3453
		    $this->img->DashedLine($xl,$y,$xr,$y,8,6);
3454
	    }
3455
	}
3456
	elseif( $this->scale->type=="x" ) {
3457
	    $yu=$this->img->top_margin;
3458
	    $yl=$this->img->height-$this->img->bottom_margin;
3459
	    $limit=$this->img->width-$this->img->right_margin;
3460
 
3461
	    if( $this->fill ) {
3462
		// Draw filled areas
3463
		$x2 = $aTicksPos[0];
3464
		$i=1;
3465
		while( $i < $nbrgrids ) {
3466
		    $x1 = $x2;
3467
		    $x2 = min($aTicksPos[$i++],$limit) ;
3468
		    $this->img->SetColor($this->fillcolor[$i & 1]);
3469
		    $this->img->FilledRectangle($x1,$yu,$x2,$yl);
3470
		}
3471
	    }
3472
 
3473
	    $this->img->SetColor($this->grid_color);
3474
	    $this->img->SetLineWeight($this->weight);
3475
 
3476
	    // We must also test for limit since we might have
3477
	    // an offset and the number of ticks is calculated with
3478
	    // assumption offset==0 so we might end up drawing one
3479
	    // to many gridlines
3480
	    $i=0;
3481
	    $x=$aTicksPos[$i];
3482
	    while( $i<count($aTicksPos) && ($x=$aTicksPos[$i]) <= $limit ) {
3483
		if( $this->type == "solid" )
3484
		    $this->img->Line($x,$yl,$x,$yu);
3485
		elseif( $this->type == "dotted" )
3486
		    $this->img->DashedLine($x,$yl,$x,$yu,1,6);
3487
		elseif( $this->type == "dashed" )
3488
		    $this->img->DashedLine($x,$yl,$x,$yu,2,4);
3489
		elseif( $this->type == "longdashed" )
3490
		    $this->img->DashedLine($x,$yl,$x,$yu,8,6);
3491
		++$i;
3492
	    }
3493
	}
3494
	else {
3495
	    JpGraphError::RaiseL(25054,$this->scale->type);//('Internal error: Unknown grid axis ['.$this->scale->type.']');
3496
	}
3497
	return true;
3498
    }
3499
} // Class
3500
 
3501
//===================================================
3502
// CLASS Axis
3503
// Description: Defines X and Y axis. Notes that at the
3504
// moment the code is not really good since the axis on
3505
// several occasion must know wheter it's an X or Y axis.
3506
// This was a design decision to make the code easier to
3507
// follow.
3508
//===================================================
3509
class Axis {
3510
    var $pos = false;
3511
    var $weight=1;
3512
    var $color=array(0,0,0),$label_color=array(0,0,0);
3513
    var $img=null,$scale=null;
3514
    var $hide=false;
3515
    var $ticks_label=false, $ticks_label_colors=null;
3516
    var $show_first_label=true,$show_last_label=true;
3517
    var $label_step=1; // Used by a text axis to specify what multiple of major steps
3518
    // should be labeled.
3519
    var $tick_step=1;
3520
    var $labelPos=0;   // Which side of the axis should the labels be?
3521
    var $title=null,$title_adjust,$title_margin,$title_side=SIDE_LEFT;
3522
    var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=10,$label_angle=0;
3523
    var $tick_label_margin=7;
3524
    var $label_halign = '',$label_valign = '', $label_para_align='left';
3525
    var $hide_line=false,$hide_labels=false;
3526
    var $iDeltaAbsPos=0;
3527
    //var $hide_zero_label=false;
3528
 
3529
//---------------
3530
// CONSTRUCTOR
3531
    function Axis(&$img,&$aScale,$color=array(0,0,0)) {
3532
	$this->img = &$img;
3533
	$this->scale = &$aScale;
3534
	$this->color = $color;
3535
	$this->title=new Text("");
3536
 
3537
	if( $aScale->type=="y" ) {
3538
	    $this->title_margin = 25;
3539
	    $this->title_adjust="middle";
3540
	    $this->title->SetOrientation(90);
3541
	    $this->tick_label_margin=7;
3542
	    $this->labelPos=SIDE_LEFT;
3543
	    //$this->SetLabelFormat('%.1f');
3544
	}
3545
	else {
3546
	    $this->title_margin = 5;
3547
	    $this->title_adjust="high";
3548
	    $this->title->SetOrientation(0);
3549
	    $this->tick_label_margin=7;
3550
	    $this->labelPos=SIDE_DOWN;
3551
	    $this->title_side=SIDE_DOWN;
3552
	    //$this->SetLabelFormat('%.0f');
3553
	}
3554
    }
3555
//---------------
3556
// PUBLIC METHODS
3557
 
3558
    function SetLabelFormat($aFormStr) {
3559
	$this->scale->ticks->SetLabelFormat($aFormStr);
3560
    }
3561
 
3562
    function SetLabelFormatString($aFormStr,$aDate=false) {
3563
	$this->scale->ticks->SetLabelFormat($aFormStr,$aDate);
3564
    }
3565
 
3566
    function SetLabelFormatCallback($aFuncName) {
3567
	$this->scale->ticks->SetFormatCallback($aFuncName);
3568
    }
3569
 
3570
    function SetLabelAlign($aHAlign,$aVAlign="top",$aParagraphAlign='left') {
3571
	$this->label_halign = $aHAlign;
3572
	$this->label_valign = $aVAlign;
3573
	$this->label_para_align = $aParagraphAlign;
3574
    }
3575
 
3576
    // Don't display the first label
3577
    function HideFirstTickLabel($aShow=false) {
3578
	$this->show_first_label=$aShow;
3579
    }
3580
 
3581
    function HideLastTickLabel($aShow=false) {
3582
	$this->show_last_label=$aShow;
3583
    }
3584
 
3585
    // Manually specify the major and (optional) minor tick position and labels
3586
    function SetTickPositions($aMajPos,$aMinPos=NULL,$aLabels=NULL) {
3587
	$this->scale->ticks->SetTickPositions($aMajPos,$aMinPos,$aLabels);
3588
    }
3589
 
3590
    // Manually specify major tick positions and optional labels
3591
    function SetMajTickPositions($aMajPos,$aLabels=NULL) {
3592
	$this->scale->ticks->SetTickPositions($aMajPos,NULL,$aLabels);
3593
    }
3594
 
3595
    // Hide minor or major tick marks
3596
    function HideTicks($aHideMinor=true,$aHideMajor=true) {
3597
	$this->scale->ticks->SupressMinorTickMarks($aHideMinor);
3598
	$this->scale->ticks->SupressTickMarks($aHideMajor);
3599
    }
3600
 
3601
    // Hide zero label
3602
    function HideZeroLabel($aFlag=true) {
3603
	$this->scale->ticks->SupressZeroLabel();
3604
	//$this->hide_zero_label = $aFlag;
3605
    }
3606
 
3607
    function HideFirstLastLabel() {
3608
	// The two first calls to ticks method will supress
3609
	// automatically generated scale values. However, that
3610
	// will not affect manually specified value, e.g text-scales.
3611
	// therefor we also make a kludge here to supress manually
3612
	// specified scale labels.
3613
	$this->scale->ticks->SupressLast();
3614
	$this->scale->ticks->SupressFirst();
3615
	$this->show_first_label	= false;
3616
	$this->show_last_label = false;
3617
    }
3618
 
3619
    // Hide the axis
3620
    function Hide($aHide=true) {
3621
	$this->hide=$aHide;
3622
    }
3623
 
3624
    // Hide the actual axis-line, but still print the labels
3625
    function HideLine($aHide=true) {
3626
	$this->hide_line = $aHide;
3627
    }
3628
 
3629
    function HideLabels($aHide=true) {
3630
	$this->hide_labels = $aHide;
3631
    }
3632
 
3633
 
3634
    // Weight of axis
3635
    function SetWeight($aWeight) {
3636
	$this->weight = $aWeight;
3637
    }
3638
 
3639
    // Axis color
3640
    function SetColor($aColor,$aLabelColor=false) {
3641
	$this->color = $aColor;
3642
	if( !$aLabelColor ) $this->label_color = $aColor;
3643
	else $this->label_color = $aLabelColor;
3644
    }
3645
 
3646
    // Title on axis
3647
    function SetTitle($aTitle,$aAdjustAlign="high") {
3648
	$this->title->Set($aTitle);
3649
	$this->title_adjust=$aAdjustAlign;
3650
    }
3651
 
3652
    // Specify distance from the axis
3653
    function SetTitleMargin($aMargin) {
3654
	$this->title_margin=$aMargin;
3655
    }
3656
 
3657
    // Which side of the axis should the axis title be?
3658
    function SetTitleSide($aSideOfAxis) {
3659
	$this->title_side = $aSideOfAxis;
3660
    }
3661
 
3662
    // Utility function to set the direction for tick marks
3663
    function SetTickDirection($aDir) {
3664
    	// Will be deprecated from 1.7
3665
    	if( ERR_DEPRECATED )
3666
	    JpGraphError::RaiseL(25055);//('Axis::SetTickDirection() is deprecated. Use Axis::SetTickSide() instead');
3667
	$this->scale->ticks->SetSide($aDir);
3668
    }
3669
 
3670
    function SetTickSide($aDir) {
3671
	$this->scale->ticks->SetSide($aDir);
3672
    }
3673
 
3674
    // Specify text labels for the ticks. One label for each data point
3675
    function SetTickLabels($aLabelArray,$aLabelColorArray=null) {
3676
	$this->ticks_label = $aLabelArray;
3677
	$this->ticks_label_colors = $aLabelColorArray;
3678
    }
3679
 
3680
    // How far from the axis should the labels be drawn
3681
    function SetTickLabelMargin($aMargin) {
3682
	if( ERR_DEPRECATED )
3683
	    JpGraphError::RaiseL(25056);//('SetTickLabelMargin() is deprecated. Use Axis::SetLabelMargin() instead.');
3684
      	$this->tick_label_margin=$aMargin;
3685
    }
3686
 
3687
    function SetLabelMargin($aMargin) {
3688
	$this->tick_label_margin=$aMargin;
3689
    }
3690
 
3691
    // Specify that every $step of the ticks should be displayed starting
3692
    // at $start
3693
    // DEPRECATED FUNCTION: USE SetTextTickInterval() INSTEAD
3694
    function SetTextTicks($step,$start=0) {
3695
	JpGraphError::RaiseL(25057);//(" SetTextTicks() is deprecated. Use SetTextTickInterval() instead.");
3696
    }
3697
 
3698
    // Specify that every $step of the ticks should be displayed starting
3699
    // at $start
3700
    function SetTextTickInterval($aStep,$aStart=0) {
3701
	$this->scale->ticks->SetTextLabelStart($aStart);
3702
	$this->tick_step=$aStep;
3703
    }
3704
 
3705
    // Specify that every $step tick mark should have a label
3706
    // should be displayed starting
3707
    function SetTextLabelInterval($aStep,$aStart=0) {
3708
	if( $aStep < 1 )
3709
	    JpGraphError::RaiseL(25058);//(" Text label interval must be specified >= 1.");
3710
	$this->scale->ticks->SetTextLabelStart($aStart);
3711
	$this->label_step=$aStep;
3712
    }
3713
 
3714
    // Which side of the axis should the labels be on?
3715
    function SetLabelPos($aSidePos) {
3716
    	// This will be deprecated from 1.7
3717
	if( ERR_DEPRECATED )
3718
	    JpGraphError::RaiseL(25059);//('SetLabelPos() is deprecated. Use Axis::SetLabelSide() instead.');
3719
	$this->labelPos=$aSidePos;
3720
    }
3721
 
3722
    function SetLabelSide($aSidePos) {
3723
	$this->labelPos=$aSidePos;
3724
    }
3725
 
3726
    // Set the font
3727
    function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
3728
	$this->font_family = $aFamily;
3729
	$this->font_style = $aStyle;
3730
	$this->font_size = $aSize;
3731
    }
3732
 
3733
    // Position for axis line on the "other" scale
3734
    function SetPos($aPosOnOtherScale) {
3735
	$this->pos=$aPosOnOtherScale;
3736
    }
3737
 
3738
    // Set the position of the axis to be X-pixels delta to the right
3739
    // of the max X-position (used to position the multiple Y-axis)
3740
    function SetPosAbsDelta($aDelta) {
3741
      $this->iDeltaAbsPos=$aDelta;
3742
    }
3743
 
3744
    // Specify the angle for the tick labels
3745
    function SetLabelAngle($aAngle) {
3746
	$this->label_angle = $aAngle;
3747
    }
3748
 
3749
    // Stroke the axis.
3750
    function Stroke($aOtherAxisScale,$aStrokeLabels=true) {
3751
	if( $this->hide ) return;
3752
	if( is_numeric($this->pos) ) {
3753
	    $pos=$aOtherAxisScale->Translate($this->pos);
3754
	}
3755
	else {	// Default to minimum of other scale if pos not set
3756
	    if( ($aOtherAxisScale->GetMinVal() >= 0 && $this->pos==false) || $this->pos=="min" ) {
3757
		$pos = $aOtherAxisScale->scale_abs[0];
3758
	    }
3759
	    elseif($this->pos == "max") {
3760
		$pos = $aOtherAxisScale->scale_abs[1];
3761
	    }
3762
	    else { // If negative set x-axis at 0
3763
		$this->pos=0;
3764
		$pos=$aOtherAxisScale->Translate(0);
3765
	    }
3766
	}
3767
	$pos += $this->iDeltaAbsPos;
3768
	$this->img->SetLineWeight($this->weight);
3769
	$this->img->SetColor($this->color);
3770
	$this->img->SetFont($this->font_family,$this->font_style,$this->font_size);
3771
	if( $this->scale->type == "x" ) {
3772
	    if( !$this->hide_line )
3773
		$this->img->FilledRectangle($this->img->left_margin,$pos,
3774
					    $this->img->width-$this->img->right_margin,$pos+$this->weight-1);
3775
	    if( $this->title_side == SIDE_DOWN ) {
3776
		$y = $pos + $this->img->GetFontHeight() + $this->title_margin + $this->title->margin;
3777
		$yalign = 'top';
3778
	    }
3779
	    else {
3780
		$y = $pos - $this->img->GetFontHeight() - $this->title_margin - $this->title->margin;
3781
		$yalign = 'bottom';
3782
	    }
3783
 
3784
	    if( $this->title_adjust=="high" )
3785
		$this->title->Pos($this->img->width-$this->img->right_margin,$y,"right",$yalign);
3786
	    elseif( $this->title_adjust=="middle" || $this->title_adjust=="center" )
3787
		$this->title->Pos(($this->img->width-$this->img->left_margin-$this->img->right_margin)/2+$this->img->left_margin,$y,"center",$yalign);
3788
	    elseif($this->title_adjust=="low")
3789
		$this->title->Pos($this->img->left_margin,$y,"left",$yalign);
3790
	    else {
3791
		JpGraphError::RaiseL(25060,$this->title_adjust);//('Unknown alignment specified for X-axis title. ('.$this->title_adjust.')');
3792
	    }
3793
	}
3794
	elseif( $this->scale->type == "y" ) {
3795
	    // Add line weight to the height of the axis since
3796
	    // the x-axis could have a width>1 and we want the axis to fit nicely together.
3797
	    if( !$this->hide_line )
3798
		$this->img->FilledRectangle($pos-$this->weight+1,$this->img->top_margin,
3799
					    $pos,$this->img->height-$this->img->bottom_margin+$this->weight-1);
3800
	    $x=$pos ;
3801
	    if( $this->title_side == SIDE_LEFT ) {
3802
		$x -= $this->title_margin;
3803
		$x -= $this->title->margin;
3804
		$halign="right";
3805
	    }
3806
	    else {
3807
		$x += $this->title_margin;
3808
		$x += $this->title->margin;
3809
		$halign="left";
3810
	    }
3811
	    // If the user has manually specified an hor. align
3812
	    // then we override the automatic settings with this
3813
	    // specifed setting. Since default is 'left' we compare
3814
	    // with that. (This means a manually set 'left' align
3815
	    // will have no effect.)
3816
	    if( $this->title->halign != 'left' )
3817
		$halign = $this->title->halign;
3818
	    if( $this->title_adjust=="high" )
3819
		$this->title->Pos($x,$this->img->top_margin,$halign,"top");
3820
	    elseif($this->title_adjust=="middle" || $this->title_adjust=="center")
3821
		$this->title->Pos($x,($this->img->height-$this->img->top_margin-$this->img->bottom_margin)/2+$this->img->top_margin,$halign,"center");
3822
	    elseif($this->title_adjust=="low")
3823
		$this->title->Pos($x,$this->img->height-$this->img->bottom_margin,$halign,"bottom");
3824
	    else
3825
		JpGraphError::RaiseL(25061,$this->title_adjust);//('Unknown alignment specified for Y-axis title. ('.$this->title_adjust.')');
3826
 
3827
	}
3828
	$this->scale->ticks->Stroke($this->img,$this->scale,$pos);
3829
	if( $aStrokeLabels ) {
3830
	    if( !$this->hide_labels )
3831
		$this->StrokeLabels($pos);
3832
	    $this->title->Stroke($this->img);
3833
	}
3834
    }
3835
 
3836
//---------------
3837
// PRIVATE METHODS
3838
    // Draw all the tick labels on major tick marks
3839
    function StrokeLabels($aPos,$aMinor=false,$aAbsLabel=false) {
3840
 
3841
	$this->img->SetColor($this->label_color);
3842
	$this->img->SetFont($this->font_family,$this->font_style,$this->font_size);
3843
	$yoff=$this->img->GetFontHeight()/2;
3844
 
3845
	// Only draw labels at major tick marks
3846
	$nbr = count($this->scale->ticks->maj_ticks_label);
3847
 
3848
	// We have the option to not-display the very first mark
3849
	// (Usefull when the first label might interfere with another
3850
	// axis.)
3851
	$i = $this->show_first_label ? 0 : 1 ;
3852
	if( !$this->show_last_label ) --$nbr;
3853
	// Now run through all labels making sure we don't overshoot the end
3854
	// of the scale.
3855
	$ncolor=0;
3856
	if( isset($this->ticks_label_colors) )
3857
	    $ncolor=count($this->ticks_label_colors);
3858
 
3859
	while( $i<$nbr ) {
3860
	    // $tpos holds the absolute text position for the label
3861
	    $tpos=$this->scale->ticks->maj_ticklabels_pos[$i];
3862
 
3863
	    // Note. the $limit is only used for the x axis since we
3864
	    // might otherwise overshoot if the scale has been centered
3865
	    // This is due to us "loosing" the last tick mark if we center.
3866
	    if( $this->scale->type=="x" && $tpos > $this->img->width-$this->img->right_margin+1 ) {
3867
	    	return;
3868
	    }
3869
	    // we only draw every $label_step label
3870
	    if( ($i % $this->label_step)==0 ) {
3871
 
3872
		// Set specific label color if specified
3873
		if( $ncolor > 0 )
3874
		    $this->img->SetColor($this->ticks_label_colors[$i % $ncolor]);
3875
 
3876
		// If the label has been specified use that and in other case
3877
		// just label the mark with the actual scale value
3878
		$m=$this->scale->ticks->GetMajor();
3879
 
3880
		// ticks_label has an entry for each data point and is the array
3881
		// that holds the labels set by the user. If the user hasn't
3882
		// specified any values we use whats in the automatically asigned
3883
		// labels in the maj_ticks_label
3884
		if( isset($this->ticks_label[$i*$m]) )
3885
		    $label=$this->ticks_label[$i*$m];
3886
		else {
3887
		    if( $aAbsLabel )
3888
			$label=abs($this->scale->ticks->maj_ticks_label[$i]);
3889
		    else
3890
			$label=$this->scale->ticks->maj_ticks_label[$i];
3891
		    if( $this->scale->textscale && $this->scale->ticks->label_formfunc == '' ) {
3892
			++$label;
3893
		    }
3894
		}
3895
 
3896
		//if( $this->hide_zero_label && $label==0.0 ) {
3897
		//	++$i;
3898
		//	continue;
3899
		//}
3900
 
3901
		if( $this->scale->type == "x" ) {
3902
		    if( $this->labelPos == SIDE_DOWN ) {
3903
			if( $this->label_angle==0 || $this->label_angle==90 ) {
3904
			    if( $this->label_halign=='' && $this->label_valign=='')
3905
				$this->img->SetTextAlign('center','top');
3906
			    else
3907
			    	$this->img->SetTextAlign($this->label_halign,$this->label_valign);
3908
 
3909
			}
3910
			else {
3911
			    if( $this->label_halign=='' && $this->label_valign=='')
3912
				$this->img->SetTextAlign("right","top");
3913
			    else
3914
				$this->img->SetTextAlign($this->label_halign,$this->label_valign);
3915
			}
3916
 
3917
			$this->img->StrokeText($tpos,$aPos+$this->tick_label_margin+1,$label,
3918
					       $this->label_angle,$this->label_para_align);
3919
		    }
3920
		    else {
3921
			if( $this->label_angle==0 || $this->label_angle==90 ) {
3922
			    if( $this->label_halign=='' && $this->label_valign=='')
3923
				$this->img->SetTextAlign("center","bottom");
3924
			    else
3925
			    	$this->img->SetTextAlign($this->label_halign,$this->label_valign);
3926
			}
3927
			else {
3928
			    if( $this->label_halign=='' && $this->label_valign=='')
3929
				$this->img->SetTextAlign("right","bottom");
3930
			    else
3931
			    	$this->img->SetTextAlign($this->label_halign,$this->label_valign);
3932
			}
3933
			$this->img->StrokeText($tpos,$aPos-$this->tick_label_margin-1,$label,
3934
					       $this->label_angle,$this->label_para_align);
3935
		    }
3936
		}
3937
		else {
3938
		    // scale->type == "y"
3939
		    //if( $this->label_angle!=0 )
3940
		    //JpGraphError::Raise(" Labels at an angle are not supported on Y-axis");
3941
		    if( $this->labelPos == SIDE_LEFT ) { // To the left of y-axis
3942
			if( $this->label_halign=='' && $this->label_valign=='')
3943
			    $this->img->SetTextAlign("right","center");
3944
			else
3945
			    $this->img->SetTextAlign($this->label_halign,$this->label_valign);
3946
			$this->img->StrokeText($aPos-$this->tick_label_margin,$tpos,$label,$this->label_angle,$this->label_para_align);
3947
		    }
3948
		    else { // To the right of the y-axis
3949
			if( $this->label_halign=='' && $this->label_valign=='')
3950
			    $this->img->SetTextAlign("left","center");
3951
			else
3952
			    $this->img->SetTextAlign($this->label_halign,$this->label_valign);
3953
			$this->img->StrokeText($aPos+$this->tick_label_margin,$tpos,$label,$this->label_angle,$this->label_para_align);
3954
		    }
3955
		}
3956
	    }
3957
	    ++$i;
3958
	}
3959
    }
3960
 
3961
} // Class
3962
 
3963
//===================================================
3964
// CLASS Ticks
3965
// Description: Abstract base class for drawing linear and logarithmic
3966
// tick marks on axis
3967
//===================================================
3968
class Ticks {
3969
    var $minor_abs_size=3, $major_abs_size=5;
3970
    var $direction=1; // Should ticks be in(=1) the plot area or outside (=-1)?
3971
    var $scale;
3972
    var $is_set=false;
3973
    var $precision;
3974
    var $supress_zerolabel=false,$supress_first=false;
3975
    var $supress_last=false,$supress_tickmarks=false,$supress_minor_tickmarks=false;
3976
    var $mincolor="",$majcolor="";
3977
    var $weight=1;
3978
    var $label_formatstr='';   // C-style format string to use for labels
3979
    var $label_formfunc='';
3980
    var $label_dateformatstr='';
3981
    var $label_usedateformat=FALSE;
3982
 
3983
 
3984
//---------------
3985
// CONSTRUCTOR
3986
    function Ticks(&$aScale) {
3987
	$this->scale=&$aScale;
3988
	$this->precision = -1;
3989
    }
3990
 
3991
//---------------
3992
// PUBLIC METHODS
3993
    // Set format string for automatic labels
3994
    function SetLabelFormat($aFormatString,$aDate=FALSE) {
3995
	$this->label_formatstr=$aFormatString;
3996
	$this->label_usedateformat=$aDate;
3997
    }
3998
 
3999
    function SetLabelDateFormat($aFormatString) {
4000
	$this->label_dateformatstr=$aFormatString;
4001
    }
4002
 
4003
    function SetFormatCallback($aCallbackFuncName) {
4004
	$this->label_formfunc = $aCallbackFuncName;
4005
    }
4006
 
4007
    // Don't display the first zero label
4008
    function SupressZeroLabel($aFlag=true) {
4009
	$this->supress_zerolabel=$aFlag;
4010
    }
4011
 
4012
    // Don't display minor tick marks
4013
    function SupressMinorTickMarks($aHide=true) {
4014
	$this->supress_minor_tickmarks=$aHide;
4015
    }
4016
 
4017
    // Don't display major tick marks
4018
    function SupressTickMarks($aHide=true) {
4019
	$this->supress_tickmarks=$aHide;
4020
    }
4021
 
4022
    // Hide the first tick mark
4023
    function SupressFirst($aHide=true) {
4024
	$this->supress_first=$aHide;
4025
    }
4026
 
4027
    // Hide the last tick mark
4028
    function SupressLast($aHide=true) {
4029
	$this->supress_last=$aHide;
4030
    }
4031
 
4032
    // Size (in pixels) of minor tick marks
4033
    function GetMinTickAbsSize() {
4034
	return $this->minor_abs_size;
4035
    }
4036
 
4037
    // Size (in pixels) of major tick marks
4038
    function GetMajTickAbsSize() {
4039
	return $this->major_abs_size;
4040
    }
4041
 
4042
    function SetSize($aMajSize,$aMinSize=3) {
4043
	$this->major_abs_size = $aMajSize;
4044
	$this->minor_abs_size = $aMinSize;
4045
    }
4046
 
4047
    // Have the ticks been specified
4048
    function IsSpecified() {
4049
	return $this->is_set;
4050
    }
4051
 
4052
    // Set the distance between major and minor tick marks
4053
    function Set($aMaj,$aMin) {
4054
	// "Virtual method"
4055
	// Should be implemented by the concrete subclass
4056
	// if any action is wanted.
4057
    }
4058
 
4059
    // Specify number of decimals in automatic labels
4060
    // Deprecated from 1.4. Use SetFormatString() instead
4061
    function SetPrecision($aPrecision) {
4062
    	if( ERR_DEPRECATED )
4063
	    JpGraphError::RaiseL(25063);//('Ticks::SetPrecision() is deprecated. Use Ticks::SetLabelFormat() (or Ticks::SetFormatCallback()) instead');
4064
	$this->precision=$aPrecision;
4065
    }
4066
 
4067
    function SetSide($aSide) {
4068
	$this->direction=$aSide;
4069
    }
4070
 
4071
    // Which side of the axis should the ticks be on
4072
    function SetDirection($aSide=SIDE_RIGHT) {
4073
	$this->direction=$aSide;
4074
    }
4075
 
4076
    // Set colors for major and minor tick marks
4077
    function SetMarkColor($aMajorColor,$aMinorColor="") {
4078
	$this->SetColor($aMajorColor,$aMinorColor);
4079
    }
4080
 
4081
    function SetColor($aMajorColor,$aMinorColor="") {
4082
	$this->majcolor=$aMajorColor;
4083
 
4084
	// If not specified use same as major
4085
	if( $aMinorColor=="" )
4086
	    $this->mincolor=$aMajorColor;
4087
	else
4088
	    $this->mincolor=$aMinorColor;
4089
    }
4090
 
4091
    function SetWeight($aWeight) {
4092
	$this->weight=$aWeight;
4093
    }
4094
 
4095
} // Class
4096
 
4097
//===================================================
4098
// CLASS LinearTicks
4099
// Description: Draw linear ticks on axis
4100
//===================================================
4101
class LinearTicks extends Ticks {
4102
    var $minor_step=1, $major_step=2;
4103
    var $xlabel_offset=0,$xtick_offset=0;
4104
    var $label_offset=0; // What offset should the displayed label have
4105
    // i.e should we display 0,1,2 or 1,2,3,4 or 2,3,4 etc
4106
    var $text_label_start=0;
4107
    var $iManualTickPos = NULL, $iManualMinTickPos = NULL, $iManualTickLabels = NULL;
4108
    var $maj_ticks_pos = array(), $maj_ticklabels_pos = array(),
4109
	$ticks_pos = array(), $maj_ticks_label = array();
4110
    var $iAdjustForDST = false; // If a date falls within the DST period add one hour to the diaplyed time
4111
 
4112
//---------------
4113
// CONSTRUCTOR
4114
    function LinearTicks() {
4115
	$this->precision = -1;
4116
    }
4117
 
4118
//---------------
4119
// PUBLIC METHODS
4120
 
4121
 
4122
    // Return major step size in world coordinates
4123
    function GetMajor() {
4124
	return $this->major_step;
4125
    }
4126
 
4127
    // Return minor step size in world coordinates
4128
    function GetMinor() {
4129
	return $this->minor_step;
4130
    }
4131
 
4132
    // Set Minor and Major ticks (in world coordinates)
4133
    function Set($aMajStep,$aMinStep=false) {
4134
	if( $aMinStep==false )
4135
	    $aMinStep=$aMajStep;
4136
 
4137
	if( $aMajStep <= 0 || $aMinStep <= 0 ) {
4138
	    JpGraphError::RaiseL(25064);
4139
//(" 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.");
4140
	}
4141
 
4142
	$this->major_step=$aMajStep;
4143
	$this->minor_step=$aMinStep;
4144
	$this->is_set = true;
4145
    }
4146
 
4147
    function SetMajTickPositions($aMajPos,$aLabels=NULL) {
4148
	$this->SetTickPositions($aMajPos,NULL,$aLabels);
4149
    }
4150
 
4151
    function SetTickPositions($aMajPos,$aMinPos=NULL,$aLabels=NULL) {
4152
	if( !is_array($aMajPos) || ($aMinPos!==NULL && !is_array($aMinPos)) ) {
4153
	    JpGraphError::RaiseL(25065);//('Tick positions must be specifued as an array()');
4154
	    return;
4155
	}
4156
	$n=count($aMajPos);
4157
	if( is_array($aLabels) && (count($aLabels) != $n) ) {
4158
	    JpGraphError::RaiseL(25066);//('When manually specifying tick positions and labels the number of labels must be the same as the number of specified ticks.');
4159
	    return;
4160
	}
4161
	$this->iManualTickPos = $aMajPos;
4162
	$this->iManualMinTickPos = $aMinPos;
4163
	$this->iManualTickLabels = $aLabels;
4164
    }
4165
 
4166
    // Specify all the tick positions manually and possible also the exact labels
4167
    function _doManualTickPos($aScale) {
4168
	$n=count($this->iManualTickPos);
4169
	$m=count($this->iManualMinTickPos);
4170
	$doLbl=count($this->iManualTickLabels) > 0;
4171
 
4172
	$this->maj_ticks_pos = array();
4173
	$this->maj_ticklabels_pos = array();
4174
	$this->ticks_pos = array();
4175
 
4176
	// Now loop through the supplied positions and translate them to screen coordinates
4177
	// and store them in the maj_label_positions
4178
	$minScale = $aScale->scale[0];
4179
	$maxScale = $aScale->scale[1];
4180
	$j=0;
4181
	for($i=0; $i < $n ; ++$i ) {
4182
	    // First make sure that the first tick is not lower than the lower scale value
4183
	    if( !isset($this->iManualTickPos[$i])  ||
4184
		$this->iManualTickPos[$i] < $minScale  || $this->iManualTickPos[$i] > $maxScale) {
4185
		continue;
4186
	    }
4187
 
4188
 
4189
	    $this->maj_ticks_pos[$j] = $aScale->Translate($this->iManualTickPos[$i]);
4190
	    $this->maj_ticklabels_pos[$j] = $this->maj_ticks_pos[$j];
4191
 
4192
	    // Set the minor tick marks the same as major if not specified
4193
	    if( $m <= 0 ) {
4194
		$this->ticks_pos[$j] = $this->maj_ticks_pos[$j];
4195
	    }
4196
 
4197
	    if( $doLbl ) {
4198
		$this->maj_ticks_label[$j] = $this->iManualTickLabels[$i];
4199
	    }
4200
	    else {
4201
		$this->maj_ticks_label[$j]=$this->_doLabelFormat($this->iManualTickPos[$i],$i,$n);
4202
	    }
4203
	    ++$j;
4204
	}
4205
 
4206
	// Some sanity check
4207
	if( count($this->maj_ticks_pos) < 2 ) {
4208
	    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.');
4209
	}
4210
 
4211
	// Setup the minor tick marks
4212
	$j=0;
4213
	for($i=0; $i < $m; ++$i ) {
4214
	    if(  empty($this->iManualMinTickPos[$i]) ||
4215
		 $this->iManualMinTickPos[$i] < $minScale  || $this->iManualMinTickPos[$i] > $maxScale)
4216
		continue;
4217
	    $this->ticks_pos[$j] = $aScale->Translate($this->iManualMinTickPos[$i]);
4218
	    ++$j;
4219
	}
4220
    }
4221
 
4222
    function _doAutoTickPos($aScale) {
4223
	$maj_step_abs = $aScale->scale_factor*$this->major_step;
4224
	$min_step_abs = $aScale->scale_factor*$this->minor_step;
4225
 
4226
	if( $min_step_abs==0 || $maj_step_abs==0 ) {
4227
	    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')");
4228
	}
4229
	// We need to make this an int since comparing it below
4230
	// with the result from round() can give wrong result, such that
4231
	// (40 < 40) == TRUE !!!
4232
	$limit = (int)$aScale->scale_abs[1];
4233
 
4234
	if( $aScale->textscale ) {
4235
	    // This can only be true for a X-scale (horizontal)
4236
	    // Define ticks for a text scale. This is slightly different from a
4237
	    // normal linear type of scale since the position might be adjusted
4238
	    // and the labels start at on
4239
	    $label = (float)$aScale->GetMinVal()+$this->text_label_start+$this->label_offset;
4240
	    $start_abs=$aScale->scale_factor*$this->text_label_start;
4241
	    $nbrmajticks=round(($aScale->GetMaxVal()-$aScale->GetMinVal()-$this->text_label_start )/$this->major_step)+1;
4242
	    $x = $aScale->scale_abs[0]+$start_abs+$this->xlabel_offset*$min_step_abs;
4243
	    for( $i=0; $label <= $aScale->GetMaxVal()+$this->label_offset; ++$i ) {
4244
		// Apply format to label
4245
		$this->maj_ticks_label[$i]=$this->_doLabelFormat($label,$i,$nbrmajticks);
4246
		$label+=$this->major_step;
4247
 
4248
		// The x-position of the tick marks can be different from the labels.
4249
		// Note that we record the tick position (not the label) so that the grid
4250
		// happen upon tick marks and not labels.
4251
		$xtick=$aScale->scale_abs[0]+$start_abs+$this->xtick_offset*$min_step_abs+$i*$maj_step_abs;
4252
		$this->maj_ticks_pos[$i]=$xtick;
4253
		$this->maj_ticklabels_pos[$i] = round($x);
4254
		$x += $maj_step_abs;
4255
 
4256
	    }
4257
	}
4258
	else {
4259
	    $label = $aScale->GetMinVal();
4260
	    $abs_pos = $aScale->scale_abs[0];
4261
	    $j=0; $i=0;
4262
	    $step = round($maj_step_abs/$min_step_abs);
4263
	    if( $aScale->type == "x" ) {
4264
		// For a normal linear type of scale the major ticks will always be multiples
4265
		// of the minor ticks. In order to avoid any rounding issues the major ticks are
4266
		// defined as every "step" minor ticks and not calculated separately
4267
		$nbrmajticks=round(($aScale->GetMaxVal()-$aScale->GetMinVal()-$this->text_label_start )/$this->major_step)+1;
4268
		while( round($abs_pos) <= $limit ) {
4269
		    $this->ticks_pos[] = round($abs_pos);
4270
		    $this->ticks_label[] = $label;
4271
		    if( $step == 0 || $i % $step == 0 && $j < $nbrmajticks ) {
4272
			$this->maj_ticks_pos[$j] = round($abs_pos);
4273
			$this->maj_ticklabels_pos[$j] = round($abs_pos);
4274
			$this->maj_ticks_label[$j]=$this->_doLabelFormat($label,$j,$nbrmajticks);
4275
			++$j;
4276
		    }
4277
		    ++$i;
4278
		    $abs_pos += $min_step_abs;
4279
		    $label+=$this->minor_step;
4280
		}
4281
	    }
4282
	    elseif( $aScale->type == "y" ) {
4283
		$nbrmajticks=round(($aScale->GetMaxVal()-$aScale->GetMinVal())/$this->major_step)+1;
4284
		while( round($abs_pos) >= $limit ) {
4285
		    $this->ticks_pos[$i] = round($abs_pos);
4286
		    $this->ticks_label[$i]=$label;
4287
		    if( $step == 0 || $i % $step == 0 && $j < $nbrmajticks ) {
4288
			$this->maj_ticks_pos[$j] = round($abs_pos);
4289
			$this->maj_ticklabels_pos[$j] = round($abs_pos);
4290
			$this->maj_ticks_label[$j]=$this->_doLabelFormat($label,$j,$nbrmajticks);
4291
			++$j;
4292
		    }
4293
		    ++$i;
4294
		    $abs_pos += $min_step_abs;
4295
		    $label += $this->minor_step;
4296
		}
4297
	    }
4298
	}
4299
    }
4300
 
4301
    function AdjustForDST($aFlg=true) {
4302
	$this->iAdjustForDST = $aFlg;
4303
    }
4304
 
4305
 
4306
    function _doLabelFormat($aVal,$aIdx,$aNbrTicks) {
4307
 
4308
	// If precision hasn't been specified set it to a sensible value
4309
	if( $this->precision==-1 ) {
4310
	    $t = log10($this->minor_step);
4311
	    if( $t > 0 )
4312
		$precision = 0;
4313
	    else
4314
		$precision = -floor($t);
4315
	}
4316
	else
4317
	    $precision = $this->precision;
4318
 
4319
	if( $this->label_formfunc != '' ) {
4320
	    $f=$this->label_formfunc;
4321
	    $l = call_user_func($f,$aVal);
4322
	}
4323
	elseif( $this->label_formatstr != '' || $this->label_dateformatstr != '' ) {
4324
	    if( $this->label_usedateformat ) {
4325
		// Adjust the value to take daylight savings into account
4326
		if (date("I",$aVal)==1 && $this->iAdjustForDST ) // DST
4327
		    $aVal+=3600;
4328
		$l = date($this->label_formatstr,$aVal);
4329
		if( $this->label_formatstr == 'W' ) {
4330
		    // If we use week formatting then add a single 'w' in front of the
4331
		    // week number to differentiate it from dates
4332
		    $l = 'w'.$l;
4333
		}
4334
	    }
4335
	    else {
4336
		if( $this->label_dateformatstr !== '' ) {
4337
		    // Adjust the value to take daylight savings into account
4338
		    if (date("I",$aVal)==1 && $this->iAdjustForDST ) // DST
4339
			$aVal+=3600;
4340
		    $l = date($this->label_dateformatstr,$aVal);
4341
		    if( $this->label_formatstr == 'W' ) {
4342
			// If we use week formatting then add a single 'w' in front of the
4343
			// week number to differentiate it from dates
4344
			$l = 'w'.$l;
4345
		    }
4346
		}
4347
		else
4348
		    $l = sprintf($this->label_formatstr,$aVal);
4349
	    }
4350
	}
4351
	else {
4352
	    $l = sprintf('%01.'.$precision.'f',round($aVal,$precision));
4353
	}
4354
 
4355
	if( ($this->supress_zerolabel && $l==0) ||  ($this->supress_first && $aIdx==0) ||
4356
	    ($this->supress_last  && $aIdx==$aNbrTicks-1) ) {
4357
	    $l='';
4358
	}
4359
	return $l;
4360
    }
4361
 
4362
    // Stroke ticks on either X or Y axis
4363
    function _StrokeTicks(&$aImg,$aScale,$aPos) {
4364
	$hor = $aScale->type == 'x';
4365
	$aImg->SetLineWeight($this->weight);
4366
 
4367
	// We need to make this an int since comparing it below
4368
	// with the result from round() can give wrong result, such that
4369
	// (40 < 40) == TRUE !!!
4370
	$limit = (int)$aScale->scale_abs[1];
4371
 
4372
	// A text scale doesn't have any minor ticks
4373
	if( !$aScale->textscale ) {
4374
	    // Stroke minor ticks
4375
	    $yu = $aPos - $this->direction*$this->GetMinTickAbsSize();
4376
	    $xr = $aPos + $this->direction*$this->GetMinTickAbsSize();
4377
	    $n = count($this->ticks_pos);
4378
	    for($i=0; $i < $n; ++$i ) {
4379
		if( !$this->supress_tickmarks && !$this->supress_minor_tickmarks) {
4380
		    if( $this->mincolor!="" ) $aImg->PushColor($this->mincolor);
4381
		    if( $hor ) {
4382
			//if( $this->ticks_pos[$i] <= $limit )
4383
			$aImg->Line($this->ticks_pos[$i],$aPos,$this->ticks_pos[$i],$yu);
4384
		    }
4385
		    else {
4386
			//if( $this->ticks_pos[$i] >= $limit )
4387
			$aImg->Line($aPos,$this->ticks_pos[$i],$xr,$this->ticks_pos[$i]);
4388
		    }
4389
		    if( $this->mincolor!="" ) $aImg->PopColor();
4390
		}
4391
	    }
4392
	}
4393
 
4394
	// Stroke major ticks
4395
	$yu = $aPos - $this->direction*$this->GetMajTickAbsSize();
4396
	$xr = $aPos + $this->direction*$this->GetMajTickAbsSize();
4397
	$nbrmajticks=round(($aScale->GetMaxVal()-$aScale->GetMinVal()-$this->text_label_start )/$this->major_step)+1;
4398
	$n = count($this->maj_ticks_pos);
4399
	for($i=0; $i < $n ; ++$i ) {
4400
	    if(!($this->xtick_offset > 0 && $i==$nbrmajticks-1) && !$this->supress_tickmarks) {
4401
		if( $this->majcolor!="" ) $aImg->PushColor($this->majcolor);
4402
		if( $hor ) {
4403
		    //if( $this->maj_ticks_pos[$i] <= $limit )
4404
		    $aImg->Line($this->maj_ticks_pos[$i],$aPos,$this->maj_ticks_pos[$i],$yu);
4405
		}
4406
		else {
4407
		    //if( $this->maj_ticks_pos[$i] >= $limit )
4408
		    $aImg->Line($aPos,$this->maj_ticks_pos[$i],$xr,$this->maj_ticks_pos[$i]);
4409
		}
4410
		if( $this->majcolor!="" ) $aImg->PopColor();
4411
	    }
4412
	}
4413
 
4414
    }
4415
 
4416
    // Draw linear ticks
4417
    function Stroke(&$aImg,$aScale,$aPos) {
4418
	if( $this->iManualTickPos != NULL )
4419
	    $this->_doManualTickPos($aScale);
4420
	else
4421
	    $this->_doAutoTickPos($aScale);
4422
	$this->_StrokeTicks($aImg,$aScale,$aPos, $aScale->type == 'x' );
4423
    }
4424
 
4425
//---------------
4426
// PRIVATE METHODS
4427
    // Spoecify the offset of the displayed tick mark with the tick "space"
4428
    // Legal values for $o is [0,1] used to adjust where the tick marks and label
4429
    // should be positioned within the major tick-size
4430
    // $lo specifies the label offset and $to specifies the tick offset
4431
    // this comes in handy for example in bar graphs where we wont no offset for the
4432
    // tick but have the labels displayed halfway under the bars.
4433
    function SetXLabelOffset($aLabelOff,$aTickOff=-1) {
4434
	$this->xlabel_offset=$aLabelOff;
4435
	if( $aTickOff==-1 )	// Same as label offset
4436
	    $this->xtick_offset=$aLabelOff;
4437
	else
4438
	    $this->xtick_offset=$aTickOff;
4439
	if( $aLabelOff>0 )
4440
	    $this->SupressLast();	// The last tick wont fit
4441
    }
4442
 
4443
    // Which tick label should we start with?
4444
    function SetTextLabelStart($aTextLabelOff) {
4445
	$this->text_label_start=$aTextLabelOff;
4446
    }
4447
 
4448
} // Class
4449
 
4450
//===================================================
4451
// CLASS LinearScale
4452
// Description: Handle linear scaling between screen and world
4453
//===================================================
4454
class LinearScale {
4455
    var $scale=array(0,0);
4456
    var $scale_abs=array(0,0);
4457
    var $scale_factor; // Scale factor between world and screen
4458
    var $world_size;	// Plot area size in world coordinates
4459
    var $world_abs_size; // Plot area size in pixels
4460
    var $off; // Offset between image edge and plot area
4461
    var $type; // is this x or y scale ?
4462
    var $ticks=null; // Store ticks
4463
    var $text_scale_off = 0;
4464
    var $autoscale_min=false; // Forced minimum value, auto determine max
4465
    var $autoscale_max=false; // Forced maximum value, auto determine min
4466
    var $gracetop=0,$gracebottom=0;
4467
    var $intscale=false; // Restrict autoscale to integers
4468
    var $textscale=false; // Just a flag to let the Plot class find out if
4469
    // we are a textscale or not. This is a cludge since
4470
    // this ionformatyion is availabale in Graph::axtype but
4471
    // we don't have access to the graph object in the Plots
4472
    // stroke method. So we let graph store the status here
4473
    // when the linear scale is created. A real cludge...
4474
    var $auto_ticks=false; // When using manual scale should the ticks be automatically set?
4475
    var $name = 'lin';
4476
//---------------
4477
// CONSTRUCTOR
4478
    function LinearScale($aMin=0,$aMax=0,$aType="y") {
4479
	assert($aType=="x" || $aType=="y" );
4480
	assert($aMin<=$aMax);
4481
 
4482
	$this->type=$aType;
4483
	$this->scale=array($aMin,$aMax);
4484
	$this->world_size=$aMax-$aMin;
4485
	$this->ticks = new LinearTicks();
4486
    }
4487
 
4488
//---------------
4489
// PUBLIC METHODS
4490
    // Check if scale is set or if we should autoscale
4491
    // We should do this is either scale or ticks has not been set
4492
    function IsSpecified() {
4493
	if( $this->GetMinVal()==$this->GetMaxVal() ) {		// Scale not set
4494
	    return false;
4495
	}
4496
	return true;
4497
    }
4498
 
4499
    // Set the minimum data value when the autoscaling is used.
4500
    // Usefull if you want a fix minimum (like 0) but have an
4501
    // automatic maximum
4502
    function SetAutoMin($aMin) {
4503
	$this->autoscale_min=$aMin;
4504
    }
4505
 
4506
    // Set the minimum data value when the autoscaling is used.
4507
    // Usefull if you want a fix minimum (like 0) but have an
4508
    // automatic maximum
4509
    function SetAutoMax($aMax) {
4510
	$this->autoscale_max=$aMax;
4511
    }
4512
 
4513
    // If the user manually specifies a scale should the ticks
4514
    // still be set automatically?
4515
    function SetAutoTicks($aFlag=true) {
4516
	$this->auto_ticks = $aFlag;
4517
    }
4518
 
4519
    // Specify scale "grace" value (top and bottom)
4520
    function SetGrace($aGraceTop,$aGraceBottom=0) {
4521
	if( $aGraceTop<0 || $aGraceBottom < 0  )
4522
	    JpGraphError::RaiseL(25069);//(" Grace must be larger then 0");
4523
	$this->gracetop=$aGraceTop;
4524
	$this->gracebottom=$aGraceBottom;
4525
    }
4526
 
4527
    // Get the minimum value in the scale
4528
    function GetMinVal() {
4529
	return $this->scale[0];
4530
    }
4531
 
4532
    // get maximum value for scale
4533
    function GetMaxVal() {
4534
	return $this->scale[1];
4535
    }
4536
 
4537
    // Specify a new min/max value for sclae
4538
    function Update(&$aImg,$aMin,$aMax) {
4539
	$this->scale=array($aMin,$aMax);
4540
	$this->world_size=$aMax-$aMin;
4541
	$this->InitConstants($aImg);
4542
    }
4543
 
4544
    // Translate between world and screen
4545
    function Translate($aCoord) {
4546
	if( !is_numeric($aCoord) ) {
4547
	    if( $aCoord != '' && $aCoord != '-' && $aCoord != 'x' )
4548
		JpGraphError::RaiseL(25070);//('Your data contains non-numeric values.');
4549
	    return 0;
4550
	}
4551
	else {
4552
	    return $this->off + ($aCoord - $this->scale[0])*$this->scale_factor;
4553
	}
4554
    }
4555
 
4556
    // Relative translate (don't include offset) usefull when we just want
4557
    // to know the relative position (in pixels) on the axis
4558
    function RelTranslate($aCoord) {
4559
	if( !is_numeric($aCoord) ) {
4560
	    if( $aCoord != '' && $aCoord != '-' && $aCoord != 'x'  )
4561
		JpGraphError::RaiseL(25070);//('Your data contains non-numeric values.');
4562
	    return 0;
4563
	}
4564
	else {
4565
	    return ($aCoord - $this->scale[0]) * $this->scale_factor;
4566
	}
4567
    }
4568
 
4569
    // Restrict autoscaling to only use integers
4570
    function SetIntScale($aIntScale=true) {
4571
	$this->intscale=$aIntScale;
4572
    }
4573
 
4574
    // Calculate an integer autoscale
4575
    function IntAutoScale(&$img,$min,$max,$maxsteps,$majend=true) {
4576
	// Make sure limits are integers
4577
	$min=floor($min);
4578
	$max=ceil($max);
4579
	if( abs($min-$max)==0 ) {
4580
	    --$min; ++$max;
4581
	}
4582
	$maxsteps = floor($maxsteps);
4583
 
4584
	$gracetop=round(($this->gracetop/100.0)*abs($max-$min));
4585
	$gracebottom=round(($this->gracebottom/100.0)*abs($max-$min));
4586
	if( is_numeric($this->autoscale_min) ) {
4587
	    $min = ceil($this->autoscale_min);
4588
	    if( $min >= $max ) {
4589
		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.');
4590
	    }
4591
	}
4592
 
4593
	if( is_numeric($this->autoscale_max) ) {
4594
	    $max = ceil($this->autoscale_max);
4595
	    if( $min >= $max ) {
4596
		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.');
4597
	    }
4598
	}
4599
 
4600
	if( abs($min-$max ) == 0 ) {
4601
	    ++$max;
4602
	    --$min;
4603
	}
4604
 
4605
	$min -= $gracebottom;
4606
	$max += $gracetop;
4607
 
4608
	// First get tickmarks as multiples of 1, 10, ...
4609
	if( $majend ) {
4610
	    list($num1steps,$adj1min,$adj1max,$maj1step) =
4611
		$this->IntCalcTicks($maxsteps,$min,$max,1);
4612
	}
4613
	else {
4614
	    $adj1min = $min;
4615
	    $adj1max = $max;
4616
	    list($num1steps,$maj1step) =
4617
		$this->IntCalcTicksFreeze($maxsteps,$min,$max,1);
4618
	}
4619
 
4620
	if( abs($min-$max) > 2 ) {
4621
	    // Then get tick marks as 2:s 2, 20, ...
4622
	    if( $majend ) {
4623
		list($num2steps,$adj2min,$adj2max,$maj2step) =
4624
		    $this->IntCalcTicks($maxsteps,$min,$max,5);
4625
	    }
4626
	    else {
4627
		$adj2min = $min;
4628
		$adj2max = $max;
4629
		list($num2steps,$maj2step) =
4630
		    $this->IntCalcTicksFreeze($maxsteps,$min,$max,5);
4631
	    }
4632
	}
4633
	else {
4634
	    $num2steps = 10000;	// Dummy high value so we don't choose this
4635
	}
4636
 
4637
	if( abs($min-$max) > 5 ) {
4638
	    // Then get tickmarks as 5:s 5, 50, 500, ...
4639
	    if( $majend ) {
4640
		list($num5steps,$adj5min,$adj5max,$maj5step) =
4641
		    $this->IntCalcTicks($maxsteps,$min,$max,2);
4642
	    }
4643
	    else {
4644
		$adj5min = $min;
4645
		$adj5max = $max;
4646
		list($num5steps,$maj5step) =
4647
		    $this->IntCalcTicksFreeze($maxsteps,$min,$max,2);
4648
	    }
4649
	}
4650
	else {
4651
	    $num5steps = 10000;	// Dummy high value so we don't choose this
4652
	}
4653
 
4654
	// Check to see whichof 1:s, 2:s or 5:s fit better with
4655
	// the requested number of major ticks
4656
	$match1=abs($num1steps-$maxsteps);
4657
	$match2=abs($num2steps-$maxsteps);
4658
	if( !empty($maj5step) && $maj5step > 1 )
4659
	    $match5=abs($num5steps-$maxsteps);
4660
	else
4661
	    $match5=10000; 	// Dummy high value
4662
 
4663
	// Compare these three values and see which is the closest match
4664
	// We use a 0.6 weight to gravitate towards multiple of 5:s
4665
	if( $match1 < $match2 ) {
4666
	    if( $match1 < $match5 )
4667
		$r=1;
4668
	    else
4669
		$r=3;
4670
	}
4671
	else {
4672
	    if( $match2 < $match5 )
4673
		$r=2;
4674
	    else
4675
		$r=3;
4676
	}
4677
	// Minsteps are always the same as maxsteps for integer scale
4678
	switch( $r ) {
4679
	    case 1:
4680
		$this->ticks->Set($maj1step,$maj1step);
4681
		$this->Update($img,$adj1min,$adj1max);
4682
		break;
4683
	    case 2:
4684
		$this->ticks->Set($maj2step,$maj2step);
4685
		$this->Update($img,$adj2min,$adj2max);
4686
		break;
4687
	    case 3:
4688
		$this->ticks->Set($maj5step,$maj5step);
4689
		$this->Update($img,$adj5min,$adj5max);
4690
		break;
4691
	    default:
4692
		JpGraphError::RaiseL(25073,$r);//('Internal error. Integer scale algorithm comparison out of bound (r=$r)');
4693
	}
4694
    }
4695
 
4696
 
4697
    // Calculate autoscale. Used if user hasn't given a scale and ticks
4698
    // $maxsteps is the maximum number of major tickmarks allowed.
4699
    function AutoScale(&$img,$min,$max,$maxsteps,$majend=true) {
4700
	if( $this->intscale ) {
4701
	    $this->IntAutoScale($img,$min,$max,$maxsteps,$majend);
4702
	    return;
4703
	}
4704
	if( abs($min-$max) < 0.00001 ) {
4705
	    // We need some difference to be able to autoscale
4706
	    // make it 5% above and 5% below value
4707
	    if( $min==0 && $max==0 ) {		// Special case
4708
		$min=-1; $max=1;
4709
	    }
4710
	    else {
4711
		$delta = (abs($max)+abs($min))*0.005;
4712
		$min -= $delta;
4713
		$max += $delta;
4714
	    }
4715
	}
4716
 
4717
	$gracetop=($this->gracetop/100.0)*abs($max-$min);
4718
	$gracebottom=($this->gracebottom/100.0)*abs($max-$min);
4719
	if( is_numeric($this->autoscale_min) ) {
4720
	    $min = $this->autoscale_min;
4721
	    if( $min >= $max ) {
4722
		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.');
4723
	    }
4724
	    if( abs($min-$max ) < 0.00001 )
4725
		$max *= 1.2;
4726
	}
4727
 
4728
	if( is_numeric($this->autoscale_max) ) {
4729
	    $max = $this->autoscale_max;
4730
	    if( $min >= $max ) {
4731
		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.');
4732
	    }
4733
	    if( abs($min-$max ) < 0.00001 )
4734
		$min *= 0.8;
4735
	}
4736
 
4737
 
4738
	$min -= $gracebottom;
4739
	$max += $gracetop;
4740
 
4741
	// First get tickmarks as multiples of 0.1, 1, 10, ...
4742
	if( $majend ) {
4743
	    list($num1steps,$adj1min,$adj1max,$min1step,$maj1step) =
4744
		$this->CalcTicks($maxsteps,$min,$max,1,2);
4745
	}
4746
	else {
4747
	    $adj1min=$min;
4748
	    $adj1max=$max;
4749
	    list($num1steps,$min1step,$maj1step) =
4750
		$this->CalcTicksFreeze($maxsteps,$min,$max,1,2,false);
4751
	}
4752
 
4753
	// Then get tick marks as 2:s 0.2, 2, 20, ...
4754
	if( $majend ) {
4755
	    list($num2steps,$adj2min,$adj2max,$min2step,$maj2step) =
4756
		$this->CalcTicks($maxsteps,$min,$max,5,2);
4757
	}
4758
	else {
4759
	    $adj2min=$min;
4760
	    $adj2max=$max;
4761
	    list($num2steps,$min2step,$maj2step) =
4762
		$this->CalcTicksFreeze($maxsteps,$min,$max,5,2,false);
4763
	}
4764
 
4765
	// Then get tickmarks as 5:s 0.05, 0.5, 5, 50, ...
4766
	if( $majend ) {
4767
	    list($num5steps,$adj5min,$adj5max,$min5step,$maj5step) =
4768
		$this->CalcTicks($maxsteps,$min,$max,2,5);
4769
	}
4770
	else {
4771
	    $adj5min=$min;
4772
	    $adj5max=$max;
4773
	    list($num5steps,$min5step,$maj5step) =
4774
		$this->CalcTicksFreeze($maxsteps,$min,$max,2,5,false);
4775
	}
4776
 
4777
	// Check to see whichof 1:s, 2:s or 5:s fit better with
4778
	// the requested number of major ticks
4779
	$match1=abs($num1steps-$maxsteps);
4780
	$match2=abs($num2steps-$maxsteps);
4781
	$match5=abs($num5steps-$maxsteps);
4782
	// Compare these three values and see which is the closest match
4783
	// We use a 0.8 weight to gravitate towards multiple of 5:s
4784
	$r=$this->MatchMin3($match1,$match2,$match5,0.8);
4785
	switch( $r ) {
4786
	    case 1:
4787
		$this->Update($img,$adj1min,$adj1max);
4788
		$this->ticks->Set($maj1step,$min1step);
4789
		break;
4790
	    case 2:
4791
		$this->Update($img,$adj2min,$adj2max);
4792
		$this->ticks->Set($maj2step,$min2step);
4793
		break;
4794
	    case 3:
4795
		$this->Update($img,$adj5min,$adj5max);
4796
		$this->ticks->Set($maj5step,$min5step);
4797
		break;
4798
	}
4799
    }
4800
 
4801
//---------------
4802
// PRIVATE METHODS
4803
 
4804
    // This method recalculates all constants that are depending on the
4805
    // margins in the image. If the margins in the image are changed
4806
    // this method should be called for every scale that is registred with
4807
    // that image. Should really be installed as an observer of that image.
4808
    function InitConstants(&$img) {
4809
	if( $this->type=="x" ) {
4810
	    $this->world_abs_size=$img->width - $img->left_margin - $img->right_margin;
4811
	    $this->off=$img->left_margin;
4812
	    $this->scale_factor = 0;
4813
	    if( $this->world_size > 0 )
4814
		$this->scale_factor=$this->world_abs_size/($this->world_size*1.0);
4815
	}
4816
	else { // y scale
4817
	    $this->world_abs_size=$img->height - $img->top_margin - $img->bottom_margin;
4818
	    $this->off=$img->top_margin+$this->world_abs_size;
4819
	    $this->scale_factor = 0;
4820
	    if( $this->world_size > 0 ) {
4821
		$this->scale_factor=-$this->world_abs_size/($this->world_size*1.0);
4822
	    }
4823
	}
4824
	$size = $this->world_size * $this->scale_factor;
4825
	$this->scale_abs=array($this->off,$this->off + $size);
4826
    }
4827
 
4828
    // Initialize the conversion constants for this scale
4829
    // This tries to pre-calculate as much as possible to speed up the
4830
    // actual conversion (with Translate()) later on
4831
    // $start	=scale start in absolute pixels (for x-scale this is an y-position
4832
    //				 and for an y-scale this is an x-position
4833
    // $len 		=absolute length in pixels of scale
4834
    function SetConstants($aStart,$aLen) {
4835
	$this->world_abs_size=$aLen;
4836
	$this->off=$aStart;
4837
 
4838
	if( $this->world_size<=0 ) {
4839
	    // This should never ever happen !!
4840
	    JpGraphError::RaiseL(25074);
4841
//("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.");
4842
 
4843
	}
4844
 
4845
	// scale_factor = number of pixels per world unit
4846
	$this->scale_factor=$this->world_abs_size/($this->world_size*1.0);
4847
 
4848
	// scale_abs = start and end points of scale in absolute pixels
4849
	$this->scale_abs=array($this->off,$this->off+$this->world_size*$this->scale_factor);
4850
    }
4851
 
4852
 
4853
    // Calculate number of ticks steps with a specific division
4854
    // $a is the divisor of 10**x to generate the first maj tick intervall
4855
    // $a=1, $b=2 give major ticks with multiple of 10, ...,0.1,1,10,...
4856
    // $a=5, $b=2 give major ticks with multiple of 2:s ...,0.2,2,20,...
4857
    // $a=2, $b=5 give major ticks with multiple of 5:s ...,0.5,5,50,...
4858
    // We return a vector of
4859
    // 	[$numsteps,$adjmin,$adjmax,$minstep,$majstep]
4860
    // If $majend==true then the first and last marks on the axis will be major
4861
    // labeled tick marks otherwise it will be adjusted to the closest min tick mark
4862
    function CalcTicks($maxsteps,$min,$max,$a,$b,$majend=true) {
4863
	$diff=$max-$min;
4864
	if( $diff==0 )
4865
	    $ld=0;
4866
	else
4867
	    $ld=floor(log10($diff));
4868
 
4869
	// Gravitate min towards zero if we are close
4870
	if( $min>0 && $min < pow(10,$ld) ) $min=0;
4871
 
4872
	//$majstep=pow(10,$ld-1)/$a;
4873
	$majstep=pow(10,$ld)/$a;
4874
	$minstep=$majstep/$b;
4875
 
4876
	$adjmax=ceil($max/$minstep)*$minstep;
4877
	$adjmin=floor($min/$minstep)*$minstep;
4878
	$adjdiff = $adjmax-$adjmin;
4879
	$numsteps=$adjdiff/$majstep;
4880
 
4881
	while( $numsteps>$maxsteps ) {
4882
	    $majstep=pow(10,$ld)/$a;
4883
	    $numsteps=$adjdiff/$majstep;
4884
	    ++$ld;
4885
	}
4886
 
4887
	$minstep=$majstep/$b;
4888
	$adjmin=floor($min/$minstep)*$minstep;
4889
	$adjdiff = $adjmax-$adjmin;
4890
	if( $majend ) {
4891
	    $adjmin = floor($min/$majstep)*$majstep;
4892
	    $adjdiff = $adjmax-$adjmin;
4893
	    $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin;
4894
	}
4895
	else
4896
	    $adjmax=ceil($max/$minstep)*$minstep;
4897
 
4898
	return array($numsteps,$adjmin,$adjmax,$minstep,$majstep);
4899
    }
4900
 
4901
    function CalcTicksFreeze($maxsteps,$min,$max,$a,$b) {
4902
	// Same as CalcTicks but don't adjust min/max values
4903
	$diff=$max-$min;
4904
	if( $diff==0 )
4905
	    $ld=0;
4906
	else
4907
	    $ld=floor(log10($diff));
4908
 
4909
	//$majstep=pow(10,$ld-1)/$a;
4910
	$majstep=pow(10,$ld)/$a;
4911
	$minstep=$majstep/$b;
4912
	$numsteps=floor($diff/$majstep);
4913
 
4914
	while( $numsteps > $maxsteps ) {
4915
	    $majstep=pow(10,$ld)/$a;
4916
	    $numsteps=floor($diff/$majstep);
4917
	    ++$ld;
4918
	}
4919
	$minstep=$majstep/$b;
4920
	return array($numsteps,$minstep,$majstep);
4921
    }
4922
 
4923
 
4924
    function IntCalcTicks($maxsteps,$min,$max,$a,$majend=true) {
4925
	$diff=$max-$min;
4926
	if( $diff==0 )
4927
	    JpGraphError::RaiseL(25075);//('Can\'t automatically determine ticks since min==max.');
4928
	else
4929
	    $ld=floor(log10($diff));
4930
 
4931
	// Gravitate min towards zero if we are close
4932
	if( $min>0 && $min < pow(10,$ld) ) $min=0;
4933
 
4934
	if( $ld == 0 ) $ld=1;
4935
 
4936
	if( $a == 1 )
4937
	    $majstep = 1;
4938
	else
4939
	    $majstep=pow(10,$ld)/$a;
4940
	$adjmax=ceil($max/$majstep)*$majstep;
4941
 
4942
	$adjmin=floor($min/$majstep)*$majstep;
4943
	$adjdiff = $adjmax-$adjmin;
4944
	$numsteps=$adjdiff/$majstep;
4945
	while( $numsteps>$maxsteps ) {
4946
	    $majstep=pow(10,$ld)/$a;
4947
	    $numsteps=$adjdiff/$majstep;
4948
	    ++$ld;
4949
	}
4950
 
4951
	$adjmin=floor($min/$majstep)*$majstep;
4952
	$adjdiff = $adjmax-$adjmin;
4953
	if( $majend ) {
4954
	    $adjmin = floor($min/$majstep)*$majstep;
4955
	    $adjdiff = $adjmax-$adjmin;
4956
	    $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin;
4957
	}
4958
	else
4959
	    $adjmax=ceil($max/$majstep)*$majstep;
4960
 
4961
	return array($numsteps,$adjmin,$adjmax,$majstep);
4962
    }
4963
 
4964
 
4965
    function IntCalcTicksFreeze($maxsteps,$min,$max,$a) {
4966
	// Same as IntCalcTick but don't change min/max values
4967
	$diff=$max-$min;
4968
	if( $diff==0 )
4969
	    JpGraphError::RaiseL(25075);//('Can\'t automatically determine ticks since min==max.');
4970
	else
4971
	    $ld=floor(log10($diff));
4972
 
4973
	if( $ld == 0 ) $ld=1;
4974
 
4975
	if( $a == 1 )
4976
	    $majstep = 1;
4977
	else
4978
	    $majstep=pow(10,$ld)/$a;
4979
 
4980
	$numsteps=floor($diff/$majstep);
4981
	while( $numsteps > $maxsteps ) {
4982
	    $majstep=pow(10,$ld)/$a;
4983
	    $numsteps=floor($diff/$majstep);
4984
	    ++$ld;
4985
	}
4986
 
4987
	return array($numsteps,$majstep);
4988
    }
4989
 
4990
 
4991
 
4992
    // Determine the minimum of three values witha  weight for last value
4993
    function MatchMin3($a,$b,$c,$weight) {
4994
	if( $a < $b ) {
4995
	    if( $a < ($c*$weight) )
4996
		return 1; // $a smallest
4997
	    else
4998
		return 3; // $c smallest
4999
	}
5000
	elseif( $b < ($c*$weight) )
5001
	    return 2; // $b smallest
5002
	return 3; // $c smallest
5003
    }
5004
} // Class
5005
//===================================================
5006
// CLASS ImgStreamCache
5007
// Description: Handle caching of graphs to files
5008
//===================================================
5009
class ImgStreamCache {
5010
    var $cache_dir;
5011
    var $img=null;
5012
    var $timeout=0; 	// Infinite timeout
5013
    //---------------
5014
    // CONSTRUCTOR
5015
    function ImgStreamCache(&$aImg, $aCacheDir=CACHE_DIR) {
5016
	$this->img = &$aImg;
5017
	$this->cache_dir = $aCacheDir;
5018
    }
5019
 
5020
//---------------
5021
// PUBLIC METHODS
5022
 
5023
    // Specify a timeout (in minutes) for the file. If the file is older then the
5024
    // timeout value it will be overwritten with a newer version.
5025
    // If timeout is set to 0 this is the same as infinite large timeout and if
5026
    // timeout is set to -1 this is the same as infinite small timeout
5027
    function SetTimeout($aTimeout) {
5028
	$this->timeout=$aTimeout;
5029
    }
5030
 
5031
    // Output image to browser and also write it to the cache
5032
    function PutAndStream(&$aImage,$aCacheFileName,$aInline,$aStrokeFileName) {
5033
	// Some debugging code to brand the image with numbe of colors
5034
	// used
5035
	GLOBAL $gJpgBrandTiming;
5036
 
5037
	if( $gJpgBrandTiming ) {
5038
	    global $tim;
5039
	    $t=$tim->Pop()/1000.0;
5040
	    $c=$aImage->SetColor("black");
5041
	    $t=sprintf(BRAND_TIME_FORMAT,round($t,3));
5042
	    imagestring($this->img->img,2,5,$this->img->height-20,$t,$c);
5043
	}
5044
 
5045
	// Check if we should stroke the image to an arbitrary file
5046
	if( _FORCE_IMGTOFILE ) {
5047
	    $aStrokeFileName = _FORCE_IMGDIR.GenImgName();
5048
	}
5049
 
5050
	if( $aStrokeFileName!="" ) {
5051
	    if( $aStrokeFileName == "auto" )
5052
		$aStrokeFileName = GenImgName();
5053
	    if( file_exists($aStrokeFileName) ) {
5054
		// Delete the old file
5055
		if( !@unlink($aStrokeFileName) )
5056
		    JpGraphError::RaiseL(25111,$aStrokeFileName);//(" Can't delete cached image $aStrokeFileName. Permission problem?");
5057
	    }
5058
	    $aImage->Stream($aStrokeFileName);
5059
	    return;
5060
	}
5061
 
5062
	if( $aCacheFileName != "" && USE_CACHE) {
5063
 
5064
	    $aCacheFileName = $this->cache_dir . $aCacheFileName;
5065
	    if( file_exists($aCacheFileName) ) {
5066
		if( !$aInline ) {
5067
		    // If we are generating image off-line (just writing to the cache)
5068
		    // and the file exists and is still valid (no timeout)
5069
		    // then do nothing, just return.
5070
		    $diff=time()-filemtime($aCacheFileName);
5071
		    if( $diff < 0 )
5072
			JpGraphError::RaiseL(25112,$aCacheFileName);//(" Cached imagefile ($aCacheFileName) has file date in the future!!");
5073
		    if( $this->timeout>0 && ($diff <= $this->timeout*60) )
5074
			return;
5075
		}
5076
		if( !@unlink($aCacheFileName) )
5077
		    JpGraphError::RaiseL(25113,$aStrokeFileName);//(" Can't delete cached image $aStrokeFileName. Permission problem?");
5078
		$aImage->Stream($aCacheFileName);
5079
	    }
5080
	    else {
5081
		$this->MakeDirs(dirname($aCacheFileName));
5082
		if( !is_writeable(dirname($aCacheFileName)) ) {
5083
		    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.');
5084
		}
5085
		$aImage->Stream($aCacheFileName);
5086
	    }
5087
 
5088
	    $res=true;
5089
	    // Set group to specified
5090
	    if( CACHE_FILE_GROUP != "" )
5091
		$res = @chgrp($aCacheFileName,CACHE_FILE_GROUP);
5092
	    if( CACHE_FILE_MOD != "" )
5093
		$res = @chmod($aCacheFileName,CACHE_FILE_MOD);
5094
	    if( !$res )
5095
		JpGraphError::RaiseL(25115,$aStrokeFileName);//(" Can't set permission for cached image $aStrokeFileName. Permission problem?");
5096
 
5097
	    $aImage->Destroy();
5098
	    if( $aInline ) {
5099
		if ($fh = @fopen($aCacheFileName, "rb") ) {
5100
		    $this->img->Headers();
5101
		    fpassthru($fh);
5102
		    return;
5103
		}
5104
		else
5105
		    JpGraphError::RaiseL(25116,$aFile);//(" Cant open file from cache [$aFile]");
5106
	    }
5107
	}
5108
	elseif( $aInline ) {
5109
	    $this->img->Headers();
5110
	    $aImage->Stream();
5111
	    return;
5112
	}
5113
    }
5114
 
5115
    // Check if a given image is in cache and in that case
5116
    // pass it directly on to web browser. Return false if the
5117
    // image file doesn't exist or exists but is to old
5118
    function GetAndStream($aCacheFileName) {
5119
	$aCacheFileName = $this->cache_dir.$aCacheFileName;
5120
	if ( USE_CACHE && file_exists($aCacheFileName) && $this->timeout>=0 ) {
5121
	    $diff=time()-filemtime($aCacheFileName);
5122
	    if( $this->timeout>0 && ($diff > $this->timeout*60) ) {
5123
		return false;
5124
	    }
5125
	    else {
5126
		if ($fh = @fopen($aCacheFileName, "rb")) {
5127
		    $this->img->Headers();
5128
		    fpassthru($fh);
5129
		    return true;
5130
		}
5131
		else
5132
		    JpGraphError::RaiseL(25117,$aCacheFileName);//(" Can't open cached image \"$aCacheFileName\" for reading.");
5133
	    }
5134
	}
5135
	return false;
5136
    }
5137
 
5138
    //---------------
5139
    // PRIVATE METHODS
5140
    // Create all necessary directories in a path
5141
    function MakeDirs($aFile) {
5142
	$dirs = array();
5143
	while ( !(file_exists($aFile)) ) {
5144
	    $dirs[] = $aFile;
5145
	    $aFile = dirname($aFile);
5146
	}
5147
	for ($i = sizeof($dirs)-1; $i>=0; $i--) {
5148
	    if(! @mkdir($dirs[$i],0777) )
5149
		JpGraphError::RaiseL(25118,$aFile);//(" Can't create directory $aFile. Make sure PHP has write permission to this directory.");
5150
	    // We also specify mode here after we have changed group.
5151
	    // This is necessary if Apache user doesn't belong the
5152
	    // default group and hence can't specify group permission
5153
	    // in the previous mkdir() call
5154
	    if( CACHE_FILE_GROUP != "" ) {
5155
		$res=true;
5156
		$res =@chgrp($dirs[$i],CACHE_FILE_GROUP);
5157
		$res &= @chmod($dirs[$i],0777);
5158
		if( !$res )
5159
		    JpGraphError::RaiseL(25119,$aFile);//(" Can't set permissions for $aFile. Permission problems?");
5160
	    }
5161
	}
5162
	return true;
5163
    }
5164
} // CLASS Cache
5165
 
5166
//===================================================
5167
// CLASS Legend
5168
// Description: Responsible for drawing the box containing
5169
// all the legend text for the graph
5170
//===================================================
5171
DEFINE('_DEFAULT_LPM_SIZE',8);
5172
class Legend {
5173
    var $color=array(0,0,0); // Default fram color
5174
    var $fill_color=array(235,235,235); // Default fill color
5175
    var $shadow=true; // Shadow around legend "box"
5176
    var $shadow_color='gray';
5177
    var $txtcol=array();
5178
    var $mark_abs_hsize=_DEFAULT_LPM_SIZE, $mark_abs_vsize=_DEFAULT_LPM_SIZE;
5179
    var $xmargin=10,$ymargin=3,$shadow_width=2;
5180
    var $xlmargin=2, $ylmargin='';
5181
    var $xpos=0.05, $ypos=0.15, $xabspos=-1, $yabspos=-1;
5182
	var $halign="right", $valign="top";
5183
    var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=10;
5184
    var $font_color='black';
5185
    var $hide=false,$layout_n=1;
5186
    var $weight=1,$frameweight=1;
5187
    var $csimareas='';
5188
    var $reverse = false ;
5189
//---------------
5190
// CONSTRUCTOR
5191
    function Legend() {
5192
	// Empty
5193
    }
5194
//---------------
5195
// PUBLIC METHODS
5196
    function Hide($aHide=true) {
5197
	$this->hide=$aHide;
5198
    }
5199
 
5200
    function SetHColMargin($aXMarg) {
5201
	$this->xmargin = $aXMarg;
5202
    }
5203
 
5204
    function SetVColMargin($aSpacing) {
5205
	$this->ymargin = $aSpacing ;
5206
    }
5207
 
5208
    function SetLeftMargin($aXMarg) {
5209
	$this->xlmargin = $aXMarg;
5210
    }
5211
 
5212
    // Synonym
5213
    function SetLineSpacing($aSpacing) {
5214
	$this->ymargin = $aSpacing ;
5215
    }
5216
 
5217
    function SetShadow($aShow='gray',$aWidth=2) {
5218
	if( is_string($aShow) ) {
5219
	    $this->shadow_color = $aShow;
5220
	    $this->shadow=true;
5221
	}
5222
	else
5223
	    $this->shadow=$aShow;
5224
	$this->shadow_width=$aWidth;
5225
    }
5226
 
5227
    function SetMarkAbsSize($aSize) {
5228
	$this->mark_abs_vsize = $aSize ;
5229
	$this->mark_abs_hsize = $aSize ;
5230
    }
5231
 
5232
    function SetMarkAbsVSize($aSize) {
5233
	$this->mark_abs_vsize = $aSize ;
5234
    }
5235
 
5236
    function SetMarkAbsHSize($aSize) {
5237
	$this->mark_abs_hsize = $aSize ;
5238
    }
5239
 
5240
    function SetLineWeight($aWeight) {
5241
	$this->weight = $aWeight;
5242
    }
5243
 
5244
    function SetFrameWeight($aWeight) {
5245
	$this->frameweight = $aWeight;
5246
    }
5247
 
5248
    function SetLayout($aDirection=LEGEND_VERT) {
5249
	$this->layout_n = $aDirection==LEGEND_VERT ? 1 : 99 ;
5250
    }
5251
 
5252
    function SetColumns($aCols) {
5253
	$this->layout_n = $aCols ;
5254
    }
5255
 
5256
    function SetReverse($f=true) {
5257
	$this->reverse = $f ;
5258
    }
5259
 
5260
    // Set color on frame around box
5261
    function SetColor($aFontColor,$aColor='black') {
5262
	$this->font_color=$aFontColor;
5263
	$this->color=$aColor;
5264
    }
5265
 
5266
    function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
5267
	$this->font_family = $aFamily;
5268
	$this->font_style = $aStyle;
5269
	$this->font_size = $aSize;
5270
    }
5271
 
5272
    function SetPos($aX,$aY,$aHAlign="right",$aVAlign="top") {
5273
	$this->Pos($aX,$aY,$aHAlign,$aVAlign);
5274
    }
5275
 
5276
    function SetAbsPos($aX,$aY,$aHAlign="right",$aVAlign="top") {
5277
	$this->xabspos=$aX;
5278
	$this->yabspos=$aY;
5279
	$this->halign=$aHAlign;
5280
	$this->valign=$aVAlign;
5281
    }
5282
 
5283
 
5284
    function Pos($aX,$aY,$aHAlign="right",$aVAlign="top") {
5285
	if( !($aX<1 && $aY<1) )
5286
	    JpGraphError::RaiseL(25120);//(" Position for legend must be given as percentage in range 0-1");
5287
	$this->xpos=$aX;
5288
	$this->ypos=$aY;
5289
	$this->halign=$aHAlign;
5290
	$this->valign=$aVAlign;
5291
    }
5292
 
5293
    function SetFillColor($aColor) {
5294
	$this->fill_color=$aColor;
5295
    }
5296
 
5297
    function Add($aTxt,$aColor,$aPlotmark="",$aLinestyle=0,$csimtarget='',$csimalt='',$csimwintarget='') {
5298
	$this->txtcol[]=array($aTxt,$aColor,$aPlotmark,$aLinestyle,$csimtarget,$csimalt,$csimwintarget);
5299
    }
5300
 
5301
    function GetCSIMAreas() {
5302
	return $this->csimareas;
5303
    }
5304
 
5305
    function Stroke(&$aImg) {
5306
	// Constant
5307
	$fillBoxFrameWeight=1;
5308
 
5309
	if( $this->hide ) return;
5310
 
5311
	$aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
5312
 
5313
	if( $this->reverse ) {
5314
	    $this->txtcol = array_reverse($this->txtcol);
5315
	}
5316
 
5317
	$n=count($this->txtcol);
5318
	if( $n == 0 ) return;
5319
 
5320
	// Find out the max width and height of each column to be able
5321
        // to size the legend box.
5322
	$numcolumns = ($n > $this->layout_n ? $this->layout_n : $n);
5323
	for( $i=0; $i < $numcolumns; ++$i ) {
5324
	    $colwidth[$i] = $aImg->GetTextWidth($this->txtcol[$i][0]) +
5325
		            2*$this->xmargin + 2*$this->mark_abs_hsize;
5326
	    $colheight[$i] = 0;
5327
	}
5328
 
5329
	// Find our maximum height in each row
5330
	$rows = 0 ; $rowheight[0] = 0;
5331
	for( $i=0; $i < $n; ++$i ) {
5332
	    $h = max($this->mark_abs_vsize,$aImg->GetTextHeight($this->txtcol[$i][0]))+$this->ymargin;
5333
	    if( $i % $numcolumns == 0 ) {
5334
		$rows++;
5335
		$rowheight[$rows-1] = 0;
5336
	    }
5337
	    $rowheight[$rows-1] = max($rowheight[$rows-1],$h);
5338
	}
5339
 
5340
	$abs_height = 0;
5341
	for( $i=0; $i < $rows; ++$i ) {
5342
	    $abs_height += $rowheight[$i] ;
5343
	}
5344
 
5345
	// Make sure that the height is at least as high as mark size + ymargin
5346
	$abs_height = max($abs_height,$this->mark_abs_vsize);
5347
 
5348
	// We add 3 extra pixels height to compensate for the difficult in
5349
	// calculating font height
5350
	$abs_height += $this->ymargin+3;
5351
 
5352
	// Find out the maximum width in each column
5353
	for( $i=$numcolumns; $i < $n; ++$i ) {
5354
	    $colwidth[$i % $numcolumns] = max(
5355
		$aImg->GetTextWidth($this->txtcol[$i][0])+2*$this->xmargin+2*$this->mark_abs_hsize,$colwidth[$i % $numcolumns]);
5356
	}
5357
 
5358
	// Get the total width
5359
	$mtw = 0;
5360
	for( $i=0; $i < $numcolumns; ++$i ) {
5361
	    $mtw += $colwidth[$i] ;
5362
	}
5363
 
5364
	// Find out maximum width we need for legend box
5365
	$abs_width = $mtw+$this->xlmargin;
5366
 
5367
	if( $this->xabspos === -1  && $this->yabspos === -1 ) {
5368
	    $this->xabspos = $this->xpos*$aImg->width ;
5369
	    $this->yabspos = $this->ypos*$aImg->height ;
5370
	}
5371
 
5372
	// Positioning of the legend box
5373
	if( $this->halign=="left" )
5374
	    $xp = $this->xabspos;
5375
	elseif( $this->halign=="center" )
5376
	    $xp = $this->xabspos - $abs_width/2;
5377
	else
5378
	    $xp = $aImg->width - $this->xabspos - $abs_width;
5379
 
5380
	$yp=$this->yabspos;
5381
	if( $this->valign=="center" )
5382
	    $yp-=$abs_height/2;
5383
	elseif( $this->valign=="bottom" )
5384
	    $yp-=$abs_height;
5385
 
5386
	// Stroke legend box
5387
	$aImg->SetColor($this->color);
5388
	$aImg->SetLineWeight($this->frameweight);
5389
	$aImg->SetLineStyle('solid');
5390
 
5391
	if( $this->shadow )
5392
	    $aImg->ShadowRectangle($xp,$yp,$xp+$abs_width+$this->shadow_width,
5393
				   $yp+$abs_height+$this->shadow_width,
5394
				   $this->fill_color,$this->shadow_width,$this->shadow_color);
5395
	else {
5396
	    $aImg->SetColor($this->fill_color);
5397
	    $aImg->FilledRectangle($xp,$yp,$xp+$abs_width,$yp+$abs_height);
5398
	    $aImg->SetColor($this->color);
5399
	    $aImg->Rectangle($xp,$yp,$xp+$abs_width,$yp+$abs_height);
5400
	}
5401
 
5402
	// x1,y1 is the position for the legend mark
5403
	$x1=$xp+$this->mark_abs_hsize+$this->xlmargin;
5404
	$y1=$yp + $this->ymargin;
5405
 
5406
	$f2 =  round($aImg->GetTextHeight('X')/2);
5407
 
5408
	$grad = new Gradient($aImg);
5409
	$patternFactory = null;
5410
 
5411
	// Now stroke each legend in turn
5412
	// Each plot has added the following information to  the legend
5413
	// p[0] = Legend text
5414
	// p[1] = Color,
5415
	// p[2] = For markers a reference to the PlotMark object
5416
	// p[3] = For lines the line style, for gradient the negative gradient style
5417
	// p[4] = CSIM target
5418
	// p[5] = CSIM Alt text
5419
	$i = 1 ; $row = 0;
5420
	foreach($this->txtcol as $p) {
5421
 
5422
	    // STROKE DEBUG BOX
5423
	    if( _JPG_DEBUG ) {
5424
	        $aImg->SetLineWeight(1);
5425
	        $aImg->SetColor('red');
5426
	        $aImg->SetLineStyle('solid');
5427
	        $aImg->Rectangle($xp,$y1,$xp+$abs_width,$y1+$rowheight[$row]);
5428
	    }
5429
 
5430
	    $aImg->SetLineWeight($this->weight);
5431
	    $x1 = round($x1); $y1=round($y1);
5432
	    if ( $p[2] && $p[2]->GetType() > -1 ) {
5433
		// Make a plot mark legend
5434
		$aImg->SetColor($p[1]);
5435
		if( is_string($p[3]) || $p[3]>0 ) {
5436
		    $aImg->SetLineStyle($p[3]);
5437
		    $aImg->StyleLine($x1-$this->mark_abs_hsize,$y1+$f2,$x1+$this->mark_abs_hsize,$y1+$f2);
5438
		}
5439
		// Stroke a mark with the standard size
5440
		// (As long as it is not an image mark )
5441
		if( $p[2]->GetType() != MARK_IMG ) {
5442
 
5443
		    // Clear any user callbacks since we ont want them called for
5444
		    // the legend marks
5445
		    $p[2]->iFormatCallback = '';
5446
		    $p[2]->iFormatCallback2 = '';
5447
 
5448
		    // Since size for circles is specified as the radius
5449
		    // this means that we must half the size to make the total
5450
		    // width behave as the other marks
5451
		    if( $p[2]->GetType() == MARK_FILLEDCIRCLE || $p[2]->GetType() == MARK_CIRCLE ) {
5452
		        $p[2]->SetSize(min($this->mark_abs_vsize,$this->mark_abs_hsize)/2);
5453
			$p[2]->Stroke($aImg,$x1,$y1+$f2);
5454
		    }
5455
		    else {
5456
		        $p[2]->SetSize(min($this->mark_abs_vsize,$this->mark_abs_hsize));
5457
			$p[2]->Stroke($aImg,$x1,$y1+$f2);
5458
		    }
5459
		}
5460
	    }
5461
	    elseif ( $p[2] && (is_string($p[3]) || $p[3]>0 ) ) {
5462
		// Draw a styled line
5463
		$aImg->SetColor($p[1]);
5464
		$aImg->SetLineStyle($p[3]);
5465
		$aImg->StyleLine($x1-1,$y1+$f2,$x1+$this->mark_abs_hsize,$y1+$f2);
5466
		$aImg->StyleLine($x1-1,$y1+$f2+1,$x1+$this->mark_abs_hsize,$y1+$f2+1);
5467
	    }
5468
	    else {
5469
		// Draw a colored box
5470
		$color = $p[1] ;
5471
		// We make boxes slightly larger to better show
5472
		$boxsize = min($this->mark_abs_vsize,$this->mark_abs_hsize) + 2 ;
5473
		$ym =  round($y1 + $f2 - $boxsize/2);
5474
		// We either need to plot a gradient or a
5475
		// pattern. To differentiate we use a kludge.
5476
		// Patterns have a p[3] value of < -100
5477
		if( $p[3] < -100 ) {
5478
		    // p[1][0] == iPattern, p[1][1] == iPatternColor, p[1][2] == iPatternDensity
5479
		    if( $patternFactory == null ) {
5480
			$patternFactory = new RectPatternFactory();
5481
		    }
5482
		    $prect = $patternFactory->Create($p[1][0],$p[1][1],1);
5483
		    $prect->SetBackground($p[1][3]);
5484
		    $prect->SetDensity($p[1][2]+1);
5485
		    $prect->SetPos(new Rectangle($x1,$ym,$boxsize,$boxsize));
5486
		    $prect->Stroke($aImg);
5487
		    $prect=null;
5488
		}
5489
		else {
5490
		    if( is_array($color) && count($color)==2 ) {
5491
			// The client want a gradient color
5492
			$grad->FilledRectangle($x1,$ym,
5493
					       $x1+$boxsize,$ym+$boxsize,
5494
					       $color[0],$color[1],-$p[3]);
5495
		    }
5496
		    else {
5497
			$aImg->SetColor($p[1]);
5498
			$aImg->FilledRectangle($x1,$ym,$x1+$boxsize,$ym+$boxsize);
5499
		    }
5500
		    $aImg->SetColor($this->color);
5501
		    $aImg->SetLineWeight($fillBoxFrameWeight);
5502
		    $aImg->Rectangle($x1,$ym,$x1+$boxsize,$ym+$boxsize);
5503
		}
5504
	    }
5505
	    $aImg->SetColor($this->font_color);
5506
	    $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
5507
	    $aImg->SetTextAlign("left","top");
5508
	    $aImg->StrokeText(round($x1+$this->mark_abs_hsize+$this->xmargin),$y1,$p[0]);
5509
 
5510
	    // Add CSIM for Legend if defined
5511
	    if( $p[4] != "" ) {
5512
		$xe = $x1 + $this->xmargin+$this->mark_abs_hsize+$aImg->GetTextWidth($p[0]);
5513
		$ye = $y1 + max($this->mark_abs_vsize,$aImg->GetTextHeight($p[0]));
5514
		$coords = "$x1,$y1,$xe,$y1,$xe,$ye,$x1,$ye";
5515
		if( ! empty($p[4]) ) {
5516
		    $this->csimareas .= "<area shape=\"poly\" coords=\"$coords\" href=\"".htmlentities($p[4])."\"";
5517
 
5518
		    if( !empty($p[6]) ) {
5519
			$this->csimareas .= " target=\"".$p[6]."\"";
5520
		    }
5521
 
5522
		    if( !empty($p[5]) ) {
5523
			$tmp=sprintf($p[5],$p[0]);
5524
			$this->csimareas .= " title=\"$tmp\" alt=\"$tmp\" ";
5525
		    }
5526
		    $this->csimareas .= " />\n";
5527
		}
5528
	    }
5529
	    if( $i >= $this->layout_n ) {
5530
		$x1 = $xp+$this->mark_abs_hsize+$this->xlmargin;
5531
		$y1 += $rowheight[$row++];
5532
		$i = 1;
5533
	    }
5534
	    else {
5535
		$x1 += $colwidth[($i-1) % $numcolumns] ;
5536
		++$i;
5537
	    }
5538
	}
5539
    }
5540
} // Class
5541
 
5542
 
5543
//===================================================
5544
// CLASS DisplayValue
5545
// Description: Used to print data values at data points
5546
//===================================================
5547
class DisplayValue {
5548
    var $show=false,$format="%.1f",$negformat="";
5549
    var $iFormCallback='';
5550
    var $angle=0;
5551
    var $ff=FF_FONT1,$fs=FS_NORMAL,$fsize=10;
5552
    var $color="navy",$negcolor="";
5553
    var $margin=5,$valign="",$halign="center";
5554
    var $iHideZero=false;
5555
 
5556
    function Show($aFlag=true) {
5557
	$this->show=$aFlag;
5558
    }
5559
 
5560
    function SetColor($aColor,$aNegcolor="") {
5561
	$this->color = $aColor;
5562
	$this->negcolor = $aNegcolor;
5563
    }
5564
 
5565
    function SetFont($aFontFamily,$aFontStyle=FS_NORMAL,$aFontSize=10) {
5566
	$this->ff=$aFontFamily;
5567
	$this->fs=$aFontStyle;
5568
	$this->fsize=$aFontSize;
5569
    }
5570
 
5571
    function SetMargin($aMargin) {
5572
	$this->margin = $aMargin;
5573
    }
5574
 
5575
    function SetAngle($aAngle) {
5576
	$this->angle = $aAngle;
5577
    }
5578
 
5579
    function SetAlign($aHAlign,$aVAlign='') {
5580
	$this->halign = $aHAlign;
5581
	$this->valign = $aVAlign;
5582
    }
5583
 
5584
    function SetFormat($aFormat,$aNegFormat="") {
5585
	$this->format= $aFormat;
5586
	$this->negformat= $aNegFormat;
5587
    }
5588
 
5589
    function SetFormatCallback($aFunc) {
5590
	$this->iFormCallback = $aFunc;
5591
    }
5592
 
5593
    function HideZero($aFlag=true) {
5594
	$this->iHideZero=$aFlag;
5595
    }
5596
 
5597
    function Stroke(&$img,$aVal,$x,$y) {
5598
 
5599
	if( $this->show )
5600
	{
5601
	    if( $this->negformat=="" ) $this->negformat=$this->format;
5602
	    if( $this->negcolor=="" ) $this->negcolor=$this->color;
5603
 
5604
	    if( $aVal===NULL || (is_string($aVal) && ($aVal=="" || $aVal=="-" || $aVal=="x" ) ) )
5605
		return;
5606
 
5607
	    if( is_numeric($aVal) && $aVal==0 && $this->iHideZero ) {
5608
		return;
5609
	    }
5610
 
5611
	    // Since the value is used in different cirumstances we need to check what
5612
	    // kind of formatting we shall use. For example, to display values in a line
5613
	    // graph we simply display the formatted value, but in the case where the user
5614
	    // has already specified a text string we don't fo anything.
5615
	    if( $this->iFormCallback != '' ) {
5616
		$f = $this->iFormCallback;
5617
		$sval = call_user_func($f,$aVal);
5618
	    }
5619
	    elseif( is_numeric($aVal) ) {
5620
		if( $aVal >= 0 )
5621
		    $sval=sprintf($this->format,$aVal);
5622
		else
5623
		    $sval=sprintf($this->negformat,$aVal);
5624
	    }
5625
	    else
5626
		$sval=$aVal;
5627
 
5628
	    $y = $y-sign($aVal)*$this->margin;
5629
 
5630
	    $txt = new Text($sval,$x,$y);
5631
	    $txt->SetFont($this->ff,$this->fs,$this->fsize);
5632
	    if( $this->valign == "" ) {
5633
		if( $aVal >= 0 )
5634
		    $valign = "bottom";
5635
		else
5636
		    $valign = "top";
5637
	    }
5638
	    else
5639
		$valign = $this->valign;
5640
	    $txt->Align($this->halign,$valign);
5641
 
5642
	    $txt->SetOrientation($this->angle);
5643
	    if( $aVal > 0 )
5644
		$txt->SetColor($this->color);
5645
	    else
5646
		$txt->SetColor($this->negcolor);
5647
	    $txt->Stroke($img);
5648
	}
5649
    }
5650
}
5651
 
5652
//===================================================
5653
// CLASS Plot
5654
// Description: Abstract base class for all concrete plot classes
5655
//===================================================
5656
class Plot {
5657
    var $line_weight=1;
5658
    var $coords=array();
5659
    var $legend='',$hidelegend=false;
5660
    var $csimtargets=array();	// Array of targets for CSIM
5661
    var $csimwintargets=array();	// Array of window targets for CSIM
5662
    var $csimareas="";			// Resultant CSIM area tags
5663
    var $csimalts=null;			// ALT:s for corresponding target
5664
    var $color="black";
5665
    var $numpoints=0;
5666
    var $weight=1;
5667
    var $value;
5668
    var $center=false;
5669
    var $legendcsimtarget='',$legendcsimwintarget='';
5670
    var $legendcsimalt='';
5671
//---------------
5672
// CONSTRUCTOR
5673
    function Plot(&$aDatay,$aDatax=false) {
5674
	$this->numpoints = count($aDatay);
5675
	if( $this->numpoints==0 )
5676
	    JpGraphError::RaiseL(25121);//("Empty input data array specified for plot. Must have at least one data point.");
5677
	$this->coords[0]=$aDatay;
5678
	if( is_array($aDatax) ) {
5679
	    $this->coords[1]=$aDatax;
5680
	    $n = count($aDatax);
5681
	    for($i=0; $i < $n; ++$i ) {
5682
		if( !is_numeric($aDatax[$i])) {
5683
		    JpGraphError::RaiseL(25070);
5684
		}
5685
	    }
5686
	}
5687
	$this->value = new DisplayValue();
5688
    }
5689
 
5690
//---------------
5691
// PUBLIC METHODS
5692
 
5693
    // Stroke the plot
5694
    // "virtual" function which must be implemented by
5695
    // the subclasses
5696
    function Stroke(&$aImg,&$aXScale,&$aYScale) {
5697
	JpGraphError::RaiseL(25122);//("JpGraph: Stroke() must be implemented by concrete subclass to class Plot");
5698
    }
5699
 
5700
    function HideLegend($f=true) {
5701
	$this->hidelegend = $f;
5702
    }
5703
 
5704
    function DoLegend(&$graph) {
5705
	if( !$this->hidelegend )
5706
	    $this->Legend($graph);
5707
    }
5708
 
5709
    function StrokeDataValue($img,$aVal,$x,$y) {
5710
	$this->value->Stroke($img,$aVal,$x,$y);
5711
    }
5712
 
5713
    // Set href targets for CSIM
5714
    function SetCSIMTargets($aTargets,$aAlts='',$aWinTargets='') {
5715
	$this->csimtargets=$aTargets;
5716
	$this->csimwintargets=$aWinTargets;
5717
	$this->csimalts=$aAlts;
5718
    }
5719
 
5720
    // Get all created areas
5721
    function GetCSIMareas() {
5722
	return $this->csimareas;
5723
    }
5724
 
5725
    // "Virtual" function which gets called before any scale
5726
    // or axis are stroked used to do any plot specific adjustment
5727
    function PreStrokeAdjust(&$aGraph) {
5728
	if( substr($aGraph->axtype,0,4) == "text" && (isset($this->coords[1])) )
5729
	    JpGraphError::RaiseL(25123);//("JpGraph: You can't use a text X-scale with specified X-coords. Use a \"int\" or \"lin\" scale instead.");
5730
	return true;
5731
    }
5732
 
5733
    // Get minimum values in plot
5734
    function Min() {
5735
	if( isset($this->coords[1]) )
5736
	    $x=$this->coords[1];
5737
	else
5738
	    $x="";
5739
	if( $x != "" && count($x) > 0 ) {
5740
	    $xm=min($x);
5741
	}
5742
	else
5743
	    $xm=0;
5744
	$y=$this->coords[0];
5745
	$cnt = count($y);
5746
	if( $cnt > 0 ) {
5747
	    /*
5748
	    if( ! isset($y[0]) ) {
5749
		JpGraphError('The input data array must have consecutive values from position 0 and forward. The given y-array starts with empty values (NULL)');
5750
	    }
5751
	    */
5752
	    //$ym = $y[0];
5753
	    $i=0;
5754
	    while( $i<$cnt && !is_numeric($ym=$y[$i]) )
5755
		$i++;
5756
	    while( $i < $cnt) {
5757
		if( is_numeric($y[$i]) )
5758
		    $ym=min($ym,$y[$i]);
5759
		++$i;
5760
	    }
5761
	}
5762
	else
5763
	    $ym="";
5764
	return array($xm,$ym);
5765
    }
5766
 
5767
    // Get maximum value in plot
5768
    function Max() {
5769
	if( isset($this->coords[1]) )
5770
	    $x=$this->coords[1];
5771
	else
5772
	    $x="";
5773
 
5774
	if( $x!="" && count($x) > 0 )
5775
	    $xm=max($x);
5776
	else {
5777
	    $xm = $this->numpoints-1;
5778
	}
5779
	$y=$this->coords[0];
5780
	if( count($y) > 0 ) {
5781
	    /*
5782
	    if( !isset($y[0]) ) {
5783
		JpGraphError::Raise('The input data array must have consecutive values from position 0 and forward. The given y-array starts with empty values (NULL)');
5784
		//$y[0] = 0;
5785
// Change in 1.5.1 Don't treat this as an error any more. Just silently convert to 0
5786
// Change in 1.17 Treat his as an error again !! This is the right way to do !!
5787
	    }
5788
	    */
5789
	    $cnt = count($y);
5790
	    $i=0;
5791
	    while( $i<$cnt && !is_numeric($ym=$y[$i]) )
5792
		$i++;
5793
	    while( $i < $cnt ) {
5794
		if( is_numeric($y[$i]) )
5795
		    $ym=max($ym,$y[$i]);
5796
		++$i;
5797
	    }
5798
 
5799
	}
5800
	else
5801
	    $ym="";
5802
	return array($xm,$ym);
5803
    }
5804
 
5805
    function SetColor($aColor) {
5806
	$this->color=$aColor;
5807
    }
5808
 
5809
    function SetLegend($aLegend,$aCSIM='',$aCSIMAlt='',$aCSIMWinTarget='') {
5810
	$this->legend = $aLegend;
5811
	$this->legendcsimtarget = $aCSIM;
5812
	$this->legendcsimwintarget = $aCSIMWinTarget;
5813
	$this->legendcsimalt = $aCSIMAlt;
5814
    }
5815
 
5816
    function SetWeight($aWeight) {
5817
	$this->weight=$aWeight;
5818
    }
5819
 
5820
    function SetLineWeight($aWeight=1) {
5821
	$this->line_weight=$aWeight;
5822
    }
5823
 
5824
    function SetCenter($aCenter=true) {
5825
	$this->center = $aCenter;
5826
    }
5827
 
5828
    // This method gets called by Graph class to plot anything that should go
5829
    // into the margin after the margin color has been set.
5830
    function StrokeMargin(&$aImg) {
5831
	return true;
5832
    }
5833
 
5834
    // Framework function the chance for each plot class to set a legend
5835
    function Legend(&$aGraph) {
5836
	if( $this->legend != "" )
5837
	    $aGraph->legend->Add($this->legend,$this->color,"",0,$this->legendcsimtarget,
5838
				 $this->legendcsimalt,$this->legendcsimwintarget);
5839
    }
5840
 
5841
} // Class
5842
 
5843
 
5844
//===================================================
5845
// CLASS PlotLine
5846
// Description:
5847
// Data container class to hold properties for a static
5848
// line that is drawn directly in the plot area.
5849
// Usefull to add static borders inside a plot to show
5850
// for example set-values
5851
//===================================================
5852
class PlotLine {
5853
    var $weight=1;
5854
    var $color="black";
5855
    var $direction=-1;
5856
    var $scaleposition;
5857
    var $legend='',$hidelegend=false, $legendcsimtarget='', $legendcsimalt='', $legendcsimwintarget='';
5858
    var $iLineStyle='solid';
5859
 
5860
 
5861
//---------------
5862
// CONSTRUCTOR
5863
    function PlotLine($aDir=HORIZONTAL,$aPos=0,$aColor="black",$aWeight=1) {
5864
	$this->direction = $aDir;
5865
	$this->color=$aColor;
5866
	$this->weight=$aWeight;
5867
	$this->scaleposition=$aPos;
5868
    }
5869
 
5870
//---------------
5871
// PUBLIC METHODS
5872
 
5873
    function SetLegend($aLegend,$aCSIM='',$aCSIMAlt='',$aCSIMWinTarget='') {
5874
	$this->legend = $aLegend;
5875
	$this->legendcsimtarget = $aCSIM;
5876
	$this->legendcsimwintarget = $aCSIMWinTarget;
5877
	$this->legendcsimalt = $aCSIMAlt;
5878
    }
5879
 
5880
    function HideLegend($f=true) {
5881
	$this->hidelegend = $f;
5882
    }
5883
 
5884
    function SetPosition($aScalePosition) {
5885
	$this->scaleposition=$aScalePosition;
5886
    }
5887
 
5888
    function SetDirection($aDir) {
5889
	$this->direction = $aDir;
5890
    }
5891
 
5892
    function SetColor($aColor) {
5893
	$this->color=$aColor;
5894
    }
5895
 
5896
    function SetWeight($aWeight) {
5897
	$this->weight=$aWeight;
5898
    }
5899
 
5900
    function SetLineStyle($aStyle) {
5901
	$this->iLineStyle = $aStyle;
5902
    }
5903
 
5904
//---------------
5905
// PRIVATE METHODS
5906
 
5907
    function DoLegend(&$graph) {
5908
	if( !$this->hidelegend )
5909
	    $this->Legend($graph);
5910
    }
5911
 
5912
    // Framework function the chance for each plot class to set a legend
5913
    function Legend(&$aGraph) {
5914
	if( $this->legend != "" ) {
5915
	    $dummyPlotMark = new PlotMark();
5916
	    $lineStyle = 1;
5917
	    $aGraph->legend->Add($this->legend,$this->color,$dummyPlotMark,$lineStyle,
5918
				 $this->legendcsimtarget,$this->legendcsimalt,$this->legendcsimwintarget);
5919
	}
5920
    }
5921
 
5922
    function PreStrokeAdjust($aGraph) {
5923
	// Nothing to do
5924
    }
5925
 
5926
    function Stroke(&$aImg,&$aXScale,&$aYScale) {
5927
	$aImg->SetColor($this->color);
5928
	$aImg->SetLineWeight($this->weight);
5929
	$oldStyle = $aImg->SetLineStyle($this->iLineStyle);
5930
	if( $this->direction == VERTICAL ) {
5931
	    $ymin_abs=$aYScale->Translate($aYScale->GetMinVal());
5932
	    $ymax_abs=$aYScale->Translate($aYScale->GetMaxVal());
5933
	    $xpos_abs=$aXScale->Translate($this->scaleposition);
5934
	    $aImg->StyleLine($xpos_abs, $ymin_abs, $xpos_abs, $ymax_abs);
5935
	}
5936
	elseif( $this->direction == HORIZONTAL ) {
5937
	    $xmin_abs=$aXScale->Translate($aXScale->GetMinVal());
5938
	    $xmax_abs=$aXScale->Translate($aXScale->GetMaxVal());
5939
	    $ypos_abs=$aYScale->Translate($this->scaleposition);
5940
	    $aImg->StyleLine($xmin_abs, $ypos_abs, $xmax_abs, $ypos_abs);
5941
	}
5942
	else {
5943
	    JpGraphError::RaiseL(25125);//(" Illegal direction for static line");
5944
	}
5945
	$aImg->SetLineStyle($oldStyle);
5946
    }
5947
}
5948
 
5949
// <EOF>
5950
?>