source: trunk/client/modules/Elezioni/grafici-old/jpgraph.php@ 2

Last change on this file since 2 was 2, checked in by root, 15 years ago

importo il progetto

File size: 244.6 KB
RevLine 
[2]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.242.2.1 2003/05/30 20:41:05 aditus 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","/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.
52DEFINE("CSIMCACHE_DIR","csimcache/");
53DEFINE("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)
64DEFINE("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.
76DEFINE('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.
85DEFINE("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.
93DEFINE("USE_CACHE",false);
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.
100DEFINE("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.
105DEFINE("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.
116DEFINE("USE_APPROX_COLORS",true);
117
118// Special unicode cyrillic language support
119DEFINE("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
124DEFINE("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.)
128DEFINE("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
133DEFINE("BRAND_TIMING",false);
134
135// What format should be used for the timing string?
136DEFINE("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.
147DEFINE("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")
151DEFINE("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
157DEFINE("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.
162DEFINE("_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.
167DEFINE("_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.
172DEFINE("_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.
178DEFINE("JPG_DEBUG",false);
179
180// Version info
181DEFINE('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//------------------------------------------------------------------------
187if (!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
200if (!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
218DEFINE("FF_COURIER",10);
219DEFINE("FF_VERDANA",11);
220DEFINE("FF_TIMES",12);
221DEFINE("FF_COMIC",14);
222DEFINE("FF_ARIAL",15);
223DEFINE("FF_GEORGIA",16);
224DEFINE("FF_TREBUCHE",17);
225
226// Chinese font
227DEFINE("FF_SIMSUN",18);
228
229// Older deprecated fonts
230DEFINE("FF_BOOK",91); // Deprecated fonts from 1.9
231DEFINE("FF_HANDWRT",92); // Deprecated fonts from 1.9
232
233// TTF Font styles
234DEFINE("FS_NORMAL",9001);
235DEFINE("FS_BOLD",9002);
236DEFINE("FS_ITALIC",9003);
237DEFINE("FS_BOLDIT",9004);
238DEFINE("FS_BOLDITALIC",9004);
239
240//Definitions for internal font, new style
241DEFINE("FF_FONT0",1);
242DEFINE("FF_FONT1",2);
243DEFINE("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)
248DEFINE("FONT0",99); // Deprecated from 1.2
249DEFINE("FONT1",98); // Deprecated from 1.2
250DEFINE("FONT1_BOLD",97); // Deprecated from 1.2
251DEFINE("FONT2",96); // Deprecated from 1.2
252DEFINE("FONT2_BOLD",95); // Deprecated from 1.2
253
254// Tick density
255DEFINE("TICKD_DENSE",1);
256DEFINE("TICKD_NORMAL",2);
257DEFINE("TICKD_SPARSE",3);
258DEFINE("TICKD_VERYSPARSE",4);
259
260// Side for ticks and labels.
261DEFINE("SIDE_LEFT",-1);
262DEFINE("SIDE_RIGHT",1);
263DEFINE("SIDE_DOWN",-1);
264DEFINE("SIDE_BOTTOM",-1);
265DEFINE("SIDE_UP",1);
266DEFINE("SIDE_TOP",1);
267
268// Legend type stacked vertical or horizontal
269DEFINE("LEGEND_VERT",0);
270DEFINE("LEGEND_HOR",1);
271
272// Mark types for plot marks
273DEFINE("MARK_SQUARE",1);
274DEFINE("MARK_UTRIANGLE",2);
275DEFINE("MARK_DTRIANGLE",3);
276DEFINE("MARK_DIAMOND",4);
277DEFINE("MARK_CIRCLE",5);
278DEFINE("MARK_FILLEDCIRCLE",6);
279DEFINE("MARK_CROSS",7);
280DEFINE("MARK_STAR",8);
281DEFINE("MARK_X",9);
282DEFINE("MARK_LEFTTRIANGLE",10);
283DEFINE("MARK_RIGHTTRIANGLE",11);
284DEFINE("MARK_FLASH",12);
285DEFINE("MARK_IMG",13);
286
287// Builtin images
288DEFINE("MARK_IMG_PUSHPIN",50);
289DEFINE("MARK_IMG_SPUSHPIN",50);
290DEFINE("MARK_IMG_LPUSHPIN",51);
291DEFINE("MARK_IMG_DIAMOND",52);
292DEFINE("MARK_IMG_SQUARE",53);
293DEFINE("MARK_IMG_STAR",54);
294DEFINE("MARK_IMG_BALL",55);
295DEFINE("MARK_IMG_SBALL",55);
296DEFINE("MARK_IMG_MBALL",56);
297DEFINE("MARK_IMG_LBALL",57);
298DEFINE("MARK_IMG_BEVEL",58);
299
300// Styles for gradient color fill
301DEFINE("GRAD_VER",1);
302DEFINE("GRAD_VERT",1);
303DEFINE("GRAD_HOR",2);
304DEFINE("GRAD_MIDHOR",3);
305DEFINE("GRAD_MIDVER",4);
306DEFINE("GRAD_CENTER",5);
307DEFINE("GRAD_WIDE_MIDVER",6);
308DEFINE("GRAD_WIDE_MIDHOR",7);
309DEFINE("GRAD_LEFT_REFLECTION",8);
310DEFINE("GRAD_RIGHT_REFLECTION",9);
311
312// Inline defines
313DEFINE("INLINE_YES",1);
314DEFINE("INLINE_NO",0);
315
316// Format for background images
317DEFINE("BGIMG_FILLPLOT",1);
318DEFINE("BGIMG_FILLFRAME",2);
319DEFINE("BGIMG_COPY",3);
320DEFINE("BGIMG_CENTER",4);
321
322// Depth of objects
323DEFINE("DEPTH_BACK",0);
324DEFINE("DEPTH_FRONT",1);
325
326// Direction
327DEFINE("VERTICAL",1);
328DEFINE("HORIZONTAL",0);
329
330// Constants for types of static bands in plot area
331DEFINE("BAND_RDIAG",1); // Right diagonal lines
332DEFINE("BAND_LDIAG",2); // Left diagonal lines
333DEFINE("BAND_SOLID",3); // Solid one color
334DEFINE("BAND_VLINE",4); // Vertical lines
335DEFINE("BAND_HLINE",5); // Horizontal lines
336DEFINE("BAND_3DPLANE",6); // "3D" Plane
337DEFINE("BAND_HVCROSS",7); // Vertical/Hor crosses
338DEFINE("BAND_DIAGCROSS",8); // Diagonal crosses
339
340// Axis styles for scientific style axis
341DEFINE('AXSTYLE_SIMPLE',1);
342DEFINE('AXSTYLE_BOXIN',2);
343DEFINE('AXSTYLE_BOXOUT',3);
344DEFINE('AXSTYLE_YBOXIN',4);
345DEFINE('AXSTYLE_YBOXOUT',5);
346
347// Style for title backgrounds
348DEFINE('TITLEBKG_STYLE1',1);
349DEFINE('TITLEBKG_STYLE2',2);
350DEFINE('TITLEBKG_STYLE3',3);
351DEFINE('TITLEBKG_FRAME_NONE',0);
352DEFINE('TITLEBKG_FRAME_FULL',1);
353DEFINE('TITLEBKG_FRAME_BOTTOM',2);
354DEFINE('TITLEBKG_FRAME_BEVEL',3);
355DEFINE('TITLEBKG_FILLSTYLE_HSTRIPED',1);
356DEFINE('TITLEBKG_FILLSTYLE_VSTRIPED',2);
357DEFINE('TITLEBKG_FILLSTYLE_SOLID',3);
358
359// Width of tab titles
360DEFINE('TABTITLE_WIDTHFIT',0);
361DEFINE('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//
367include "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//=============================================================
376class 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//==============================================================
395class 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//
427class 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//
442if( USE_IMAGE_ERROR_HANDLER ) {
443 JpGraphError::Install("JpGraphErrObjectImg");
444}
445else {
446 JpGraphError::Install("JpGraphErrObject");
447}
448
449
450//
451//Check if there were any warnings, perhaps some wrong includes by the
452//user
453//
454if( 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//
462function 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//
482if( 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}
498else {
499 $GLOBALS['gd2'] = USE_LIBRARY_GD2;
500 $GLOBALS['copyfunc'] = USE_LIBRARY_GD2 ? 'imagecopyresampled' : 'imagecopyresized';
501}
502
503// Usefull mathematical function
504function 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
510function 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($_SERVER['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($_SERVER['PHP_SELF']);
523 // Replace the ".php" extension with the image format extension
524 return substr($fname,0,strlen($fname)-4).".".$img_format;
525}
526
527class 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//===================================================
568class 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//===================================================
604class 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//===================================================
696class 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//=======================================================
733class 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//===================================================
774class 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//===================================================
2357class 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//===================================================
2407class 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//===================================================
2444class 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
2648class 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//===================================================
2764class 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//===================================================
2940class 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//===================================================
3090class 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//===================================================
3520class 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//===================================================
3646class 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//===================================================
3897class 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//===================================================
4459class 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//===================================================
5029class 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>
5686DEFINE(\"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//===================================================
6494class 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//===================================================
6640class 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//===================================================
6798DEFINE('_DEFAULT_LPM_SIZE',8);
6799class 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//===================================================
7083class 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//===================================================
7194class 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
7360require_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
7367class 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//=====================================================================
7387class 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//=====================================================================
7448class 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//=====================================================================
7465class 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//=====================================================================
7487class 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//=====================================================================
7514class 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//=====================================================================
7584class 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//=====================================================================
7649class 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//=====================================================================
7766class 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
7802class 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//=====================================================================
7838class 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//=====================================================================
7882class 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//===================================================
7979class 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?>
Note: See TracBrowser for help on using the repository browser.