Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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