Subversion Repositories Applications.annuaire

Rev

Rev 66 | Details | Compare with Previous | Last modification | View Log | RSS feed

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