source: trunk/xgraph/jpgraph/jpgraph.php

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