Subversion Repositories Sites.tela-botanica.org

Rev

Details | Last modification | View Log | RSS feed

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