1<?php
2/*=======================================================================
3 // File:        JPGRAPH_GANTT.PHP
4 // Description: JpGraph Gantt plot extension
5 // Created:     2001-11-12
6 // Ver:         $Id: jpgraph_gantt.php 1809 2009-09-09 13:07:33Z ljp $
7 //
8 // Copyright (c) Asial Corporation. All rights reserved.
9 //========================================================================
10 */
11
12require_once('jpgraph_plotband.php');
13require_once('jpgraph_iconplot.php');
14require_once('jpgraph_plotmark.inc.php');
15
16// Maximum size for Automatic Gantt chart
17define('MAX_GANTTIMG_SIZE_W',8000);
18define('MAX_GANTTIMG_SIZE_H',5000);
19
20// Scale Header types
21define("GANTT_HDAY",1);
22define("GANTT_HWEEK",2);
23define("GANTT_HMONTH",4);
24define("GANTT_HYEAR",8);
25define("GANTT_HHOUR",16);
26define("GANTT_HMIN",32);
27
28// Bar patterns
29define("GANTT_RDIAG",BAND_RDIAG); // Right diagonal lines
30define("GANTT_LDIAG",BAND_LDIAG); // Left diagonal lines
31define("GANTT_SOLID",BAND_SOLID); // Solid one color
32define("GANTT_VLINE",BAND_VLINE); // Vertical lines
33define("GANTT_HLINE",BAND_HLINE);  // Horizontal lines
34define("GANTT_3DPLANE",BAND_3DPLANE);  // "3D" Plane
35define("GANTT_HVCROSS",BAND_HVCROSS);  // Vertical/Hor crosses
36define("GANTT_DIAGCROSS",BAND_DIAGCROSS); // Diagonal crosses
37
38// Conversion constant
39define("SECPERDAY",3600*24);
40
41// Locales. ONLY KEPT FOR BACKWARDS COMPATIBILITY
42// You should use the proper locale strings directly
43// from now on.
44define("LOCALE_EN","en_UK");
45define("LOCALE_SV","sv_SE");
46
47// Layout of bars
48define("GANTT_EVEN",1);
49define("GANTT_FROMTOP",2);
50
51// Style for minute header
52define("MINUTESTYLE_MM",0);  // 15
53define("MINUTESTYLE_CUSTOM",2);  // Custom format
54
55
56// Style for hour header
57define("HOURSTYLE_HM24",0);  // 13:10
58define("HOURSTYLE_HMAMPM",1);  // 1:10pm
59define("HOURSTYLE_H24",2);  // 13
60define("HOURSTYLE_HAMPM",3);  // 1pm
61define("HOURSTYLE_CUSTOM",4);  // User defined
62
63// Style for day header
64define("DAYSTYLE_ONELETTER",0);  // "M"
65define("DAYSTYLE_LONG",1);  // "Monday"
66define("DAYSTYLE_LONGDAYDATE1",2); // "Monday 23 Jun"
67define("DAYSTYLE_LONGDAYDATE2",3); // "Monday 23 Jun 2003"
68define("DAYSTYLE_SHORT",4);  // "Mon"
69define("DAYSTYLE_SHORTDAYDATE1",5); // "Mon 23/6"
70define("DAYSTYLE_SHORTDAYDATE2",6); // "Mon 23 Jun"
71define("DAYSTYLE_SHORTDAYDATE3",7); // "Mon 23"
72define("DAYSTYLE_SHORTDATE1",8); // "23/6"
73define("DAYSTYLE_SHORTDATE2",9); // "23 Jun"
74define("DAYSTYLE_SHORTDATE3",10); // "Mon 23"
75define("DAYSTYLE_SHORTDATE4",11); // "23"
76define("DAYSTYLE_CUSTOM",12);  // "M"
77
78// Styles for week header
79define("WEEKSTYLE_WNBR",0);
80define("WEEKSTYLE_FIRSTDAY",1);
81define("WEEKSTYLE_FIRSTDAY2",2);
82define("WEEKSTYLE_FIRSTDAYWNBR",3);
83define("WEEKSTYLE_FIRSTDAY2WNBR",4);
84
85// Styles for month header
86define("MONTHSTYLE_SHORTNAME",0);
87define("MONTHSTYLE_LONGNAME",1);
88define("MONTHSTYLE_LONGNAMEYEAR2",2);
89define("MONTHSTYLE_SHORTNAMEYEAR2",3);
90define("MONTHSTYLE_LONGNAMEYEAR4",4);
91define("MONTHSTYLE_SHORTNAMEYEAR4",5);
92define("MONTHSTYLE_FIRSTLETTER",6);
93
94
95// Types of constrain links
96define('CONSTRAIN_STARTSTART',0);
97define('CONSTRAIN_STARTEND',1);
98define('CONSTRAIN_ENDSTART',2);
99define('CONSTRAIN_ENDEND',3);
100
101// Arrow direction for constrain links
102define('ARROW_DOWN',0);
103define('ARROW_UP',1);
104define('ARROW_LEFT',2);
105define('ARROW_RIGHT',3);
106
107// Arrow type for constrain type
108define('ARROWT_SOLID',0);
109define('ARROWT_OPEN',1);
110
111// Arrow size for constrain lines
112define('ARROW_S1',0);
113define('ARROW_S2',1);
114define('ARROW_S3',2);
115define('ARROW_S4',3);
116define('ARROW_S5',4);
117
118// Activity types for use with utility method CreateSimple()
119define('ACTYPE_NORMAL',0);
120define('ACTYPE_GROUP',1);
121define('ACTYPE_MILESTONE',2);
122
123define('ACTINFO_3D',1);
124define('ACTINFO_2D',0);
125
126
127// Check if array_fill() exists
128if (!function_exists('array_fill')) {
129    function array_fill($iStart, $iLen, $vValue) {
130        $aResult = array();
131        for ($iCount = $iStart; $iCount < $iLen + $iStart; $iCount++) {
132            $aResult[$iCount] = $vValue;
133        }
134        return $aResult;
135    }
136}
137
138//===================================================
139// CLASS GanttActivityInfo
140// Description:
141//===================================================
142class GanttActivityInfo {
143    public $iShow=true;
144    public $iLeftColMargin=4,$iRightColMargin=1,$iTopColMargin=1,$iBottomColMargin=3;
145    public $vgrid = null;
146    private $iColor='black';
147    private $iBackgroundColor='lightgray';
148    private $iFFamily=FF_FONT1,$iFStyle=FS_NORMAL,$iFSize=10,$iFontColor='black';
149    private $iTitles=array();
150    private $iWidth=array(),$iHeight=-1;
151    private $iTopHeaderMargin = 4;
152    private $iStyle=1;
153    private $iHeaderAlign='center';
154
155    function __construct() {
156        $this->vgrid = new LineProperty();
157    }
158
159    function Hide($aF=true) {
160        $this->iShow=!$aF;
161    }
162
163    function Show($aF=true) {
164        $this->iShow=$aF;
165    }
166
167    // Specify font
168    function SetFont($aFFamily,$aFStyle=FS_NORMAL,$aFSize=10) {
169        $this->iFFamily = $aFFamily;
170        $this->iFStyle  = $aFStyle;
171        $this->iFSize  = $aFSize;
172    }
173
174    function SetStyle($aStyle) {
175        $this->iStyle = $aStyle;
176    }
177
178    function SetColumnMargin($aLeft,$aRight) {
179        $this->iLeftColMargin = $aLeft;
180        $this->iRightColMargin = $aRight;
181    }
182
183    function SetFontColor($aFontColor) {
184        $this->iFontColor = $aFontColor;
185    }
186
187    function SetColor($aColor) {
188        $this->iColor = $aColor;
189    }
190
191    function SetBackgroundColor($aColor) {
192        $this->iBackgroundColor = $aColor;
193    }
194
195    function SetColTitles($aTitles,$aWidth=null) {
196        $this->iTitles = $aTitles;
197        $this->iWidth = $aWidth;
198    }
199
200    function SetMinColWidth($aWidths) {
201        $n = min(count($this->iTitles),count($aWidths));
202        for($i=0; $i < $n; ++$i ) {
203            if( !empty($aWidths[$i]) ) {
204                if( empty($this->iWidth[$i]) ) {
205                    $this->iWidth[$i] = $aWidths[$i];
206                }
207                else {
208                    $this->iWidth[$i] = max($this->iWidth[$i],$aWidths[$i]);
209                }
210            }
211        }
212    }
213
214    function GetWidth($aImg) {
215        $txt = new TextProperty();
216        $txt->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
217        $n = count($this->iTitles) ;
218        $rm=$this->iRightColMargin;
219        $w = 0;
220        for($h=0, $i=0; $i < $n; ++$i ) {
221            $w += $this->iLeftColMargin;
222            $txt->Set($this->iTitles[$i]);
223            if( !empty($this->iWidth[$i]) ) {
224                $w1 = max($txt->GetWidth($aImg)+$rm,$this->iWidth[$i]);
225            }
226            else {
227                $w1 = $txt->GetWidth($aImg)+$rm;
228            }
229            $this->iWidth[$i] = $w1;
230            $w += $w1;
231            $h = max($h,$txt->GetHeight($aImg));
232        }
233        $this->iHeight = $h+$this->iTopHeaderMargin;
234        $txt='';
235        return $w;
236    }
237
238    function GetColStart($aImg,&$aStart,$aAddLeftMargin=false) {
239        $n = count($this->iTitles) ;
240        $adj = $aAddLeftMargin ? $this->iLeftColMargin : 0;
241        $aStart=array($aImg->left_margin+$adj);
242        for( $i=1; $i < $n; ++$i ) {
243            $aStart[$i] = $aStart[$i-1]+$this->iLeftColMargin+$this->iWidth[$i-1];
244        }
245    }
246
247    // Adjust headers left, right or centered
248    function SetHeaderAlign($aAlign) {
249        $this->iHeaderAlign=$aAlign;
250    }
251
252    function Stroke($aImg,$aXLeft,$aYTop,$aXRight,$aYBottom,$aUseTextHeight=false) {
253
254        if( !$this->iShow ) return;
255
256        $txt = new TextProperty();
257        $txt->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
258        $txt->SetColor($this->iFontColor);
259        $txt->SetAlign($this->iHeaderAlign,'top');
260        $n=count($this->iTitles);
261
262        if( $n == 0 )
263        return;
264
265        $x = $aXLeft;
266        $h = $this->iHeight;
267        $yTop = $aUseTextHeight ? $aYBottom-$h-$this->iTopColMargin-$this->iBottomColMargin : $aYTop ;
268
269        if( $h < 0 ) {
270            JpGraphError::RaiseL(6001);
271            //('Internal error. Height for ActivityTitles is < 0');
272        }
273
274        $aImg->SetLineWeight(1);
275        // Set background color
276        $aImg->SetColor($this->iBackgroundColor);
277        $aImg->FilledRectangle($aXLeft,$yTop,$aXRight,$aYBottom-1);
278
279        if( $this->iStyle == 1 ) {
280            // Make a 3D effect
281            $aImg->SetColor('white');
282            $aImg->Line($aXLeft,$yTop+1,$aXRight,$yTop+1);
283        }
284
285        for($i=0; $i < $n; ++$i ) {
286            if( $this->iStyle == 1 ) {
287                // Make a 3D effect
288                $aImg->SetColor('white');
289                $aImg->Line($x+1,$yTop,$x+1,$aYBottom);
290            }
291            $x += $this->iLeftColMargin;
292            $txt->Set($this->iTitles[$i]);
293
294            // Adjust the text anchor position according to the choosen alignment
295            $xp = $x;
296            if( $this->iHeaderAlign == 'center' ) {
297                $xp = (($x-$this->iLeftColMargin)+($x+$this->iWidth[$i]))/2;
298            }
299            elseif( $this->iHeaderAlign == 'right' ) {
300                $xp = $x +$this->iWidth[$i]-$this->iRightColMargin;
301            }
302
303            $txt->Stroke($aImg,$xp,$yTop+$this->iTopHeaderMargin);
304            $x += $this->iWidth[$i];
305            if( $i < $n-1 ) {
306                $aImg->SetColor($this->iColor);
307                $aImg->Line($x,$yTop,$x,$aYBottom);
308            }
309        }
310
311        $aImg->SetColor($this->iColor);
312        $aImg->Line($aXLeft,$yTop, $aXRight,$yTop);
313
314        // Stroke vertical column dividers
315        $cols=array();
316        $this->GetColStart($aImg,$cols);
317        $n=count($cols);
318        for( $i=1; $i < $n; ++$i ) {
319            $this->vgrid->Stroke($aImg,$cols[$i],$aYBottom,$cols[$i],
320            $aImg->height - $aImg->bottom_margin);
321        }
322    }
323}
324
325
326//===================================================
327// CLASS GanttGraph
328// Description: Main class to handle gantt graphs
329//===================================================
330class GanttGraph extends Graph {
331    public $scale;  // Public accessible
332    public $hgrid=null;
333    private $iObj=array();    // Gantt objects
334    private $iLabelHMarginFactor=0.2; // 10% margin on each side of the labels
335    private $iLabelVMarginFactor=0.4; // 40% margin on top and bottom of label
336    private $iLayout=GANTT_FROMTOP; // Could also be GANTT_EVEN
337    private $iSimpleFont = FF_FONT1,$iSimpleFontSize=11;
338    private $iSimpleStyle=GANTT_RDIAG,$iSimpleColor='yellow',$iSimpleBkgColor='red';
339    private $iSimpleProgressBkgColor='gray',$iSimpleProgressColor='darkgreen';
340    private $iSimpleProgressStyle=GANTT_SOLID;
341    private $iZoomFactor = 1.0;
342    //---------------
343    // CONSTRUCTOR
344    // Create a new gantt graph
345    function __construct($aWidth=0,$aHeight=0,$aCachedName="",$aTimeOut=0,$aInline=true) {
346
347        // Backward compatibility
348        if( $aWidth == -1 ) $aWidth=0;
349        if( $aHeight == -1 ) $aHeight=0;
350
351        if( $aWidth<  0 || $aHeight < 0 ) {
352            JpgraphError::RaiseL(6002);
353            //("You can't specify negative sizes for Gantt graph dimensions. Use 0 to indicate that you want the library to automatically determine a dimension.");
354        }
355        parent::__construct($aWidth,$aHeight,$aCachedName,$aTimeOut,$aInline);
356        $this->scale = new GanttScale($this->img);
357
358        // Default margins
359        $this->img->SetMargin(15,17,25,15);
360
361        $this->hgrid = new HorizontalGridLine();
362
363        $this->scale->ShowHeaders(GANTT_HWEEK|GANTT_HDAY);
364        $this->SetBox();
365    }
366
367    //---------------
368    // PUBLIC METHODS
369
370    //
371
372    function SetSimpleFont($aFont,$aSize) {
373        $this->iSimpleFont = $aFont;
374        $this->iSimpleFontSize = $aSize;
375    }
376
377    function SetSimpleStyle($aBand,$aColor,$aBkgColor) {
378        $this->iSimpleStyle = $aBand;
379        $this->iSimpleColor = $aColor;
380        $this->iSimpleBkgColor = $aBkgColor;
381    }
382
383    // A utility function to help create basic Gantt charts
384    function CreateSimple($data,$constrains=array(),$progress=array()) {
385        $num = count($data);
386        for( $i=0; $i < $num; ++$i) {
387            switch( $data[$i][1] ) {
388                case ACTYPE_GROUP:
389                    // Create a slightly smaller height bar since the
390                    // "wings" at the end will make it look taller
391                    $a = new GanttBar($data[$i][0],$data[$i][2],$data[$i][3],$data[$i][4],'',8);
392                    $a->title->SetFont($this->iSimpleFont,FS_BOLD,$this->iSimpleFontSize);
393                    $a->rightMark->Show();
394                    $a->rightMark->SetType(MARK_RIGHTTRIANGLE);
395                    $a->rightMark->SetWidth(8);
396                    $a->rightMark->SetColor('black');
397                    $a->rightMark->SetFillColor('black');
398
399                    $a->leftMark->Show();
400                    $a->leftMark->SetType(MARK_LEFTTRIANGLE);
401                    $a->leftMark->SetWidth(8);
402                    $a->leftMark->SetColor('black');
403                    $a->leftMark->SetFillColor('black');
404
405                    $a->SetPattern(BAND_SOLID,'black');
406                    $csimpos = 6;
407                    break;
408
409                case ACTYPE_NORMAL:
410                    $a = new GanttBar($data[$i][0],$data[$i][2],$data[$i][3],$data[$i][4],'',10);
411                    $a->title->SetFont($this->iSimpleFont,FS_NORMAL,$this->iSimpleFontSize);
412                    $a->SetPattern($this->iSimpleStyle,$this->iSimpleColor);
413                    $a->SetFillColor($this->iSimpleBkgColor);
414                    // Check if this activity should have a constrain line
415                    $n = count($constrains);
416                    for( $j=0; $j < $n; ++$j ) {
417                        if( empty($constrains[$j]) || (count($constrains[$j]) != 3) ) {
418                            JpGraphError::RaiseL(6003,$j);
419                            //("Invalid format for Constrain parameter at index=$j in CreateSimple(). Parameter must start with index 0 and contain arrays of (Row,Constrain-To,Constrain-Type)");
420                        }
421                        if( $constrains[$j][0]==$data[$i][0] ) {
422                            $a->SetConstrain($constrains[$j][1],$constrains[$j][2],'black',ARROW_S2,ARROWT_SOLID);
423                        }
424                    }
425
426                    // Check if this activity have a progress bar
427                    $n = count($progress);
428                    for( $j=0; $j < $n; ++$j ) {
429
430                        if( empty($progress[$j]) || (count($progress[$j]) != 2) ) {
431                            JpGraphError::RaiseL(6004,$j);
432                            //("Invalid format for Progress parameter at index=$j in CreateSimple(). Parameter must start with index 0 and contain arrays of (Row,Progress)");
433                        }
434                        if( $progress[$j][0]==$data[$i][0] ) {
435                            $a->progress->Set($progress[$j][1]);
436                            $a->progress->SetPattern($this->iSimpleProgressStyle,
437                            $this->iSimpleProgressColor);
438                            $a->progress->SetFillColor($this->iSimpleProgressBkgColor);
439                            //$a->progress->SetPattern($progress[$j][2],$progress[$j][3]);
440                            break;
441                        }
442                    }
443                    $csimpos = 6;
444                    break;
445
446                case ACTYPE_MILESTONE:
447                    $a = new MileStone($data[$i][0],$data[$i][2],$data[$i][3]);
448                    $a->title->SetFont($this->iSimpleFont,FS_NORMAL,$this->iSimpleFontSize);
449                    $a->caption->SetFont($this->iSimpleFont,FS_NORMAL,$this->iSimpleFontSize);
450                    $csimpos = 5;
451                    break;
452                default:
453                    die('Unknown activity type');
454                    break;
455            }
456
457            // Setup caption
458            $a->caption->Set($data[$i][$csimpos-1]);
459
460            // Check if this activity should have a CSIM target�?
461            if( !empty($data[$i][$csimpos]) ) {
462                $a->SetCSIMTarget($data[$i][$csimpos]);
463                $a->SetCSIMAlt($data[$i][$csimpos+1]);
464            }
465            if( !empty($data[$i][$csimpos+2]) ) {
466                $a->title->SetCSIMTarget($data[$i][$csimpos+2]);
467                $a->title->SetCSIMAlt($data[$i][$csimpos+3]);
468            }
469
470            $this->Add($a);
471        }
472    }
473
474    // Set user specified scale zoom factor when auto sizing is used
475    function SetZoomFactor($aZoom) {
476    	$this->iZoomFactor = $aZoom;
477    }
478
479
480    // Set what headers should be shown
481    function ShowHeaders($aFlg) {
482        $this->scale->ShowHeaders($aFlg);
483    }
484
485    // Specify the fraction of the font height that should be added
486    // as vertical margin
487    function SetLabelVMarginFactor($aVal) {
488        $this->iLabelVMarginFactor = $aVal;
489    }
490
491    // Synonym to the method above
492    function SetVMarginFactor($aVal) {
493        $this->iLabelVMarginFactor = $aVal;
494    }
495
496
497    // Add a new Gantt object
498    function Add($aObject) {
499        if( is_array($aObject) && count($aObject) > 0 ) {
500            $cl = $aObject[0];
501            if( class_exists('IconPlot',false) && ($cl instanceof IconPlot) ) {
502                $this->AddIcon($aObject);
503            }
504            elseif( class_exists('Text',false) && ($cl instanceof Text) ) {
505            	$this->AddText($aObject);
506            }
507            else {
508                $n = count($aObject);
509                for($i=0; $i < $n; ++$i)
510                $this->iObj[] = $aObject[$i];
511            }
512        }
513        else {
514            if( class_exists('IconPlot',false) && ($aObject instanceof IconPlot) ) {
515                $this->AddIcon($aObject);
516            }
517            elseif( class_exists('Text',false) && ($aObject instanceof Text) ) {
518            	$this->AddText($aObject);
519            }
520            else {
521                $this->iObj[] = $aObject;
522            }
523        }
524    }
525
526	function StrokeTexts() {
527        // Stroke any user added text objects
528        if( $this->texts != null ) {
529        	$n = count($this->texts);
530            for($i=0; $i < $n; ++$i) {
531            	if( $this->texts[$i]->iScalePosX !== null && $this->texts[$i]->iScalePosY !== null ) {
532            		$x = $this->scale->TranslateDate($this->texts[$i]->iScalePosX);
533            		$y = $this->scale->TranslateVertPos($this->texts[$i]->iScalePosY);
534            		$y -= $this->scale->GetVertSpacing()/2;
535            	}
536            	else {
537            		$x = $y = null;
538            	}
539                $this->texts[$i]->Stroke($this->img,$x,$y);
540            }
541        }
542	}
543
544    // Override inherit method from Graph and give a warning message
545    function SetScale($aAxisType,$aYMin=1,$aYMax=1,$aXMin=1,$aXMax=1) {
546        JpGraphError::RaiseL(6005);
547        //("SetScale() is not meaningfull with Gantt charts.");
548    }
549
550    // Specify the date range for Gantt graphs (if this is not set it will be
551    // automtically determined from the input data)
552    function SetDateRange($aStart,$aEnd) {
553        // Adjust the start and end so that the indicate the
554        // begining and end of respective start and end days
555        if( strpos($aStart,':') === false )
556        $aStart = date('Y-m-d 00:00',strtotime($aStart));
557        if( strpos($aEnd,':') === false )
558        $aEnd = date('Y-m-d 23:59',strtotime($aEnd));
559        $this->scale->SetRange($aStart,$aEnd);
560    }
561
562    // Get the maximum width of the activity titles columns for the bars
563    // The name is lightly misleading since we from now on can have
564    // multiple columns in the label section. When this was first written
565    // it only supported a single label, hence the name.
566    function GetMaxLabelWidth() {
567        $m=10;
568        if( $this->iObj != null ) {
569            $marg = $this->scale->actinfo->iLeftColMargin+$this->scale->actinfo->iRightColMargin;
570            $n = count($this->iObj);
571            for($i=0; $i < $n; ++$i) {
572                if( !empty($this->iObj[$i]->title) ) {
573                    if( $this->iObj[$i]->title->HasTabs() ) {
574                        list($tot,$w) = $this->iObj[$i]->title->GetWidth($this->img,true);
575                        $m=max($m,$tot);
576                    }
577                    else
578                    $m=max($m,$this->iObj[$i]->title->GetWidth($this->img));
579                }
580            }
581        }
582        return $m;
583    }
584
585    // Get the maximum height of the titles for the bars
586    function GetMaxLabelHeight() {
587        $m=10;
588        if( $this->iObj != null ) {
589            $n = count($this->iObj);
590            // We can not include the title of GnttVLine since that title is stroked at the bottom
591            // of the Gantt bar and not in the activity title columns
592            for($i=0; $i < $n; ++$i) {
593                if( !empty($this->iObj[$i]->title) && !($this->iObj[$i] instanceof GanttVLine) ) {
594                    $m=max($m,$this->iObj[$i]->title->GetHeight($this->img));
595                }
596            }
597        }
598        return $m;
599    }
600
601    function GetMaxBarAbsHeight() {
602        $m=0;
603        if( $this->iObj != null ) {
604            $m = $this->iObj[0]->GetAbsHeight($this->img);
605            $n = count($this->iObj);
606            for($i=1; $i < $n; ++$i) {
607                $m=max($m,$this->iObj[$i]->GetAbsHeight($this->img));
608            }
609        }
610        return $m;
611    }
612
613    // Get the maximum used line number (vertical position) for bars
614    function GetBarMaxLineNumber() {
615        $m=1;
616        if( $this->iObj != null ) {
617            $m = $this->iObj[0]->GetLineNbr();
618            $n = count($this->iObj);
619            for($i=1; $i < $n; ++$i) {
620                $m=max($m,$this->iObj[$i]->GetLineNbr());
621            }
622        }
623        return $m;
624    }
625
626    // Get the minumum and maximum used dates for all bars
627    function GetBarMinMax() {
628        $start = 0 ;
629        $n = count($this->iObj);
630        while( $start < $n && $this->iObj[$start]->GetMaxDate() === false )
631        ++$start;
632        if( $start >= $n ) {
633            JpgraphError::RaiseL(6006);
634            //('Cannot autoscale Gantt chart. No dated activities exist. [GetBarMinMax() start >= n]');
635        }
636
637        $max=$this->scale->NormalizeDate($this->iObj[$start]->GetMaxDate());
638        $min=$this->scale->NormalizeDate($this->iObj[$start]->GetMinDate());
639
640        for($i=$start+1; $i < $n; ++$i) {
641            $rmax = $this->scale->NormalizeDate($this->iObj[$i]->GetMaxDate());
642            if( $rmax != false )
643            $max=Max($max,$rmax);
644            $rmin = $this->scale->NormalizeDate($this->iObj[$i]->GetMinDate());
645            if( $rmin != false )
646            $min=Min($min,$rmin);
647        }
648        $minDate = date("Y-m-d",$min);
649        $min = strtotime($minDate);
650        $maxDate = date("Y-m-d 23:59",$max);
651        $max = strtotime($maxDate);
652        return array($min,$max);
653    }
654
655    // Create a new auto sized canvas if the user hasn't specified a size
656    // The size is determined by what scale the user has choosen and hence
657    // the minimum width needed to display the headers. Some margins are
658    // also added to make it better looking.
659    function AutoSize() {
660
661        if( $this->img->img == null ) {
662            // The predefined left, right, top, bottom margins.
663            // Note that the top margin might incease depending on
664            // the title.
665           $hadj = $vadj = 0;
666           if( $this->doshadow ) {
667           		$hadj = $this->shadow_width;
668                $vadj = $this->shadow_width+5;
669            }
670
671            $lm = $this->img->left_margin;
672            $rm = $this->img->right_margin +$hadj;
673            $rm += 2 ;
674            $tm = $this->img->top_margin;
675            $bm = $this->img->bottom_margin + $vadj;
676            $bm += 2;
677
678            // If there are any added GanttVLine we must make sure that the
679            // bottom margin is wide enough to hold a title.
680            $n = count($this->iObj);
681        	for($i=0; $i < $n; ++$i) {
682            	if( $this->iObj[$i] instanceof GanttVLine ) {
683					$bm = max($bm,$this->iObj[$i]->title->GetHeight($this->img)+10);
684            	}
685        	}
686
687            // First find out the height
688            $n=$this->GetBarMaxLineNumber()+1;
689            $m=max($this->GetMaxLabelHeight(),$this->GetMaxBarAbsHeight());
690            $height=$n*((1+$this->iLabelVMarginFactor)*$m);
691
692            // Add the height of the scale titles
693            $h=$this->scale->GetHeaderHeight();
694            $height += $h;
695
696            // Calculate the top margin needed for title and subtitle
697            if( $this->title->t != "" ) {
698                $tm += $this->title->GetFontHeight($this->img);
699            }
700            if( $this->subtitle->t != "" ) {
701                $tm += $this->subtitle->GetFontHeight($this->img);
702            }
703
704            // ...and then take the bottom and top plot margins into account
705            $height += $tm + $bm + $this->scale->iTopPlotMargin + $this->scale->iBottomPlotMargin;
706            // Now find the minimum width for the chart required
707
708            // If day scale or smaller is shown then we use the day font width
709            // as the base size unit.
710            // If only weeks or above is displayed we use a modified unit to
711            // get a smaller image.
712            if( $this->scale->IsDisplayHour() || $this->scale->IsDisplayMinute() ) {
713                // Add 2 pixel margin on each side
714                $fw=$this->scale->day->GetFontWidth($this->img)+4;
715            }
716            elseif( $this->scale->IsDisplayWeek() ) {
717                $fw = 8;
718            }
719            elseif( $this->scale->IsDisplayMonth() ) {
720                $fw = 4;
721            }
722            else {
723                $fw = 2;
724            }
725
726            $nd=$this->scale->GetNumberOfDays();
727
728            if( $this->scale->IsDisplayDay() ) {
729                // If the days are displayed we also need to figure out
730                // how much space each day's title will require.
731                switch( $this->scale->day->iStyle ) {
732                    case DAYSTYLE_LONG :
733                        $txt = "Monday";
734                        break;
735                    case DAYSTYLE_LONGDAYDATE1 :
736                        $txt =  "Monday 23 Jun";
737                        break;
738                    case DAYSTYLE_LONGDAYDATE2 :
739                        $txt =  "Monday 23 Jun 2003";
740                        break;
741                    case DAYSTYLE_SHORT :
742                        $txt =  "Mon";
743                        break;
744                    case DAYSTYLE_SHORTDAYDATE1 :
745                        $txt =  "Mon 23/6";
746                        break;
747                    case DAYSTYLE_SHORTDAYDATE2 :
748                        $txt =  "Mon 23 Jun";
749                        break;
750                    case DAYSTYLE_SHORTDAYDATE3 :
751                        $txt =  "Mon 23";
752                        break;
753                    case DAYSTYLE_SHORTDATE1 :
754                        $txt =  "23/6";
755                        break;
756                    case DAYSTYLE_SHORTDATE2 :
757                        $txt =  "23 Jun";
758                        break;
759                    case DAYSTYLE_SHORTDATE3 :
760                        $txt =  "Mon 23";
761                        break;
762                    case DAYSTYLE_SHORTDATE4 :
763                        $txt =  "88";
764                        break;
765                    case DAYSTYLE_CUSTOM :
766                        $txt = date($this->scale->day->iLabelFormStr,strtotime('2003-12-20 18:00'));
767                        break;
768                    case DAYSTYLE_ONELETTER :
769                    default:
770                        $txt = "M";
771                        break;
772                }
773                $fw = $this->scale->day->GetStrWidth($this->img,$txt)+6;
774            }
775
776            // If we have hours enabled we must make sure that each day has enough
777            // space to fit the number of hours to be displayed.
778            if( $this->scale->IsDisplayHour() ) {
779                // Depending on what format the user has choose we need different amount
780                // of space. We therefore create a typical string for the choosen format
781                // and determine the length of that string.
782                switch( $this->scale->hour->iStyle ) {
783                    case HOURSTYLE_HMAMPM:
784                        $txt = '12:00pm';
785                        break;
786                    case HOURSTYLE_H24:
787                        // 13
788                        $txt = '24';
789                        break;
790                    case HOURSTYLE_HAMPM:
791                        $txt = '12pm';
792                        break;
793                    case HOURSTYLE_CUSTOM:
794                        $txt = date($this->scale->hour->iLabelFormStr,strtotime('2003-12-20 18:00'));
795                        break;
796                    case HOURSTYLE_HM24:
797                    default:
798                        $txt = '24:00';
799                        break;
800                }
801
802                $hfw = $this->scale->hour->GetStrWidth($this->img,$txt)+6;
803                $mw = $hfw;
804                if( $this->scale->IsDisplayMinute() ) {
805                    // Depending on what format the user has choose we need different amount
806                    // of space. We therefore create a typical string for the choosen format
807                    // and determine the length of that string.
808                    switch( $this->scale->minute->iStyle ) {
809                        case HOURSTYLE_CUSTOM:
810                            $txt2 = date($this->scale->minute->iLabelFormStr,strtotime('2005-05-15 18:55'));
811                            break;
812                        case MINUTESTYLE_MM:
813                        default:
814                            $txt2 = '15';
815                            break;
816                    }
817
818                    $mfw = $this->scale->minute->GetStrWidth($this->img,$txt2)+6;
819                    $n2 = ceil(60 / $this->scale->minute->GetIntervall() );
820                    $mw = $n2 * $mfw;
821                }
822                $hfw = $hfw < $mw ? $mw : $hfw ;
823                $n = ceil(24*60 / $this->scale->TimeToMinutes($this->scale->hour->GetIntervall()) );
824                $hw = $n * $hfw;
825                $fw = $fw < $hw ? $hw : $fw ;
826            }
827
828            // We need to repeat this code block here as well.
829            // THIS iS NOT A MISTAKE !
830            // We really need it since we need to adjust for minutes both in the case
831            // where hour scale is shown and when it is not shown.
832
833            if( $this->scale->IsDisplayMinute() ) {
834                // Depending on what format the user has choose we need different amount
835                // of space. We therefore create a typical string for the choosen format
836                // and determine the length of that string.
837                switch( $this->scale->minute->iStyle ) {
838                    case HOURSTYLE_CUSTOM:
839                        $txt = date($this->scale->minute->iLabelFormStr,strtotime('2005-05-15 18:55'));
840                        break;
841                    case MINUTESTYLE_MM:
842                    default:
843                        $txt = '15';
844                        break;
845                }
846
847                $mfw = $this->scale->minute->GetStrWidth($this->img,$txt)+6;
848                $n = ceil(60 / $this->scale->TimeToMinutes($this->scale->minute->GetIntervall()) );
849                $mw = $n * $mfw;
850                $fw = $fw < $mw ? $mw : $fw ;
851            }
852
853            // If we display week we must make sure that 7*$fw is enough
854            // to fit up to 10 characters of the week font (if the week is enabled)
855            if( $this->scale->IsDisplayWeek() ) {
856                // Depending on what format the user has choose we need different amount
857                // of space
858                $fsw = strlen($this->scale->week->iLabelFormStr);
859                if( $this->scale->week->iStyle==WEEKSTYLE_FIRSTDAY2WNBR ) {
860                    $fsw += 8;
861                }
862                elseif( $this->scale->week->iStyle==WEEKSTYLE_FIRSTDAYWNBR ) {
863                    $fsw += 7;
864                }
865                else {
866                    $fsw += 4;
867                }
868
869                $ww = $fsw*$this->scale->week->GetFontWidth($this->img);
870                if( 7*$fw < $ww ) {
871                    $fw = ceil($ww/7);
872                }
873            }
874
875            if( !$this->scale->IsDisplayDay() && !$this->scale->IsDisplayHour() &&
876            	!( ($this->scale->week->iStyle==WEEKSTYLE_FIRSTDAYWNBR ||
877            		$this->scale->week->iStyle==WEEKSTYLE_FIRSTDAY2WNBR) && $this->scale->IsDisplayWeek() ) ) {
878                // If we don't display the individual days we can shrink the
879                // scale a little bit. This is a little bit pragmatic at the
880                // moment and should be re-written to take into account
881                // a) What scales exactly are shown and
882                // b) what format do they use so we know how wide we need to
883                // make each scale text space at minimum.
884                $fw /= 2;
885                if( !$this->scale->IsDisplayWeek() ) {
886                    $fw /= 1.8;
887                }
888            }
889
890            $cw = $this->GetMaxActInfoColWidth() ;
891            $this->scale->actinfo->SetMinColWidth($cw);
892            if( $this->img->width <= 0 ) {
893                // Now determine the width for the activity titles column
894
895                // Firdst find out the maximum width of each object column
896                $titlewidth = max(max($this->GetMaxLabelWidth(),
897                $this->scale->tableTitle->GetWidth($this->img)),
898                $this->scale->actinfo->GetWidth($this->img));
899
900                // Add the width of the vertivcal divider line
901                $titlewidth += $this->scale->divider->iWeight*2;
902
903				// Adjust the width by the user specified zoom factor
904				$fw *= $this->iZoomFactor;
905
906                // Now get the total width taking
907                // titlewidth, left and rigt margin, dayfont size
908                // into account
909                $width = $titlewidth + $nd*$fw + $lm+$rm;
910            }
911            else {
912                $width = $this->img->width;
913            }
914
915            $width = round($width);
916            $height = round($height);
917            // Make a sanity check on image size
918            if( $width > MAX_GANTTIMG_SIZE_W || $height > MAX_GANTTIMG_SIZE_H ) {
919                JpgraphError::RaiseL(6007,$width,$height);
920                //("Sanity check for automatic Gantt chart size failed. Either the width (=$width) or height (=$height) is larger than MAX_GANTTIMG_SIZE. This could potentially be caused by a wrong date in one of the activities.");
921            }
922            $this->img->CreateImgCanvas($width,$height);
923            $this->img->SetMargin($lm,$rm,$tm,$bm);
924        }
925    }
926
927    // Return an array width the maximum width for each activity
928    // column. This is used when we autosize the columns where we need
929    // to find out the maximum width of each column. In order to do that we
930    // must walk through all the objects, sigh...
931    function GetMaxActInfoColWidth() {
932        $n = count($this->iObj);
933        if( $n == 0 ) return;
934        $w = array();
935        $m = $this->scale->actinfo->iLeftColMargin + $this->scale->actinfo->iRightColMargin;
936
937        for( $i=0; $i < $n; ++$i ) {
938            $tmp = $this->iObj[$i]->title->GetColWidth($this->img,$m);
939            $nn = count($tmp);
940            for( $j=0; $j < $nn; ++$j ) {
941                if( empty($w[$j]) )
942                $w[$j] = $tmp[$j];
943                else
944                $w[$j] = max($w[$j],$tmp[$j]);
945            }
946        }
947        return $w;
948    }
949
950    // Stroke the gantt chart
951    function Stroke($aStrokeFileName="") {
952
953        // If the filename is the predefined value = '_csim_special_'
954        // we assume that the call to stroke only needs to do enough
955        // to correctly generate the CSIM maps.
956        // We use this variable to skip things we don't strictly need
957        // to do to generate the image map to improve performance
958        // a best we can. Therefor you will see a lot of tests !$_csim in the
959        // code below.
960        $_csim = ($aStrokeFileName===_CSIM_SPECIALFILE);
961
962        // Should we autoscale dates?
963
964        if( !$this->scale->IsRangeSet() ) {
965            list($min,$max) = $this->GetBarMinMax();
966            $this->scale->SetRange($min,$max);
967        }
968
969        $this->scale->AdjustStartEndDay();
970
971        // Check if we should autoscale the image
972        $this->AutoSize();
973
974        // Should we start from the top or just spread the bars out even over the
975        // available height
976        $this->scale->SetVertLayout($this->iLayout);
977        if( $this->iLayout == GANTT_FROMTOP ) {
978            $maxheight=max($this->GetMaxLabelHeight(),$this->GetMaxBarAbsHeight());
979            $this->scale->SetVertSpacing($maxheight*(1+$this->iLabelVMarginFactor));
980        }
981        // If it hasn't been set find out the maximum line number
982        if( $this->scale->iVertLines == -1 )
983        	$this->scale->iVertLines = $this->GetBarMaxLineNumber()+1;
984
985        $maxwidth=max($this->scale->actinfo->GetWidth($this->img),
986        max($this->GetMaxLabelWidth(),
987        $this->scale->tableTitle->GetWidth($this->img)));
988
989        $this->scale->SetLabelWidth($maxwidth+$this->scale->divider->iWeight);//*(1+$this->iLabelHMarginFactor));
990
991        if( !$_csim ) {
992            $this->StrokePlotArea();
993            if( $this->iIconDepth == DEPTH_BACK ) {
994                $this->StrokeIcons();
995            }
996        }
997
998        $this->scale->Stroke();
999
1000        if( !$_csim ) {
1001            // Due to a minor off by 1 bug we need to temporarily adjust the margin
1002            $this->img->right_margin--;
1003            $this->StrokePlotBox();
1004            $this->img->right_margin++;
1005        }
1006
1007        // Stroke Grid line
1008        $this->hgrid->Stroke($this->img,$this->scale);
1009
1010        $n = count($this->iObj);
1011        for($i=0; $i < $n; ++$i) {
1012            //$this->iObj[$i]->SetLabelLeftMargin(round($maxwidth*$this->iLabelHMarginFactor/2));
1013            $this->iObj[$i]->Stroke($this->img,$this->scale);
1014        }
1015
1016        $this->StrokeTitles();
1017
1018        if( !$_csim ) {
1019            $this->StrokeConstrains();
1020            $this->footer->Stroke($this->img);
1021
1022
1023            if( $this->iIconDepth == DEPTH_FRONT) {
1024                $this->StrokeIcons();
1025            }
1026
1027            // Stroke all added user texts
1028            $this->StrokeTexts();
1029
1030            // Should we do any final image transformation
1031            if( $this->iImgTrans ) {
1032                if( !class_exists('ImgTrans',false) ) {
1033                    require_once('jpgraph_imgtrans.php');
1034                }
1035
1036                $tform = new ImgTrans($this->img->img);
1037                $this->img->img = $tform->Skew3D($this->iImgTransHorizon,$this->iImgTransSkewDist,
1038                $this->iImgTransDirection,$this->iImgTransHighQ,
1039                $this->iImgTransMinSize,$this->iImgTransFillColor,
1040                $this->iImgTransBorder);
1041            }
1042
1043
1044            // If the filename is given as the special "__handle"
1045            // then the image handler is returned and the image is NOT
1046            // streamed back
1047            if( $aStrokeFileName == _IMG_HANDLER ) {
1048                return $this->img->img;
1049            }
1050            else {
1051                // Finally stream the generated picture
1052                $this->cache->PutAndStream($this->img,$this->cache_name,$this->inline,
1053                $aStrokeFileName);
1054            }
1055        }
1056    }
1057
1058    function StrokeConstrains() {
1059        $n = count($this->iObj);
1060
1061        // Stroke all constrains
1062        for($i=0; $i < $n; ++$i) {
1063
1064            // Some gantt objects may not have constraints associated with them
1065            // for example we can add IconPlots which doesn't have this property.
1066            if( empty($this->iObj[$i]->constraints) ) continue;
1067
1068            $numConstrains = count($this->iObj[$i]->constraints);
1069
1070            for( $k = 0; $k < $numConstrains; $k++ ) {
1071                $vpos = $this->iObj[$i]->constraints[$k]->iConstrainRow;
1072                if( $vpos >= 0 ) {
1073                    $c1 = $this->iObj[$i]->iConstrainPos;
1074
1075                    // Find out which object is on the target row
1076                    $targetobj = -1;
1077                    for( $j=0; $j < $n && $targetobj == -1; ++$j ) {
1078                        if( $this->iObj[$j]->iVPos == $vpos ) {
1079                            $targetobj = $j;
1080                        }
1081                    }
1082                    if( $targetobj == -1 ) {
1083                        JpGraphError::RaiseL(6008,$this->iObj[$i]->iVPos,$vpos);
1084                        //('You have specifed a constrain from row='.$this->iObj[$i]->iVPos.' to row='.$vpos.' which does not have any activity.');
1085                    }
1086                    $c2 = $this->iObj[$targetobj]->iConstrainPos;
1087                    if( count($c1) == 4 && count($c2 ) == 4) {
1088                        switch( $this->iObj[$i]->constraints[$k]->iConstrainType ) {
1089                            case CONSTRAIN_ENDSTART:
1090                                if( $c1[1] < $c2[1] ) {
1091                                    $link = new GanttLink($c1[2],$c1[3],$c2[0],$c2[1]);
1092                                }
1093                                else {
1094                                    $link = new GanttLink($c1[2],$c1[1],$c2[0],$c2[3]);
1095                                }
1096                                $link->SetPath(3);
1097                                break;
1098                            case CONSTRAIN_STARTEND:
1099                                if( $c1[1] < $c2[1] ) {
1100                                    $link = new GanttLink($c1[0],$c1[3],$c2[2],$c2[1]);
1101                                }
1102                                else {
1103                                    $link = new GanttLink($c1[0],$c1[1],$c2[2],$c2[3]);
1104                                }
1105                                $link->SetPath(0);
1106                                break;
1107                            case CONSTRAIN_ENDEND:
1108                                if( $c1[1] < $c2[1] ) {
1109                                    $link = new GanttLink($c1[2],$c1[3],$c2[2],$c2[1]);
1110                                }
1111                                else {
1112                                    $link = new GanttLink($c1[2],$c1[1],$c2[2],$c2[3]);
1113                                }
1114                                $link->SetPath(1);
1115                                break;
1116                            case CONSTRAIN_STARTSTART:
1117                                if( $c1[1] < $c2[1] ) {
1118                                    $link = new GanttLink($c1[0],$c1[3],$c2[0],$c2[1]);
1119                                }
1120                                else {
1121                                    $link = new GanttLink($c1[0],$c1[1],$c2[0],$c2[3]);
1122                                }
1123                                $link->SetPath(3);
1124                                break;
1125                            default:
1126                                JpGraphError::RaiseL(6009,$this->iObj[$i]->iVPos,$vpos);
1127                                //('Unknown constrain type specified from row='.$this->iObj[$i]->iVPos.' to row='.$vpos);
1128                                break;
1129                        }
1130
1131                        $link->SetColor($this->iObj[$i]->constraints[$k]->iConstrainColor);
1132                        $link->SetArrow($this->iObj[$i]->constraints[$k]->iConstrainArrowSize,
1133                        $this->iObj[$i]->constraints[$k]->iConstrainArrowType);
1134
1135                        $link->Stroke($this->img);
1136                    }
1137                }
1138            }
1139        }
1140    }
1141
1142    function GetCSIMAreas() {
1143        if( !$this->iHasStroked )
1144        $this->Stroke(_CSIM_SPECIALFILE);
1145
1146        $csim = $this->title->GetCSIMAreas();
1147        $csim .= $this->subtitle->GetCSIMAreas();
1148        $csim .= $this->subsubtitle->GetCSIMAreas();
1149
1150        $n = count($this->iObj);
1151        for( $i=$n-1; $i >= 0; --$i )
1152        $csim .= $this->iObj[$i]->GetCSIMArea();
1153        return $csim;
1154    }
1155}
1156
1157//===================================================
1158// CLASS PredefIcons
1159// Description: Predefined icons for use with Gantt charts
1160//===================================================
1161define('GICON_WARNINGRED',0);
1162define('GICON_TEXT',1);
1163define('GICON_ENDCONS',2);
1164define('GICON_MAIL',3);
1165define('GICON_STARTCONS',4);
1166define('GICON_CALC',5);
1167define('GICON_MAGNIFIER',6);
1168define('GICON_LOCK',7);
1169define('GICON_STOP',8);
1170define('GICON_WARNINGYELLOW',9);
1171define('GICON_FOLDEROPEN',10);
1172define('GICON_FOLDER',11);
1173define('GICON_TEXTIMPORTANT',12);
1174
1175class PredefIcons {
1176    private $iBuiltinIcon = null, $iLen = -1 ;
1177
1178    function GetLen() {
1179        return $this->iLen ;
1180    }
1181
1182    function GetImg($aIdx) {
1183        if( $aIdx < 0 || $aIdx >= $this->iLen ) {
1184            JpGraphError::RaiseL(6010,$aIdx);
1185            //('Illegal icon index for Gantt builtin icon ['.$aIdx.']');
1186        }
1187        return Image::CreateFromString(base64_decode($this->iBuiltinIcon[$aIdx][1]));
1188    }
1189
1190    function __construct() {
1191        //==========================================================
1192        // warning.png
1193        //==========================================================
1194        $this->iBuiltinIcon[0][0]= 1043 ;
1195        $this->iBuiltinIcon[0][1]=
1196     'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsSAAALEgHS3X78AAAA'.
1197     'B3RJTUUH0wgKFSgilWPhUQAAA6BJREFUeNrtl91rHFUYh5/3zMx+Z5JNUoOamCZNaqTZ6IWIkqRiQWmi1IDetHfeiCiltgXBP8AL'.
1198     '0SIUxf/AvfRSBS9EKILFFqyIH9CEmFZtPqrBJLs7c+b1YneT3WTTbNsUFPLCcAbmzPt73o9zzgzs2Z793231UOdv3w9k9Z2uzOdA'.
1199     '5+2+79yNeL7Hl7hw7oeixRMZ6PJM26W18DNAm/Vh7lR8fqh97NmMF11es1iFpMATqdirwMNA/J4DpIzkr5YsAF1PO6gIMYHRdPwl'.
1200     'oO2elmB+qH3sm7XozbkgYvy8SzYnZPtcblyM6I+5z3jQ+0vJfgpEu56BfI9vUkbyi2HZd1QJoeWRiAjBd4SDCW8SSAOy6wBHMzF7'.
1201     'YdV2A+ROuvRPLfHoiSU0EMY/cDAIhxJeGngKaN1VgHyPL7NBxI1K9P4QxBzw3K1zJ/zkG8B9uwaQ7/HNsRZv9kohBGD0o7JqMYS/'.
1202     '/ynPidQw/LrBiPBcS/yFCT95DvB2BWAy4575PaQbQKW+tPd3GCItu2odKI++YxiKu0d26oWmAD7paZU/rLz37VqIijD2YbnzNBBE'.
1203     'IBHf8K8qjL7vYhCGErEU8CTg3xXAeMp96GrJEqkyXkm9Bhui1xfsunjdGhcYLq+IzjsGmBt5YH/cmJkFq6gIqlon3u4LxdKGuCIo'.
1204     'Qu41g0E41po+2R33Xt5uz9kRIB2UTle7PnfKrROP1HD4sRjZlq0lzhwoZ6rDNeTi3nEg1si/7FT7kYQbXS6E5E65tA5uRF9tutq0'.
1205     'K/VwAF+/FbIYWt6+tjQM/AqUms7A4Wy6d7YSfSNxgMmzi0ycWWworio4QJvj4LpuL5BqugTnXzzqJsJwurrlNhJXFaavW67NRw3F'.
1206     'q+aJcCQVe9fzvJGmAY7/dPH0gi0f64OveGxa+usCuQMeZ0+kt8BVrX+qPO9Bzx0MgqBvs+a2PfDdYIf+WAjXU1ub4tqNaPPzRs8A'.
1207     'blrli+WVn79cXn0cWKl+tGx7HLc7pu3CSmnfitL+l1UihAhwjFkPQev4K/fSABjBM8JCaFuurJU+rgW41SroA8aNMVNAFtgHJCsn'.
1208     'XGy/58QVxAC9MccJtZ5kIzNlW440WrJ2ea4YPA9cAooA7i0A/gS+iqLoOpB1HOegqrYB3UBmJrAtQAJwpwPr1Ry92wVlgZsiYlW1'.
1209     'uX1gU36dymgqYxJIJJNJT1W9QqHgNwFQBGYqo94OwHZQUuPD7ACglSvc+5n5T9m/wfJJX4U9qzEAAAAASUVORK5CYII=' ;
1210
1211        //==========================================================
1212        // edit.png
1213        //==========================================================
1214        $this->iBuiltinIcon[1][0]= 959 ;
1215        $this->iBuiltinIcon[1][1]=
1216     'iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAABGdBTUEAALGPC/xhBQAAAAZiS0dEAFgAWABY9j+ZuwAAAAlwSFlz'.
1217     'AAALEAAACxABrSO9dQAAAAd0SU1FB9AKDAwbIEXOA6AAAAM8SURBVHicpdRPaBxlHMbx76ZvsmOTmm1dsEqQSIIsEmGVBAQjivEQ'.
1218     'PAUJngpWsAWlBw8egpQepKwplN4ULEG9CjkEyUFKlSJrWTG0IU51pCsdYW2ncUPjdtp9Z+f3vuNhu8nKbmhaf5cZeGc+PO8zf1Lc'.
1219     'm0KhkACICCKCMeaBjiLC0tLSnjNvPmuOHRpH0TZTU1M8zBi9wakzn7OFTs5sw8YYACYmJrre7HkeuVyu69qPF77hlT1XmZ0eQ03O'.
1220     'wOLJTvhBx1rLz18VmJ0eY+jVd2FxDkKXnvYLHgb97OgLzE4ON9Hzc1B1QaQzsed5O0Lta3Ec89OnR5h5McfQ+Mw2qgQUnfBOPbZ3'.
1221     'bK3l+xOvMT0+3ERLp5FNF6UEjcL32+DdVmGt5WLhDYYPZrbRqreFumXwql0S3w9tnDvLWD5PZigPpdOwuYpSCo3C8wU3UHxQdHbf'.
1222     'cZIkNM6dxcnlUM4k1eUFMlUPpUADbpkttFarHe6oYqeOr6yt4RzMQHYUcUsQVtGicHDwKprViuLDkkOtVnsHCHZVRVy/zcj1i5Af'.
1223     'h8AjdIts+hUcGcYPK3iBtKM3gD/uAzf/AdY2mmmVgy6X8YNNKmGIvyloPcB8SUin07RQ4EZHFdsdG0wkJEnEaHAJxvKEpSLeaokV'.
1224     'r4zWmhUZYLlY4b1D03y5eIEWCtS7vsciAgiIxkQRabWOrlQor66y4pUphoJb1jiO4uO5o0S3q6RSqVbiOmC7VCEgAhLSaDQ48dH7'.
1225     'vD46REY0iysegSjKQciRt99ib7qXwX0O+pG4teM6YKHLB9JMq4mTmF9/+AKA4wvLZByH7OgYL7+UY2qvw/7Bfg5kHiXjJFyv3CGO'.
1226     'Y1rof+BW4t/XLiPG0DCGr79d4XzRxRnIMn98huXSTYyJ6et1UNYQhRvcinpJq86H3wGPPPM0iBDd+QffD1g4eZjLvuG7S1Wef26E'.
1227     'J7L7eSx7gAHVg7V3MSbi6m/r93baBd6qQjerAJg/9Ql/XrvG0ON1+vv7GH3qSfY5fahUnSTpwZgIEQesaVXRPbHRG/xyJSAxMYlp'.
1228     'EOm71HUINiY7mGb95l/8jZCyQmJjMDGJjUmsdCROtZ0n/P/Z8v4Fs2MTUUf7vYoAAAAASUVORK5CYII=' ;
1229
1230        //==========================================================
1231        // endconstrain.png
1232        //==========================================================
1233        $this->iBuiltinIcon[2][0]= 666 ;
1234        $this->iBuiltinIcon[2][1]=
1235     'iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAABGdBTUEAALGPC/xhBQAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlz'.
1236     'AAALDwAACw8BkvkDpQAAAAd0SU1FB9ALEREILkh0+eQAAAIXSURBVHictZU9aFNRFMd/N81HX77aptJUWmp1LHRpIcWhg5sIDlUQ'.
1237     'LAXB4t7RRUpwEhy7iQ46CCIoSHcl0CFaoVARU2MFMYktadLXJNok7x2HtCExvuYFmnO4w/3gx+Gc/z1HKRTdMEdXqHbB/sgc/sic'.
1238     'nDoYAI8XwDa8o1RMLT+2hAsigtTvbIGVqhX46szUifBGswUeCPgAGB7QeLk0X4Ork+HOxo1VgSqGASjMqkn8W4r4vVtEgI/RRQEL'.
1239     'vaoGD85cl5V3nySR/S1mxWxab7f35PnntNyMJeRr9kCMqiHTy09EoeToLwggx6ymiMOD/VwcD7Oa/MHkcIiQx026WGYto5P/U+ZZ'.
1240     '7gD0QwDuT5z9N3LrVPi0Xs543eQPKkRzaS54eviJIp4tMFQFMllAWN2qcRZHBnixNM8NYD162xq8u7ePSQ+GX2Pjwxc2dB2cLtB8'.
1241     '7GgamCb0anBYBeChMtl8855CarclxU1gvViiUK4w2OMkNDnGeJ8bt9fH90yOnOkCwLFTwhzykhvtYzOWoBBbY//R3dbaNTYhf2RO'.
1242     'QpeuUMzv188MlwuHy0H13HnE48UzMcL0WAtUHX8OxZHoG1URiFw7rnLLCswuSPD1ulze/iWjT2PSf+dBXRFtVVGIvzqph0pQL7VE'.
1243     'avXYaXXxPwsnt0imdttCocMmZBdK7YU9D8wuNOW0nXc6QWzPsSa5naZ1beb9BbGB6dxGtMnXAAAAAElFTkSuQmCC' ;
1244
1245        //==========================================================
1246        // mail.png
1247        //==========================================================
1248        $this->iBuiltinIcon[3][0]= 1122 ;
1249        $this->iBuiltinIcon[3][1]=
1250     'iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAABGdBTUEAALGPC/xhBQAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlz'.
1251     'AAALEAAACxABrSO9dQAAAAd0SU1FB9AJHAMfFvL9OU8AAAPfSURBVHictZRdaBRXFMd/987H7tbNx8aYtGCrEexDsOBDaKHFxirb'.
1252     'h0qhsiY0ykppKq1osI99C4H2WSiFFMHWUhXBrjRi0uCmtSEUGgP1QWqhWjGkoW7M1kTX3WRn5p4+TJJNGolQ6IXDnDtz+N0z/3PP'.
1253     'UWBIpdpYa23b9g09PZ2kUrOrvmUyGVKp1Ao/mUyi56YnVgWfO/P1CihAd/dJMpmaNROIRq8BkM1m0bH6TasC3j6QXgFdXI+DR6PR'.
1254     'JX/Pno8B+KLnMKqlpUU8z8MYs2RBEDzWf9J+0RcRbMdxGBsbw/fmCXwPMUEYID4iAVp8wIRmDIHMo4yHSIBSASKC+CWE0C/PF9jU'.
1255     '3B6Cp+4M07C5FUtKGNvGwQJctPgIsgD2wRhEIqAMGB+UQYkHJgYYZD7P1HwVlmWhHcfhyk83KeRGUW4t6CgoG5SNUS4KBWgQDUov'.
1256     '7AGlwYASBVqH0Bk49dXpCviVV3dw/tI1Bvr7kMIIlh0NYUpjlF0BAYvcxSXmEVLKceHSCJm+PnbueBHbtkNwTXUNBzo6aGpq4sSZ'.
1257     'GwT5H7BsF6Wdf1GWHQAoM0upeI9PT1yioS7B7tdaSdSuw7KsUGMAy7HYsmUztTW1nMwM0txssX1rlHjjS5jy/Uq2YkK/eJuLl6/z'.
1258     'x+1xkslW6mrixGIODx8EFSlEBC0+tmXT0NhA2763iEUjnLv4C8XpUbSbAB1mKkGJ3J83Od77HW5EszvZSqK2iljMIeJaRGNuJePF'.
1259     '6mspY7BJ1DXwQnCd2fxGRq5OUCz8xt72dyhMZcn++Cu3xu9SKhdp2b4ZHWnAtTSxmIWlhcIjlksR3lNBYzlxZsb7+f7ne+xtSzOd'.
1260     'u83szH1OnThOPp/n+a0beeP1l4mvq+PU2Qyd+5PY1RuwlAqLYFaBfbTbyPSdfgaH77A//QF4f1O/vpr6RJyq+C5Kc/M8FbFxXItY'.
1261     'xOHDrvfo/fxLDnbsJBp5BowBReVWYAzabeTh5ABDw7cWoNNL3YYYNtSv57lnn6Z+Qx01VeuIuBa2DV1HD3H63BAPZu4u1WGpeLHq'.
1262     'Rh7+NcjA0O+0p4+CNwXigwnbWlQQdpuEpli+n+PIkcOc//YKuckJJFh2K2anrjFw+QZt6S6kPImIF/b+cqAJD1LihWAxC61twBTo'.
1263     'fPcQF/oGsVW5ovHQlavs2/8+uYnRVSOUgHAmmAClBIOBwKC0gPjhIRgEIX2wg7NnwpZW3d3d4vs+vu8TBMGK51rvPM9b8hdteZxd'.
1264     'LBbVR8feJDs0Rlv6GFKeXJ21rNRXESxMPR+CBUl0nN7PjtO+dye7Up/8v1I88bf/ixT/AO1/hZsqW+C6AAAAAElFTkSuQmCC' ;
1265
1266        //==========================================================
1267        // startconstrain.png
1268        //==========================================================
1269        $this->iBuiltinIcon[4][0]= 725 ;
1270        $this->iBuiltinIcon[4][1]=
1271     'iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAABGdBTUEAALGPC/xhBQAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlz'.
1272     'AAALDgAACw4BQL7hQQAAAAd0SU1FB9ALEREICJp5fBkAAAJSSURBVHic3dS9a1NRGMfx77kxtS+xqS9FG6p1ER3qVJpBQUUc3CRU'.
1273     'BwURVLB1EAuKIP0THJQiiNRJBK3iJl18AyeltRZa0bbaJMbUNmlNSm5e7s25j0NqpSSmyag/OMM9POdzDuflwn8djz8gClVRrVEV'.
1274     'ur4Bl1FTNSzLrSS6vbml0jUUwSXj8Qfk3PkLtLW2AeBIybmrgz3+gFzpucjlE4f4btuFTuWuCF5XDr3a3UPf6cM8GQvxzbsRAJdh'.
1275     'ScfxSywml5j7mVypN0eGEJ0tebIre+zxB6Tv7jPReS2hREpOvpmUXU+H5eC913JnNCSRVE60pUVbWoZjprR39Yq70bdqj4pW7PEH'.
1276     '5FpvL9e79jOTTHM7ssDL6CJZ08LbvAGnrpZg2mI2Z/MlZfN8IkxuSwu4V9+WIrj7zFlOHfXzKrLIi2SGh5ECKjnNVNxkQEc55vOw'.
1277     'rb6O8JLFdHyJ+ayFElUeHvjwkfteL/V7fKTSkFvIQE4DoLI2Mz/muTkTApcBKIwaN8pwIUrKw+ajWwDknAO0d/r4zFaMuRS63sWm'.
1278     'RoOdm+vRIriUYjKexrQV+t1o0YEVwfZSVJmD/dIABJuO0LG3lRFx0GOfiAELE9OgCrfU0XnIp5FwGLEy5WEAOxlR5uN+ARhP7GN3'.
1279     '5w7Gv4bQI2+xpt4jjv2nWBmIlcExE2vDAHYioszBZXw6CPE4ADoWVHmd/tuwlZR9eXYyoszBfpiNQqaAOU5+TXRN+DeeenADPT9b'.
1280     'EVgKVsutKPl0TGWGhwofoquaoKK4apsq/tH/e/kFwBMXLgAEKK4AAAAASUVORK5CYII=' ;
1281
1282        //==========================================================
1283        // calc.png
1284        //==========================================================
1285        $this->iBuiltinIcon[5][0]= 589 ;
1286        $this->iBuiltinIcon[5][1]=
1287     'iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAABGdBTUEAALGPC/xhBQAAAAZiS0dEAA4AIwBbgMF12wAAAAlwSFlz'.
1288     'AAALEQAACxEBf2RfkQAAAAd0SU1FB9AHBxQeFsqn0wQAAAHKSURBVHicnZWff+RAGIef3U/gcOEgUAgUCgcLhYXCwsHBQeGgUDgs'.
1289     'FgMHB4VA/4Bg4XChWFgIFIqBwkJhsRAYeOGF+TQHmWSTTbKd9pU37/x45jvfTDITXEynAbdWKVQB0NazcVm0alcL4rJaRVzm+w/e'.
1290     '3iwAkzbYRcnnYgI04GCvsxxSPabYaEdt2Ra6D0atcvvvDmyrMWBX1zPq2ircP/Tk98DiJtjV/fim6ziOCL6dDHZNhxQ3arIMsox4'.
1291     'vejleL2Ay9+jaw6A+4OSICG2cacGKhsGxg+CxeqAQS0Y7BYJvowq7iGMOhXHEfzpvpQkA9bLKgOgWKt+4Lo1mM9hs9m17QNsJ70P'.
1292     'Fjc/O52joogoX8MZKiBiAFxd9Z1vcj9wfSpUlDRNMcYQxzFpmnJ0FPH8nDe1MQaWSz9woQpWSZKEojDkeaWoKAyr1tlu+s48wfVx'.
1293     'u7n5i7jthmGIiEGcT+36PP+gFeJrxWLhb0UA/lb4ggGs1T0rZs0zwM/ZjNfilcIY5tutPxgOW3F6dUX464LrKILLiw+A7WErrl+2'.
1294     'rABG1EL/BilZP8DjU2uR4U+2E49P1Z8QJmNXUzl24A9GBT0IruCfi86d9x+D12RGzt+pNAAAAABJRU5ErkJggg==' ;
1295
1296        //==========================================================
1297        // mag.png
1298        //==========================================================
1299        $this->iBuiltinIcon[6][0]= 1415 ;
1300        $this->iBuiltinIcon[6][1]=
1301     'iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAABGdBTUEAALGPC/xhBQAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlz'.
1302     'AAALDAAACwwBP0AiyAAAAAd0SU1FB9ALDxEWDY6Ul+UAAAUESURBVHicdZVrbFRFGIafsyyF0nalV1R6WiggaAptlzsr1OgEogmC'.
1303     '0IgoBAsBgkIrBAPEhBj/AP6xRTCUFEwRI4jcgsitXMrFCJptJWvBNpXYbbXtbtttt6e7e86ec/yxadlCfZPJZDIz73zzzjfvR2VL'.
1304     'F7U+hf0HD2JduIzTFy6SlJRkPtkcDgdCCE65OxFC8NPV6wghyM7OptankJ2dzbSC5QghEEIgCSHog9PpNAF27dlN6miZuPgElB4/'.
1305     'nmY3O7ZtByA1NVUCkGWZweD1eklJScESTbqxuIjrd+/x6uIl5M19hSy7nfGOeUxf+g7VjU1sKi7C4/GYsiyz7tAJAD4/cRaA1tZW'.
1306     'AHIPnECUVGD1+/3U19ebG4uLeHf1akamjsIwoVnVCOvQEdLoVILYYmMo3PIxSBJflpSaDX5FAmju1QAYv/8k/s8+wLVxOU0jR2LZ'.
1307     '8sMFAApWrCApbRRDrRZirBYSLBKaoRPQw3SFernf2sav7T0Ubt4KwL4FMwF4Vu8FoHBCKgCzDhwHwLIhZ7y5a89u4m2JhA0wTdDC'.
1308     'OrphEjJMNElCHxKDEjaobmvlfo/Krj27CQQCJsCGJW8C0KXqAMxMiosQA8hZWcTFx9OsaniDKh1qmG7VoFsL0x0K06kbeAMhWpRe'.
1309     '/KpG+gwHAKUnz7Dz3BUMw6DK18nuw99wt0Nh6VdHI8RJicmETQgFg7SFwjSrGv+oKp6ghldV6dZ0ugJBlF6FmCESQ2w2AIqXLsan'.
1310     'BrFYLJTnTCBrdBqveeopWZiPFaBHUegJhegMqGgxEkHDwB/UaQ9rdIV06v0+TD2EEQjQFtAY0dsNgNvt5sialQAIIXh7wQKuVf6J'.
1311     'gTsSccPDWlQstClBGjr9eHpVWvUQncEwdYEedF8noQ4vmYmpZMTH0nTvDn25vLbrNmu7bvfnsYEbAMnhcPDgwQPzUo2LJusw/mhp'.
1312     'QwlHNO0KBAnoIfxtrcQMT2De1Mm891wyUzNlUlJSpIyMDBobGzlzr5rFM/Koq6vrP8ASGxsLwPmKcvIShjPGZiPOakE3VFB8hHwd'.
1313     'vJAxhrk5L7Ly+RQuH/sWgPdXrwFg/6HDFBUsIj09nehfbAWwPWOT9n5RYhqGwarNWxkRM5TRCfF4U1PQsDDJFk9uYhwXvzvKjm3b'.
1314     'KSsro3DJInNW5RXp7u2bAKSlpeH1esnPz6eqqgqLpmmcr3Fht9ulfaV7mZk1Bs+lM6T1djM9fhg5egDPpTNMy5TZsW07kydPYdWM'.
1315     'aXx96ixOp9O8cfUa80srmDpjOgAulytiQqZpMnvObLbt/JTtHxXj9/tRVdU0DGOAufRpevPDTeac0hJyc3NxOOawfv161lVWS6eX'.
1316     'z+9/UOCxu1VWVvaTRGv16NFfjB2bNeAQp9NpTpmSM4DcbrdL0WsGDKLRR+52uwe1yP8jb2lpYfikyY9t80n03UCWZeaXVjw1f+zs'.
1317     'Oen+/d+pqanhzp2fKSsrw+l0mi6XiyPl5ZGITdN8fAVJwjRNJEmi1qfw1kw7siyTnJxMe3s71dXV3GpoZO64DG41NPJylvxU5D/e'.
1318     'qJKsfWQD9IkaZ2RmUvr9aV4aGYcQgjfO3aWoYBF5eXm4ewIsu/CbdPz1aWb0/p1bNoOrQxlUiuiaFo3c3FyEEOx9+C9CCD6paaTW'.
1319     'p/TXyYkTJ0Xe59jf7QOyAKDWp/QXxcFQ61P4pT3ShBBcvnUHIQTjxmX19/8BCeVg+/GPpskAAAAASUVORK5CYII=' ;
1320
1321        //==========================================================
1322        // lock.png
1323        //==========================================================
1324        $this->iBuiltinIcon[7][0]= 963 ;
1325        $this->iBuiltinIcon[7][1]=
1326     'iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAABGdBTUEAALGPC/xhBQAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlz'.
1327     'AAALCwAACwsBbQSEtwAAAAd0SU1FB9AKAw0XDmwMOwIAAANASURBVHic7ZXfS1t3GMY/3+PprI7aisvo2YU6h6ATA8JW4rrlsF4U'.
1328     'qiAsF9mhl0N2cYTRy9G/wptAYWPD9iJtRy5asDe7cYFmyjaXOLaMImOrmkRrjL9yTmIS3120JybWQgfb3R74wuc8Lzw858vLOUpE'.
1329     'OK6pqSm2trbY39+nu7tbPHYch7m5OcLhMIA67kWj0aMQEWk6tm17rNm2LSIie3t7ksvlJJ1OSyqVkls3Z8SyLMnlcqTTaVKpFLdu'.
1330     'zmBZVj1HeY2VUti2TSQSQSml2bZdi0QirK2tMT09zerqKtlslqGhISYnJ4nHv2N+foFsNquOe9FotLlxOBwmk8lgWRbhcFgymYxY'.
1331     'liUi0mqaJoAuIi2macrdO7fFsizx3to0Te7euV1vrXtXEgqFmJmZYWVlhXK5LB4/U9kwDL784kYV0A3DYHd3m4sXRymXywKoRi8U'.
1332     'Ch01DgQCJBIJLMsiEAhIIpHw2uLz+eqtYrEYIqKZpimxWEyCwaCMjY01zYPBIJpXqVQqsby8TLVabWKA/v5+RkZGMAyDrq4ulFKH'.
1333     'HsfjcWZnZ+ns7KTRqwcnk0mKxSKFQqGJlVKtruuSTCYB6O3trW9UI/v9/iZPB/j8s2HOnX0FgHfeXpeffnzK+fWf+fijvhLs0PtG'.
1334     'D/n1OJ9+MsrlSwb3733DwMCAt1EyPj6uACYmJp56168NU6nUqFSE9nZdPE7+WqC/r4NKTagcCJVqDaUUB5VDAA4Pa9x7sMLlSwan'.
1335     'WjRmv13D7/erpaWlo604qOp88OF7LC48rPNosMq5Th+Dgxd4/XyA1rbzADi7j8jnf2P++wdcvSr8MJ/i8eomAKlUqn41OsDAQDeD'.
1336     'g++yuPCwzm/2vU8+n2a7sMFfj79mp7BBuVzioFSiXHJx3SKuW2Rzy0Up9dxnQVvODALQerqNRn4ZKe0Mvtc6TpzpmqbxalcY9Ato'.
1337     '2v06t515C73YQftZB9GLnDrt4LoujuPgOA4Ui+C6yOpXJwZrJ7r/gv4P/u+D9W7fLxTz+1ScQxrZ3atRLaVxdjbY2d184R6/sLHe'.
1338     'opHP7/Do90Ua+WWUyezzZHObP/7cfX54/dowE1d66s8TV3oE+Mfn+L/zb4XmHPjRG9YjAAAAAElFTkSuQmCC' ;
1339
1340        //==========================================================
1341        // stop.png
1342        //==========================================================
1343        $this->iBuiltinIcon[8][0]= 889 ;
1344        $this->iBuiltinIcon[8][1]=
1345     'iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAABGdBTUEAALGPC/xhBQAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlz'.
1346     'AAALDwAACw8BkvkDpQAAAAd0SU1FB9AJDwEvNyD6M/0AAAL2SURBVHic1ZTLaxVnGIefb2bO5OScHJN4oWrFNqcUJYoUEgU3/Qf6'.
1347     'F7gwCkIrvdBLUtqqiLhSg9bgBduFSHZdiG5ctkJ3xRDbUFwUmghNzBDanPGMkzOX79LFJGPMOSd204U/+Bbzvd/78F4H/ieJdoad'.
1348     'pZKxRFszAI/DcP0HazXY22v+HB01kee1PA/v3zfnjx4xgGnHcNZe7OvuNj+cOEF1ZATv5nUA4jhBSgmADCVWo8Ge2Of9wb18P/G7'.
1349     'oUXmYi30zqlTVEdGWLh1g2D6MYlKkXGE0Vl8aa2GEB149+4xXSzyoOIw/mimiZV/DPb25pFOj13A9gOMEChhUEqhVYqWKUk9QAUp'.
1350     'sT/P4s8PmKlUmNhQaIJbkDVqBbpw6wZ2zUc4Nm+ePku5p4eOrgpueQOFUoVCVxcD4+N07dpF9+5tVJeWGPBjhvr7WF1zC8ASgtcP'.
1351     'H8a7eZ1odh4sh50nzwCw9ZNh3M4Stutiu0X2nB/LyjZ6lcIbVTpdQU/jWVPzLADM8+ZGBRdtC7wrF/O7bR99iu26VL86iU4SAH4b'.
1352     'Po5d6AQhstMSvGyI4wS5FJBKSRwnzF8byx/u+PjzzMF1mfryQ1K/jnCahqp1xEopjFLoNEFJSRJHzF799gWHqa+/QKcSUXBI609f'.
1353     'Al5W4teQSiHDOipNUKnMI13RvnOXAIEKQixvGWya98SC560MFwPiqEG86JM8q79Q06lvhnOndy5/B6GPCUOMUu3BQgg8z0M3GmBZ'.
1354     'iGJn3v2VmsqnfzNx7FDueODuj8ROCFpjtG5TCmOYv32bJ09msP0ISydMfnAUgF8/O45RAA6WTPjlvXcB+Gn7FuRf/zAnNX6x3ARe'.
1355     'PSdmqL+P/YHkwMGDOGWDZTlQcNBRhPEComgB/YeHfq2InF1kLlXUOkpMbio1bd7aATRD/X0M1lPeSlM2vt2X1XBZjZnpLG2tmZO6'.
1356     'LbQVOIcP+HG2UauH3xgwBqOz9Cc3l1tC24Fz+MvUDroeGNb5if9H/1dM/wLPCYMw9fryKgAAAABJRU5ErkJggg==' ;
1357
1358        //==========================================================
1359        // error.png
1360        //==========================================================
1361        $this->iBuiltinIcon[9][0]= 541 ;
1362        $this->iBuiltinIcon[9][1]=
1363     'iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAMAAAC7IEhfAAAAaVBMVEX//////2Xy8mLl5V/Z2VvMzFi/v1WyslKlpU+ZmUyMjEh/'.
1364     'f0VyckJlZT9YWDxMTDjAwMDy8sLl5bnY2K/MzKW/v5yyspKlpYiYmH+MjHY/PzV/f2xycmJlZVlZWU9MTEXY2Ms/PzwyMjLFTjea'.
1365     'AAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAAHdElNRQfTCAkUMSj9wWSOAAABLUlEQVR4'.
1366     '2s2U3ZKCMAxGjfzJanFAXFkUle/9H9JUKA1gKTN7Yy6YMjl+kNPK5rlZVSuxf1ZRnlZxFYAm93NnIKvR+MEHUgqBXx93wZGIUrSe'.
1367     'h+ctEgbpiMo3iQ4kioHCGxir/ZYUbr7AgPXs9bX0BCYM8vN/cPe8oQYzom3tVsSBMVHEoOJ5dm5F1RsIe9CtqGgRacCAkUvRtevT'.
1368     'e2pd6vOWF+gCuc/brcuhyARakBU9FgK5bUBWdHEH8tHpDsZnRTZQGzdLVvQ3CzyYZiTAmSIODEwzFCAdJopuvbpeZDisJ4pKEcjD'.
1369     'ijWPJhU1MjCo9dkYfiUVjQNTDKY6CVbR6A0niUSZjRwFanR0l9i/TyvGnFdqwStq5axMfDbyBksld/FUumvxS/Bd9VyJvQDWiiMx'.
1370     'iOsCHgAAAABJRU5ErkJggg==' ;
1371
1372        //==========================================================
1373        // openfolder.png
1374        //==========================================================
1375        $this->iBuiltinIcon[10][0]= 2040 ;
1376        $this->iBuiltinIcon[10][1]=
1377     'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAZiS0dEANAAtwClFht71AAAAAlwSFlz'.
1378     'AAALEAAACxABrSO9dQAAAAd0SU1FB9AKDQ4RIXMeaLcAAAd1SURBVHicxZd7jBXVHcc/58zcvTNzH8vusqw8FsTsKiCUUh5WBZXG'.
1379     'GkOptmqwNWsWLKXFGlEpzZI0AWNKSy0WhDS22gJKtWlTsSRqzYIuLGB2WVvDIwQMZQMsy2OFfdzde+/OnHP6x907vJaFpjb9JZM5'.
1380     'c85Mfp/f9/s7Jxn4P4e41gtSyp78WGvtfdEAcqDFYUOH9HS0NhGk9tPb/ilSyp789UUB2AMuqhQy3Uzm7HGkE6W3dTNZMRI3EcWO'.
1381     'jf9ClLmWBT3dzW8jUsevWHCG3UpWl+IkHSxnbDh/Mcz12NevBcuWXTmf6TjnXvJ88gDmVB3pw3+nt3UzHa1NqMzBS2zqPLGFjtMN'.
1382     'ZNr3XdW+qyqwZcFk76HX/tHWfuQvyO4W7qhaHwL8efkMRlRUpPv7rqD0RrJ+FgAjLy1a20OIxZJEEuNCRfIApj+om4bGM3u2/sYU'.
1383     '9J41d8973f3Dhg1pISTV1dXXBRNJxPGFCzhou+DCQrScZOkktNaeDZjamgeZ9MgiYmVDccvHhjAzJw0NTh8/alyZMaVJicp0iTHj'.
1384     'JpgNv38tjWUhhGROdbUL9W5/MH5XCkjlcibi+KIop5LVHLKEu8A/f4r286doa9pGrGwYAAsfqbbH3b8MgO/Nqgy6WvdbbXHMkEFJ'.
1385     '4xUOMVEvaTZu3BgmvF4Yk4hz9rO/Ulr5cE9owae/rcGxohSOuiWkC2IjcIqKyPZm+OmCH7GhoZEF077EEzVVweAbJ+riEeO0Ey8y'.
1386     'UubqOHn0AOgMwvf59txnBrSp9dgxKmf/+kIP1NY8SFk0jh5ajmNHAWg5b2E5EexojGHjbiVRMoRMNs0LC+Yz46vTuH3enN7BI8fr'.
1387     'qFdo0BoVZNC9aVSQ4fNjBzEmQJiARxb+/AqYPMAVB5FsPU5v37g9OxgLhe14ZM5/ju052E6MNZvf5pmHHuLmmWOkEysxUtpGAtme'.
1388     'dtHTflJkezqQto3jFRnLssyf1jydxiiM7zNnye/c3ZsqLu2BN5fcMfzrv/hby1tPzmRUoihcTJ87CwQI2yLtDcIqsIjYUf51qBlf'.
1389     'OnScOSrdQUOMURkiXsLUzJnvbGhoBGDHH5cGyZLhOpYoNl5hqYnYEXOu5fDl9eYAHntx98n8hFHZcPHUuTSxSASAeK/CGIOxJJ0f'.
1390     'bOGNPU280dgkq6Y2yu8vfjCIlwwzr+/ZQ/PHO0gOLuO5qsftDQ2NbN+4OCgqG6WTxWVaq6zpF+DiSHWnicdylp3r6aZTWthIOrNp'.
1391     'ktHcvBu0sHX1Sm6ozB3B42d90zZA9bQp7PvgPSzXZfnqX/HS4DKKK2+x69Y/HURs26iBAN5ccsfw7774UcumF37C6f07KSt2OHji'.
1392     'DEUJD0tISjyPrrSPlAKvN0JP/U4O1NfjuhG2rvklN1SOpfXwftpbTqAyKRrff5fb7rs9V1R7m4wlz2ihA3HpmXflUWyOH2umpLiY'.
1393     'ui3v8M+6bWzfsRNbSgqkxaCkiy0simMuEWEhpcRzIhQWOIAh6tiAwS4owInFiTou5dOnMnl2NR++ujBwXEc9terD6M43nrj6LgAB'.
1394     'QnDPA9/irtkP8JRS7Hr/3T6YekDQ1pEiEXOwpUVJzCVlZZFS4mZtkpEo9ChAkDp/jtLMBACy6S4RiQghLyv5cgBRPnKUOX6smUGF'.
1395     'hSil0MYw9d77mPy1e5mnFE3batm3czvb6nYgEJztSFGU9LCRlMRdUjIH0+lnEMIwPNXD3NumoVJnrMCJaiciMUZfvQnz4QcBSvV1'.
1396     'vjE5GK358t0zmXDnDB79saLpo20c+aSRD+t25JTp7GZQwsEWFiVxl6hlUf/WO9z32CxmL1rOe6u/I2KuwGhzLQCB7/sYY9Bah3el'.
1397     'FKbvrrVm4vS7GH/7ncx+chEHGz7myCeNbPtoO0JI2jq78WIRLGkzsqs7V5SfFV5EovXACoiqqsfNpk2vo5VCWtYFBfoU0VoTBAFa'.
1398     'a7TRaK2p+MoURk+cxMzq+Rzbv49DDbuo27UTW9h0dedssPxuK+kIfN8XxhgDYPVXf2Fh4XKtFIl4AiklAlBKAYRKKK36wHIweTCt'.
1399     'NfHiEkaOn8j0+7/BmDFjaT30GbHywSxcuZkpFfFg+m1jjZ/NmnVvNfRvwd69e8WBA/uNFAIh4JVXXmHsmDHE4vEQQgjQ2lxQIm9N'.
1400     'nz35q3BEOZOHzaG2thaA4mRU+L29It+IV21CpbRQfeMFC35gRB/M2rVrubnyZmLxWJhECBEmz/eHyo/7lMlH3LFFujsthNFCCGOu'.
1401     '+WNyeUgpjSVzMKtWraKyshLPdcPEeYWCIEBdpIxSivr6eta8vI7d6+cGnhdV06pe1QP+F/QXWmuRL+jZZ58LlVmxYgUVFRV4rhtu'.
1402     '4TzMxXAA6XRaRAtsYUkx8I/JtSJQOlSwpmZpCLN8+fPcdNNoHMfB9/0QJgRoP295TlR7UVv8xxZcHMuWIZ9/Hn35vG3JEGZpzVJG'.
1403     'jx5N1IlitKahsZE1L69j69qHgx+urFX/lQL9JYdLlfnZihUhzOLFi8N3Ml1dthOxVH/f/8/CtqSJ2JaJ2JZ59J7RPsC/AViJsQS/'.
1404     'dBntAAAAAElFTkSuQmCC' ;
1405
1406        //==========================================================
1407        // folder.png
1408        //==========================================================
1409        $this->iBuiltinIcon[11][0]= 1824 ;
1410        $this->iBuiltinIcon[11][1]=
1411     'iVBORw0KGgoAAAANSUhEUgAAACIAAAAiCAYAAAA6RwvCAAAABGdBTUEAALGPC/xhBQAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlz'.
1412     'AAALEAAACxABrSO9dQAAAAd0SU1FB9ECAQgFFyd9cRUAAAadSURBVHiczdhvbBP3Hcfx9/2xfefEOA5JoCNNnIT8AdtZmYBETJsI'.
1413     '6+jQOlQihT1AYgytqzZpD1atfyYqlT1h0lRpT7aRJ4NQpRvZGELVuo5Ua9jEJDIETQsNQyPBsUJMWGPnj//e+e72wNg4xElMR6ed'.
1414     'ZNln3933dZ/f93f6yfB/sgmrHdDV1WXlPg8NDZUDScD8LFFFEZZlWYZhWMFg0Orq6sq/gDJAfFy1iiZy9OjrVnj4JzQ1rMWqfxm/'.
1415     '309jYyNtbW0kEgnu3bvH4cOH88c/jqSKQl4/XGkd+eVtAN46up1LH92ktqYS++ZX8Pv9NDQ0sGnTJlKpFOFwmO7u7vy5IyMjeVRd'.
1416     'XV1+WEOh0IrY4pDnq6wXX/sTiCJaMkFZdRNqxefoe7VtCSqXVDqdZnZ2ltraWkzTpKqqijt3JpFlG7dvj7NzZ1f++qFQyA3EClHL'.
1417     'Ql743nFkhxPDtJAd5eTaYSVUfX09lZWVlJWVIUnSg7sVQMBCUcu4ceMGe/bsIRQK1QAzOcyykIM9P0KyudAyCWyqG8nhwqa4SkLt'.
1418     '3r0bVVVxu924XC40TUOWZUQxe97CwgIdHR2LMHIxSCaVInVvFElxE0vMY1Pd2NUKJMWNTXHlUfF//4vETJCelwbpFm3MjP2dt37x'.
1419     'AlN+PzU1NViWRSwW4+7du3g8HjweD4qi5EFAJzAExIpCANbooxhplfB0FJvTg6xWIqsVRVF6MopkU3FXPcnkJxGU0VEAdF2noqKC'.
1420     'W3/8DpnqLjzep2lubsblcjE8PExHR8fboVDID9xYFpLBDpJF0jDQIncQpWlkm31FlFLtp9PfyuW/vYQj1kPSuRW/38+lj27S2Q7v'.
1421     '/aWXUBVUffVNtm3blivVCEwsC5Eyc5iiApEpDEAXMqQdldhSiWVQHjJagud+8Fuexck/zv+K82dfoSbSCsDe75/km+4GVPd6+l5t'.
1422     '4zJHcqVUYN2yEEtZQDCSJCueRAYsPY49HsFIZVG6p25JUumFafT4DKJN4amtT7Nz38sk5+5A70HMtEYyMkFiZhxzjQ/poXrLQrRU'.
1423     'DFGEeFpAlkQkm4pRiCpIKodKzk0T/2QMh+piPjxKZPwiSkUtu/b9mNnJEWS7E8nhAmvpM60oJDkXJxqNozxRRUxPIesispBBlsXV'.
1424     'UaKEFo8gzoaJhz8s2lOmrpUG+WBhJ9/60g+Z+fDXTAXfxllRjl1VkO0OFATsYhYliiK21ZKKhhHnFveUqSdKgwAEOp7F2v51vvw8'.
1425     'XH7/N1wd/BlTweuUV65BdtgfoLTSkipsdD3tRi0VYpommUwGwzDwdT5HYEc3giAwcvH3jLz3BlPB67jWeZBEKYsSBWwpHZtNKo4q'.
1426     'aHTDsJeeiGEYWJaFZVmYpommaRiGQdPnv0bb1m8gSRL/vPIOV979aR4lmAJ2p4qCgCxksNuKJ6VNpx4NYhgGpmkuQhmGQTqdxjAM'.
1427     'qr2d7HtxEEEQuH1tkKvvvkF44tqDnrIcKJKAPf1g+LAUElq8dIiu60sApmnm93Pfzc7OYhgGrie+wFe++ztcLhcT1wf54PzPCU9c'.
1428     'w7XWjWS3IdsdOAUBWZAxrRJnTQ6SG5bce2FCpmkughmGQSqVYm5uDtnj44sH38TtdhP6+Dwf//V4ttHXrkGURZJaic8RgHQ6jWma'.
1429     'SJKUL5RLKNfIOczDKF3XSSaTRCIRhLJWntp3nGfWrSMxc5OLf3iNP4+68T9Ub9nF76lTpxgfHycajZJKpdA0LZ9GbjYV7hcDWZaF'.
1430     'pmnMz88Ti8UYunSLmu1HFi2aVkxkaGjINTY2ttDb24vX6+XQoUNs3ryZ8vJyIDu1BUFYkkxhgxeiWlpaOHPmDE1NTdTX1xe98eWG'.
1431     'JnF/9dQZCoXUYDA4AOD1ejlw4ACtra2Ul5fniwmCkEcUJiUIAoFAgL6+Pnw+H21tbfT39z8SxCS7hHsfWH9/8dL4MKqnp4eWlhac'.
1432     'TmcekEvMNE2am5s5ceIEgUCA9vZ2Tp48ic/nY3j4UsmQHCYOjJHtpeBKqL1799Lc3IzT6UTXdRobGxkYGKC9vZ3W1tZ8Ko86NJ8a'.
1433     'tXHjRo4dO8bp06fZsmULGzZsoL+/n0AggNfr5ezZs/8VpGTU5OSkc//+/acBfD4f1dXV7Nq1i4aGBs6dO4fP5+Pq1SuPBbIiyjTN'.
1434     'RUnV1dUNXLhwAa/Xy44dO4jFYgBEo9FFF1r134BPuYlk16LrAYXsAlmtq6sbKDwoFAp9m+ykuP5ZQVZF3f8tCdwCov8LyHIoAANI'.
1435     'AXf/A1TI0XCDh7OWAAAAAElFTkSuQmCC' ;
1436
1437        //==========================================================
1438        // file_important.png
1439        //==========================================================
1440        $this->iBuiltinIcon[12][0]= 1785 ;
1441        $this->iBuiltinIcon[12][1]=
1442     'iVBORw0KGgoAAAANSUhEUgAAACIAAAAiCAYAAAA6RwvCAAAABGdBTUEAALGPC/xhBQAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlz'.
1443     'AAALDwAACw8BkvkDpQAAAAd0SU1FB9ECDAcjDeD3lKsAAAZ2SURBVHicrZhPaFzHHcc/897s7lutJCsr2VHsOHWMk0MPbsBUrcnF'.
1444     'OFRdSo6FNhdB6SGHlpDmYtJCDyoxyKe6EBxKQkt7KKL0T6ABo0NbciqigtC6PhWKI2NFqqxdSd7V2/dmftPDvPd212t55dCBYfbN'.
1445     'zpvfZ77z+/1mdhUjytWrV93Hf/24eD5z9gwiMlDjOKbb7dLtdhER2u02u7u73Lp1CxEZBw4AeZwdNQqkMd9wbziFGINJUt6rRbz5'.
1446     '1ptUq1XK5TJBEAAUMHt7e+zu7gKwvLzMysoKwAng/uNg9CgQgFKlgg1DUJ67Vqtx6tQpZmdniaIIpRTOOZRSdDoddnZ2aLfbLC8v'.
1447     's7S0xJUrV7ZGwQSj1PhhfRodVdDlMrpc5vup5Z2fvMPdu3fZ29vDWjvwztjYGPV6nVqtRqVS4dKlSywtLQFsAdOH2XwsCEApg3jl'.
1448     'w98Rak2gvYjNZpNms0mSJDjnHgkDMDc3dySYQ0Ea8w139YUX0OUKulzyg7UmCEO+l1huvHuDra0t9vf3h1TJYSqVypFhHquIrlQI'.
1449     'S5qv/uIDAC7/4bcEQYAKvK+0Wq1DVQGIoog7d+4cCeaRII35hrt+8SsEOkRlUaEyR0UpFIrXHxyMVKVUKnHv3r0jwRwaNelBjBjL'.
1450     'Sz/7KYuLiwAsLi7y4z/9kY9e+TpkCuSqjI+Po7XuAWeKXLt2DWNMUZMkwRjDhQsXWFtbK6JpCCT3jfQgxomPtPX19YHWicM5x3c2'.
1451     '73Pj3Ru8/aO3mZqaolKpoHVvyuvXr/Ppnf/Q7uzz380NPtu4y/qnG+ztd1hfX2dtbQ3gIvDnRyqSxl1UoPjyz98D4PTp0wPtq39Z'.
1452     '4fdzLxegrVaLVqvF5OQkYRgWqpRKJZ77wvNsbW1RG5tgfKLOTH2G7Z1twqBQrgrMDvhInjfSOCY5iIv+hYWFgRZArEWsZWF941Bf'.
1453     'SdMUgMnJCWpjVU4cn+HUyePM1Gc4+fRUPkzBI5w1jbukcczLv/5l0XfmzJmBFuCba38r/CRXpT+CrDUoZ0jjB4RYonJAOYRobJKT'.
1454     'z5zgqfqxAbsFSH6mpHFM2qdGXh4VnoViD6mSJF2cTQeqDqBaKVHWmonJCWpZjhkC6anR5WsffTgwaHV1FaUUq6urA/2v3f5k4LnV'.
1455     'arG9tUn3oI2YBCcWHYAxMVYs1qZEZY2SFB2aYZDGfMN9d7uJiWPSeFiNo5Rclc3NTXZbO6RpF7EJVixYA9agwwDnUiqlEPdQ3imi'.
1456     'Jo27BGHIt/7x9yEjc3Nzh27Na7c/4TdffKl4bja3ae5MUIu0T/HOEIaOpJt4gwoSsVTK4SBIY77hFtY3ABBjBiZ90rKwvsH77/+K'.
1457     't37wOhO1iPpTk4SBw1mLsz6CnKQ4l3qV+kE+t9XHlNZOk+bUJLVIE1VCcIJWQmJ6qjj30NbcXLkZMt8YPig+Z3n1G5fZ39/j/vY2'.
1458     '9ckqZT2Ochbn0p4qNkU/dDfUADdXbh4HXgRO4zNdEU0XL1784PLly5w9e7Z4SazFOfGrEotDcOKrcoJPmrYIXf/Zop3QNd1skuGt'.
1459     'cUAb2MgAxvHZTgFUq1Wmp6eZnZ0F8JlTjDduDThBnDeECEoJtbGIp6enqEblzCcEZ1PECU4yVRiOGgd0gc+AB0CZvkv1sWPHOHfu'.
1460     'HOfPn8da41cpkkltEBEPJhYnBkTQJcdYVKGkgRxCfBsq5xXNgAa2Bn+hjTOgHEKBP8pzRUxykIH4ifLJRTJAl+UMBJzPHQ6bfe/f'.
1461     'cWIzPxlUpD+zugzIZtVk1d8znBAqRxgoQuVQgSJQ3h9C5QhDRYgjUILCAzlnEdsHYTKfMTEBcP7F54YUGVmc2GLlIn6ve6v0ahSt'.
1462     '8X25TzjJ+rIx1grKpQPWR4LkGVVsMgghvS0qjPdvm5OeceOTWA5Evo2mFzkjQfL7hZPUy5yvvF/uPFQL3+nbDmsLCEmT3sTmCTNr'.
1463     'rogT6yFsOix3ftw7OwQhkvSU6CuinhCk0+kAkFoBazEEICHaHHiPVmU0gnUp4EAc1mYrF0EBVpwPi34VrBkwPxKk3W5ju/e5/c+d'.
1464     'bGUHIAIuydTIE5zfc5Wr4lJcahHnHTP3CVGm78DrgY38N+DEibp7dmYKdAQmBh1hjEFjis+9CTWYGK21H6PxPyOI0DobYwzZF/z7'.
1465     '7jadTvJtYG0kCD7lfwl49ijgT1gc0AH+dZSJA/xB+Mz/GSIvFoj/B7H1mAd8CO/zAAAAAElFTkSuQmCC' ;
1466
1467        $this->iLen = count($this->iBuiltinIcon);
1468    }
1469}
1470
1471//===================================================
1472// Global cache for builtin images
1473//===================================================
1474$_gPredefIcons = new PredefIcons();
1475
1476//===================================================
1477// CLASS IconImage
1478// Description: Holds properties for an icon image
1479//===================================================
1480class IconImage {
1481    private $iGDImage=null;
1482    private $iWidth,$iHeight;
1483    private $ixalign='left',$iyalign='center';
1484    private $iScale=1.0;
1485
1486    function __construct($aIcon,$aScale=1) {
1487        GLOBAL $_gPredefIcons ;
1488        if( is_string($aIcon) ) {
1489            $this->iGDImage = Graph::LoadBkgImage('',$aIcon);
1490        }
1491        elseif( is_integer($aIcon) ) {
1492            // Builtin image
1493            $this->iGDImage = $_gPredefIcons->GetImg($aIcon);
1494        }
1495        else {
1496            JpGraphError::RaiseL(6011);
1497            //('Argument to IconImage must be string or integer');
1498        }
1499        $this->iScale = $aScale;
1500        $this->iWidth = Image::GetWidth($this->iGDImage);
1501        $this->iHeight = Image::GetHeight($this->iGDImage);
1502    }
1503
1504    function GetWidth() {
1505        return round($this->iScale*$this->iWidth);
1506    }
1507
1508    function GetHeight() {
1509        return round($this->iScale*$this->iHeight);
1510    }
1511
1512    function SetAlign($aX='left',$aY='center') {
1513        $this->ixalign = $aX;
1514        $this->iyalign = $aY;
1515    }
1516
1517    function Stroke($aImg,$x,$y) {
1518
1519        if( $this->ixalign == 'right' ) {
1520            $x -= $this->iWidth;
1521        }
1522        elseif( $this->ixalign == 'center' ) {
1523            $x -= round($this->iWidth/2*$this->iScale);
1524        }
1525
1526        if( $this->iyalign == 'bottom' ) {
1527            $y -= $this->iHeight;
1528        }
1529        elseif( $this->iyalign == 'center' ) {
1530            $y -= round($this->iHeight/2*$this->iScale);
1531        }
1532
1533        $aImg->Copy($this->iGDImage,
1534        			$x,$y,0,0,
1535        			round($this->iWidth*$this->iScale),round($this->iHeight*$this->iScale),
1536        			$this->iWidth,$this->iHeight);
1537    }
1538}
1539
1540
1541//===================================================
1542// CLASS TextProperty
1543// Description: Holds properties for a text
1544//===================================================
1545class TextProperty {
1546    public $iShow=true;
1547    public $csimtarget='',$csimwintarget='',$csimalt='';
1548    private $iFFamily=FF_FONT1,$iFStyle=FS_NORMAL,$iFSize=10;
1549    private $iFontArray=array();
1550    private $iColor="black";
1551    private $iText="";
1552    private $iHAlign="left",$iVAlign="bottom";
1553
1554    //---------------
1555    // CONSTRUCTOR
1556    function __construct($aTxt='') {
1557        $this->iText = $aTxt;
1558    }
1559
1560    //---------------
1561    // PUBLIC METHODS
1562    function Set($aTxt) {
1563        $this->iText = $aTxt;
1564    }
1565
1566    function SetCSIMTarget($aTarget,$aAltText='',$aWinTarget='') {
1567        if( is_string($aTarget) )
1568        $aTarget = array($aTarget);
1569        $this->csimtarget=$aTarget;
1570
1571        if( is_string($aWinTarget) )
1572        $aWinTarget = array($aWinTarget);
1573        $this->csimwintarget=$aWinTarget;
1574
1575        if( is_string($aAltText) )
1576        $aAltText = array($aAltText);
1577        $this->csimalt=$aAltText;
1578
1579    }
1580
1581    function SetCSIMAlt($aAltText) {
1582        if( is_string($aAltText) )
1583        $aAltText = array($aAltText);
1584        $this->csimalt=$aAltText;
1585    }
1586
1587    // Set text color
1588    function SetColor($aColor) {
1589        $this->iColor = $aColor;
1590    }
1591
1592    function HasTabs() {
1593        if( is_string($this->iText) ) {
1594            return substr_count($this->iText,"\t") > 0;
1595        }
1596        elseif( is_array($this->iText) ) {
1597            return false;
1598        }
1599    }
1600
1601    // Get number of tabs in string
1602    function GetNbrTabs() {
1603        if( is_string($this->iText) ) {
1604            return substr_count($this->iText,"\t") ;
1605        }
1606        else{
1607            return 0;
1608        }
1609    }
1610
1611    // Set alignment
1612    function Align($aHAlign,$aVAlign="bottom") {
1613        $this->iHAlign=$aHAlign;
1614        $this->iVAlign=$aVAlign;
1615    }
1616
1617    // Synonym
1618    function SetAlign($aHAlign,$aVAlign="bottom") {
1619        $this->iHAlign=$aHAlign;
1620        $this->iVAlign=$aVAlign;
1621    }
1622
1623    // Specify font
1624    function SetFont($aFFamily,$aFStyle=FS_NORMAL,$aFSize=10) {
1625        $this->iFFamily = $aFFamily;
1626        $this->iFStyle  = $aFStyle;
1627        $this->iFSize  = $aFSize;
1628    }
1629
1630    function SetColumnFonts($aFontArray) {
1631        if( !is_array($aFontArray) || count($aFontArray[0]) != 3 ) {
1632            JpGraphError::RaiseL(6033);
1633            // 'Array of fonts must contain arrays with 3 elements, i.e. (Family, Style, Size)'
1634        }
1635        $this->iFontArray = $aFontArray;
1636    }
1637
1638
1639    function IsColumns() {
1640        return is_array($this->iText) ;
1641    }
1642
1643    // Get width of text. If text contains several columns separated by
1644    // tabs then return both the total width as well as an array with a
1645    // width for each column.
1646    function GetWidth($aImg,$aUseTabs=false,$aTabExtraMargin=1.1) {
1647        $extra_margin=4;
1648        $aImg->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
1649        if( is_string($this->iText) ) {
1650            if( strlen($this->iText) == 0 ) return 0;
1651            $tmp = preg_split('/\t/',$this->iText);
1652            if( count($tmp) <= 1 || !$aUseTabs ) {
1653                $w = $aImg->GetTextWidth($this->iText);
1654                return $w + 2*$extra_margin;
1655            }
1656            else {
1657                $tot=0;
1658                $n = count($tmp);
1659                for($i=0; $i < $n; ++$i) {
1660                    $res[$i] = $aImg->GetTextWidth($tmp[$i]);
1661                    $tot += $res[$i]*$aTabExtraMargin;
1662                }
1663                return array(round($tot),$res);
1664            }
1665        }
1666        elseif( is_object($this->iText) ) {
1667            // A single icon
1668            return $this->iText->GetWidth()+2*$extra_margin;
1669        }
1670        elseif( is_array($this->iText) ) {
1671            // Must be an array of texts. In this case we return the sum of the
1672            // length + a fixed margin of 4 pixels on each text string
1673            $n = count($this->iText);
1674            $nf = count($this->iFontArray);
1675            for( $i=0, $w=0; $i < $n; ++$i ) {
1676                if( $i < $nf ) {
1677                    $aImg->SetFont($this->iFontArray[$i][0],$this->iFontArray[$i][1],$this->iFontArray[$i][2]);
1678                }
1679                else {
1680                    $aImg->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
1681                }
1682                $tmp = $this->iText[$i];
1683                if( is_string($tmp) ) {
1684                    $w += $aImg->GetTextWidth($tmp)+$extra_margin;
1685                }
1686                else {
1687                    if( is_object($tmp) === false ) {
1688                        JpGraphError::RaiseL(6012);
1689                    }
1690                    $w += $tmp->GetWidth()+$extra_margin;
1691                }
1692            }
1693            return $w;
1694        }
1695        else {
1696            JpGraphError::RaiseL(6012);
1697        }
1698    }
1699
1700    // for the case where we have multiple columns this function returns the width of each
1701    // column individually. If there is no columns just return the width of the single
1702    // column as an array of one
1703    function GetColWidth($aImg,$aMargin=0) {
1704        $aImg->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
1705        if( is_array($this->iText) ) {
1706            $n = count($this->iText);
1707            $nf = count($this->iFontArray);
1708            for( $i=0, $w=array(); $i < $n; ++$i ) {
1709                $tmp = $this->iText[$i];
1710                if( is_string($tmp) ) {
1711                    if( $i < $nf ) {
1712                        $aImg->SetFont($this->iFontArray[$i][0],$this->iFontArray[$i][1],$this->iFontArray[$i][2]);
1713                    }
1714                    else {
1715                        $aImg->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
1716                    }
1717                    $w[$i] = $aImg->GetTextWidth($tmp)+$aMargin;
1718                }
1719                else {
1720                    if( is_object($tmp) === false ) {
1721                        JpGraphError::RaiseL(6012);
1722                    }
1723                    $w[$i] = $tmp->GetWidth()+$aMargin;
1724                }
1725            }
1726            return $w;
1727        }
1728        else {
1729            return array($this->GetWidth($aImg));
1730        }
1731    }
1732
1733    // Get total height of text
1734    function GetHeight($aImg) {
1735        $nf = count($this->iFontArray);
1736        $maxheight = -1;
1737
1738        if( $nf > 0 ) {
1739            // We have to find out the largest font and take that one as the
1740            // height of the row
1741            for($i=0; $i < $nf; ++$i ) {
1742                $aImg->SetFont($this->iFontArray[$i][0],$this->iFontArray[$i][1],$this->iFontArray[$i][2]);
1743                $height = $aImg->GetFontHeight();
1744                $maxheight = max($height,$maxheight);
1745            }
1746        }
1747
1748        $aImg->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
1749        $height = $aImg->GetFontHeight();
1750        $maxheight = max($height,$maxheight);
1751        return $maxheight;
1752    }
1753
1754    // Unhide/hide the text
1755    function Show($aShow=true) {
1756        $this->iShow=$aShow;
1757    }
1758
1759    // Stroke text at (x,y) coordinates. If the text contains tabs then the
1760    // x parameter should be an array of positions to be used for each successive
1761    // tab mark. If no array is supplied then the tabs will be ignored.
1762    function Stroke($aImg,$aX,$aY) {
1763        if( $this->iShow ) {
1764            $aImg->SetColor($this->iColor);
1765            $aImg->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
1766            $aImg->SetTextAlign($this->iHAlign,$this->iVAlign);
1767            if( $this->GetNbrTabs() < 1 ) {
1768                if( is_string($this->iText) ) {
1769                    if( is_array($aX) ) $aX=$aX[0];
1770                    if( is_array($aY) ) $aY=$aY[0];
1771                    $aImg->StrokeText($aX,$aY,$this->iText);
1772                }
1773                elseif( is_array($this->iText) && ($n = count($this->iText)) > 0 ) {
1774                    $ax = is_array($aX) ;
1775                    $ay = is_array($aY) ;
1776                    if( $ax && $ay ) {
1777                        // Nothing; both are already arrays
1778                    }
1779                    elseif( $ax ) {
1780                        $aY = array_fill(0,$n,$aY);
1781                    }
1782                    elseif( $ay ) {
1783                        $aX = array_fill(0,$n,$aX);
1784                    }
1785                    else {
1786                        $aX = array_fill(0,$n,$aX);
1787                        $aY = array_fill(0,$n,$aY);
1788                    }
1789                    $n = min($n, count($aX) ) ;
1790                    $n = min($n, count($aY) ) ;
1791                    for($i=0; $i < $n; ++$i ) {
1792                        $tmp = $this->iText[$i];
1793                        if( is_object($tmp) ) {
1794                            $tmp->Stroke($aImg,$aX[$i],$aY[$i]);
1795                        }
1796                        else {
1797                            if( $i < count($this->iFontArray) ) {
1798                                $font = $this->iFontArray[$i];
1799                                $aImg->SetFont($font[0],$font[1],$font[2]);
1800                            }
1801                            else {
1802                                $aImg->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
1803                            }
1804                        	$aImg->StrokeText($aX[$i],$aY[$i],str_replace("\t"," ",$tmp));
1805                        }
1806                    }
1807                }
1808            }
1809            else {
1810                $tmp = preg_split('/\t/',$this->iText);
1811                $n = min(count($tmp),count($aX));
1812                for($i=0; $i < $n; ++$i) {
1813                    if( $i < count($this->iFontArray) ) {
1814                        $font = $this->iFontArray[$i];
1815                        $aImg->SetFont($font[0],$font[1],$font[2]);
1816                    }
1817                    else {
1818                        $aImg->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
1819                    }
1820                    $aImg->StrokeText($aX[$i],$aY,$tmp[$i]);
1821                }
1822            }
1823        }
1824    }
1825}
1826
1827//===================================================
1828// CLASS HeaderProperty
1829// Description: Data encapsulating class to hold property
1830// for each type of the scale headers
1831//===================================================
1832class HeaderProperty {
1833    public $grid;
1834    public $iShowLabels=true,$iShowGrid=true;
1835    public $iTitleVertMargin=3,$iFFamily=FF_FONT0,$iFStyle=FS_NORMAL,$iFSize=8;
1836    public $iStyle=0;
1837    public $iFrameColor="black",$iFrameWeight=1;
1838    public $iBackgroundColor="white";
1839    public $iWeekendBackgroundColor="lightgray",$iSundayTextColor="red"; // these are only used with day scale
1840    public $iTextColor="black";
1841    public $iLabelFormStr="%d";
1842    public $iIntervall = 1;
1843
1844    //---------------
1845    // CONSTRUCTOR
1846    function __construct() {
1847        $this->grid = new LineProperty();
1848    }
1849
1850    //---------------
1851    // PUBLIC METHODS
1852    function Show($aShow=true) {
1853        $this->iShowLabels = $aShow;
1854    }
1855
1856    function SetIntervall($aInt) {
1857    	$this->iIntervall = $aInt;
1858    }
1859
1860    function SetInterval($aInt) {
1861        $this->iIntervall = $aInt;
1862    }
1863
1864    function GetIntervall() {
1865        return $this->iIntervall ;
1866    }
1867
1868    function SetFont($aFFamily,$aFStyle=FS_NORMAL,$aFSize=10) {
1869        $this->iFFamily = $aFFamily;
1870        $this->iFStyle  = $aFStyle;
1871        $this->iFSize  = $aFSize;
1872    }
1873
1874    function SetFontColor($aColor) {
1875        $this->iTextColor = $aColor;
1876    }
1877
1878    function GetFontHeight($aImg) {
1879        $aImg->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
1880        return $aImg->GetFontHeight();
1881    }
1882
1883    function GetFontWidth($aImg) {
1884        $aImg->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
1885        return $aImg->GetFontWidth();
1886    }
1887
1888    function GetStrWidth($aImg,$aStr) {
1889        $aImg->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
1890        return $aImg->GetTextWidth($aStr);
1891    }
1892
1893    function SetStyle($aStyle) {
1894        $this->iStyle = $aStyle;
1895    }
1896
1897    function SetBackgroundColor($aColor) {
1898        $this->iBackgroundColor=$aColor;
1899    }
1900
1901    function SetFrameWeight($aWeight) {
1902        $this->iFrameWeight=$aWeight;
1903    }
1904
1905    function SetFrameColor($aColor) {
1906        $this->iFrameColor=$aColor;
1907    }
1908
1909    // Only used by day scale
1910    function SetWeekendColor($aColor) {
1911        $this->iWeekendBackgroundColor=$aColor;
1912    }
1913
1914    // Only used by day scale
1915    function SetSundayFontColor($aColor) {
1916        $this->iSundayTextColor=$aColor;
1917    }
1918
1919    function SetTitleVertMargin($aMargin) {
1920        $this->iTitleVertMargin=$aMargin;
1921    }
1922
1923    function SetLabelFormatString($aStr) {
1924        $this->iLabelFormStr=$aStr;
1925    }
1926
1927    function SetFormatString($aStr) {
1928        $this->SetLabelFormatString($aStr);
1929    }
1930
1931
1932}
1933
1934//===================================================
1935// CLASS GanttScale
1936// Description: Responsible for calculating and showing
1937// the scale in a gantt chart. This includes providing methods for
1938// converting dates to position in the chart as well as stroking the
1939// date headers (days, week, etc).
1940//===================================================
1941class GanttScale {
1942    public $minute,$hour,$day,$week,$month,$year;
1943    public $divider,$dividerh,$tableTitle;
1944    public $iStartDate=-1,$iEndDate=-1;
1945    // Number of gantt bar position (n.b not necessariliy the same as the number of bars)
1946    // we could have on bar in position 1, and one bar in position 5 then there are two
1947    // bars but the number of bar positions is 5
1948    public $actinfo;
1949    public $iTopPlotMargin=10,$iBottomPlotMargin=15;
1950    public $iVertLines=-1;
1951    public $iVertHeaderSize=-1;
1952    // The width of the labels (defaults to the widest of all labels)
1953    private $iLabelWidth;
1954    // Out image to stroke the scale to
1955    private $iImg;
1956    private $iTableHeaderBackgroundColor="white",$iTableHeaderFrameColor="black";
1957    private $iTableHeaderFrameWeight=1;
1958    private $iAvailableHeight=-1,$iVertSpacing=-1;
1959    private $iDateLocale;
1960    private $iVertLayout=GANTT_EVEN;
1961    private $iUsePlotWeekendBackground=true;
1962    private $iWeekStart = 1; // Default to have weekends start on Monday
1963
1964    //---------------
1965    // CONSTRUCTOR
1966    function __construct($aImg) {
1967        $this->iImg = $aImg;
1968        $this->iDateLocale = new DateLocale();
1969
1970        $this->minute = new HeaderProperty();
1971        $this->minute->SetIntervall(15);
1972        $this->minute->SetLabelFormatString('i');
1973        $this->minute->SetFont(FF_FONT0);
1974        $this->minute->grid->SetColor("gray");
1975
1976        $this->hour = new HeaderProperty();
1977        $this->hour->SetFont(FF_FONT0);
1978        $this->hour->SetIntervall(6);
1979        $this->hour->SetStyle(HOURSTYLE_HM24);
1980        $this->hour->SetLabelFormatString('H:i');
1981        $this->hour->grid->SetColor("gray");
1982
1983        $this->day = new HeaderProperty();
1984        $this->day->grid->SetColor("gray");
1985        $this->day->SetLabelFormatString('l');
1986
1987        $this->week = new HeaderProperty();
1988        $this->week->SetLabelFormatString("w%d");
1989        $this->week->SetFont(FF_FONT1);
1990
1991        $this->month = new HeaderProperty();
1992        $this->month->SetFont(FF_FONT1,FS_BOLD);
1993
1994        $this->year = new HeaderProperty();
1995        $this->year->SetFont(FF_FONT1,FS_BOLD);
1996
1997        $this->divider=new LineProperty();
1998        $this->dividerh=new LineProperty();
1999        $this->dividerh->SetWeight(2);
2000        $this->divider->SetWeight(6);
2001        $this->divider->SetColor('gray');
2002        $this->divider->SetStyle('fancy');
2003
2004        $this->tableTitle=new TextProperty();
2005        $this->tableTitle->Show(false);
2006        $this->actinfo = new GanttActivityInfo();
2007    }
2008
2009    //---------------
2010    // PUBLIC METHODS
2011    // Specify what headers should be visible
2012    function ShowHeaders($aFlg) {
2013        $this->day->Show($aFlg & GANTT_HDAY);
2014        $this->week->Show($aFlg & GANTT_HWEEK);
2015        $this->month->Show($aFlg & GANTT_HMONTH);
2016        $this->year->Show($aFlg & GANTT_HYEAR);
2017        $this->hour->Show($aFlg & GANTT_HHOUR);
2018        $this->minute->Show($aFlg & GANTT_HMIN);
2019
2020        // Make some default settings of gridlines whihc makes sense
2021        if( $aFlg & GANTT_HWEEK ) {
2022            $this->month->grid->Show(false);
2023            $this->year->grid->Show(false);
2024        }
2025        if( $aFlg & GANTT_HHOUR ) {
2026            $this->day->grid->SetColor("black");
2027        }
2028    }
2029
2030    // Should the weekend background stretch all the way down in the plotarea
2031    function UseWeekendBackground($aShow) {
2032        $this->iUsePlotWeekendBackground = $aShow;
2033    }
2034
2035    // Have a range been specified?
2036    function IsRangeSet() {
2037        return $this->iStartDate!=-1 && $this->iEndDate!=-1;
2038    }
2039
2040    // Should the layout be from top or even?
2041    function SetVertLayout($aLayout) {
2042        $this->iVertLayout = $aLayout;
2043    }
2044
2045    // Which locale should be used?
2046    function SetDateLocale($aLocale) {
2047        $this->iDateLocale->Set($aLocale);
2048    }
2049
2050    // Number of days we are showing
2051    function GetNumberOfDays() {
2052        return round(($this->iEndDate-$this->iStartDate)/SECPERDAY);
2053    }
2054
2055    // The width of the actual plot area
2056    function GetPlotWidth() {
2057        $img=$this->iImg;
2058        return $img->width - $img->left_margin - $img->right_margin;
2059    }
2060
2061    // Specify the width of the titles(labels) for the activities
2062    // (This is by default set to the minimum width enought for the
2063    // widest title)
2064    function SetLabelWidth($aLabelWidth) {
2065        $this->iLabelWidth=$aLabelWidth;
2066    }
2067
2068    // Which day should the week start?
2069    // 0==Sun, 1==Monday, 2==Tuesday etc
2070    function SetWeekStart($aStartDay) {
2071        $this->iWeekStart = $aStartDay % 7;
2072
2073        //Recalculate the startday since this will change the week start
2074        $this->SetRange($this->iStartDate,$this->iEndDate);
2075    }
2076
2077    // Do we show min scale?
2078    function IsDisplayMinute() {
2079        return $this->minute->iShowLabels;
2080    }
2081
2082    // Do we show day scale?
2083    function IsDisplayHour() {
2084        return $this->hour->iShowLabels;
2085    }
2086
2087
2088    // Do we show day scale?
2089    function IsDisplayDay() {
2090        return $this->day->iShowLabels;
2091    }
2092
2093    // Do we show week scale?
2094    function IsDisplayWeek() {
2095        return $this->week->iShowLabels;
2096    }
2097
2098    // Do we show month scale?
2099    function IsDisplayMonth() {
2100        return $this->month->iShowLabels;
2101    }
2102
2103    // Do we show year scale?
2104    function IsDisplayYear() {
2105        return $this->year->iShowLabels;
2106    }
2107
2108    // Specify spacing (in percent of bar height) between activity bars
2109    function SetVertSpacing($aSpacing) {
2110        $this->iVertSpacing = $aSpacing;
2111    }
2112
2113    // Specify scale min and max date either as timestamp or as date strings
2114    // Always round to the nearest week boundary
2115    function SetRange($aMin,$aMax) {
2116        $this->iStartDate = $this->NormalizeDate($aMin);
2117        $this->iEndDate = $this->NormalizeDate($aMax);
2118    }
2119
2120
2121    // Adjust the start and end date so they fit to beginning/ending
2122    // of the week taking the specified week start day into account.
2123    function AdjustStartEndDay() {
2124
2125        if( !($this->IsDisplayYear() ||$this->IsDisplayMonth() || $this->IsDisplayWeek()) ) {
2126            // Don't adjust
2127            return;
2128        }
2129
2130        // Get day in week for start and ending date (Sun==0)
2131        $ds=strftime("%w",$this->iStartDate);
2132        $de=strftime("%w",$this->iEndDate);
2133
2134        // We want to start on iWeekStart day. But first we subtract a week
2135        // if the startdate is "behind" the day the week start at.
2136        // This way we ensure that the given start date is always included
2137        // in the range. If we don't do this the nearest correct weekday in the week
2138        // to start at might be later than the start date.
2139        if( $ds < $this->iWeekStart )
2140        $d = strtotime('-7 day',$this->iStartDate);
2141        else
2142        $d = $this->iStartDate;
2143        $adjdate = strtotime(($this->iWeekStart-$ds).' day',$d /*$this->iStartDate*/ );
2144        $this->iStartDate = $adjdate;
2145
2146        // We want to end on the last day of the week
2147        $preferredEndDay = ($this->iWeekStart+6)%7;
2148        if( $preferredEndDay != $de ) {
2149            // Solve equivalence eq:    $de + x ~ $preferredDay (mod 7)
2150            $adj = (7+($preferredEndDay - $de)) % 7;
2151            $adjdate = strtotime("+$adj day",$this->iEndDate);
2152            $this->iEndDate = $adjdate;
2153        }
2154    }
2155
2156    // Specify background for the table title area (upper left corner of the table)
2157    function SetTableTitleBackground($aColor) {
2158        $this->iTableHeaderBackgroundColor = $aColor;
2159    }
2160
2161    ///////////////////////////////////////
2162    // PRIVATE Methods
2163
2164    // Determine the height of all the scale headers combined
2165    function GetHeaderHeight() {
2166        $img=$this->iImg;
2167        $height=1;
2168        if( $this->minute->iShowLabels ) {
2169            $height += $this->minute->GetFontHeight($img);
2170            $height += $this->minute->iTitleVertMargin;
2171        }
2172        if( $this->hour->iShowLabels ) {
2173            $height += $this->hour->GetFontHeight($img);
2174            $height += $this->hour->iTitleVertMargin;
2175        }
2176        if( $this->day->iShowLabels ) {
2177            $height += $this->day->GetFontHeight($img);
2178            $height += $this->day->iTitleVertMargin;
2179        }
2180        if( $this->week->iShowLabels ) {
2181            $height += $this->week->GetFontHeight($img);
2182            $height += $this->week->iTitleVertMargin;
2183        }
2184        if( $this->month->iShowLabels ) {
2185            $height += $this->month->GetFontHeight($img);
2186            $height += $this->month->iTitleVertMargin;
2187        }
2188        if( $this->year->iShowLabels ) {
2189            $height += $this->year->GetFontHeight($img);
2190            $height += $this->year->iTitleVertMargin;
2191        }
2192        return $height;
2193    }
2194
2195    // Get width (in pixels) for a single day
2196    function GetDayWidth() {
2197        return ($this->GetPlotWidth()-$this->iLabelWidth+1)/$this->GetNumberOfDays();
2198    }
2199
2200    // Get width (in pixels) for a single hour
2201    function GetHourWidth() {
2202        return $this->GetDayWidth() / 24 ;
2203    }
2204
2205    function GetMinuteWidth() {
2206        return $this->GetHourWidth() / 60 ;
2207    }
2208
2209    // Nuber of days in a year
2210    function GetNumDaysInYear($aYear) {
2211        if( $this->IsLeap($aYear) )
2212        return 366;
2213        else
2214        return 365;
2215    }
2216
2217    // Get week number
2218    function GetWeekNbr($aDate,$aSunStart=true) {
2219        // We can't use the internal strftime() since it gets the weeknumber
2220        // wrong since it doesn't follow ISO on all systems since this is
2221        // system linrary dependent.
2222        // Even worse is that this works differently if we are on a Windows
2223        // or UNIX box (it even differs between UNIX boxes how strftime()
2224        // is natively implemented)
2225        //
2226        // Credit to Nicolas Hoizey <nhoizey@phpheaven.net> for this elegant
2227        // version of Week Nbr calculation.
2228
2229        $day = $this->NormalizeDate($aDate);
2230        if( $aSunStart )
2231        $day += 60*60*24;
2232
2233        /*-------------------------------------------------------------------------
2234         According to ISO-8601 :
2235         "Week 01 of a year is per definition the first week that has the Thursday in this year,
2236         which is equivalent to the week that contains the fourth day of January.
2237         In other words, the first week of a new year is the week that has the majority of its
2238         days in the new year."
2239
2240         Be carefull, with PHP, -3 % 7 = -3, instead of 4 !!!
2241
2242         day of year             = date("z", $day) + 1
2243         offset to thursday      = 3 - (date("w", $day) + 6) % 7
2244         first thursday of year  = 1 + (11 - date("w", mktime(0, 0, 0, 1, 1, date("Y", $day)))) % 7
2245         week number             = (thursday's day of year - first thursday's day of year) / 7 + 1
2246         ---------------------------------------------------------------------------*/
2247
2248        $thursday = $day + 60 * 60 * 24 * (3 - (date("w", $day) + 6) % 7);              // take week's thursday
2249        $week = 1 + (date("z", $thursday) - (11 - date("w", mktime(0, 0, 0, 1, 1, date("Y", $thursday)))) % 7) / 7;
2250
2251        return $week;
2252    }
2253
2254    // Is year a leap year?
2255    function IsLeap($aYear) {
2256        // Is the year a leap year?
2257        //$year = 0+date("Y",$aDate);
2258        if( $aYear % 4 == 0)
2259        if( !($aYear % 100 == 0) || ($aYear % 400 == 0) )
2260        return true;
2261        return false;
2262    }
2263
2264    // Get current year
2265    function GetYear($aDate) {
2266        return 0+Date("Y",$aDate);
2267    }
2268
2269    // Return number of days in a year
2270    function GetNumDaysInMonth($aMonth,$aYear) {
2271        $days=array(31,28,31,30,31,30,31,31,30,31,30,31);
2272        $daysl=array(31,29,31,30,31,30,31,31,30,31,30,31);
2273        if( $this->IsLeap($aYear))
2274        return $daysl[$aMonth];
2275        else
2276        return $days[$aMonth];
2277    }
2278
2279    // Get day in month
2280    function GetMonthDayNbr($aDate) {
2281        return 0+strftime("%d",$aDate);
2282    }
2283
2284    // Get day in year
2285    function GetYearDayNbr($aDate) {
2286        return 0+strftime("%j",$aDate);
2287    }
2288
2289    // Get month number
2290    function GetMonthNbr($aDate) {
2291        return 0+strftime("%m",$aDate);
2292    }
2293
2294    // Translate a date to screen coordinates (horizontal scale)
2295    function TranslateDate($aDate) {
2296        //
2297        // In order to handle the problem with Daylight savings time
2298        // the scale written with equal number of seconds per day beginning
2299        // with the start date. This means that we "cement" the state of
2300        // DST as it is in the start date. If later the scale includes the
2301        // switchover date (depends on the locale) we need to adjust back
2302        // if the date we try to translate has a different DST status since
2303        // we would otherwise be off by one hour.
2304        $aDate = $this->NormalizeDate($aDate);
2305        $tmp = localtime($aDate);
2306        $cloc = $tmp[8];
2307        $tmp = localtime($this->iStartDate);
2308        $sloc = $tmp[8];
2309        $offset = 0;
2310        if( $sloc != $cloc) {
2311            if( $sloc )
2312            $offset = 3600;
2313            else
2314            $offset = -3600;
2315        }
2316        $img=$this->iImg;
2317        return ($aDate-$this->iStartDate-$offset)/SECPERDAY*$this->GetDayWidth()+$img->left_margin+$this->iLabelWidth;;
2318    }
2319
2320    // Get screen coordinatesz for the vertical position for a bar
2321    function TranslateVertPos($aPos,$atTop=false) {
2322        $img=$this->iImg;
2323        if( $aPos > $this->iVertLines )
2324        	JpGraphError::RaiseL(6015,$aPos);
2325        // 'Illegal vertical position %d'
2326        if( $this->iVertLayout == GANTT_EVEN ) {
2327            // Position the top bar at 1 vert spacing from the scale
2328            $pos =  round($img->top_margin + $this->iVertHeaderSize +  ($aPos+1)*$this->iVertSpacing);
2329        }
2330        else {
2331            // position the top bar at 1/2 a vert spacing from the scale
2332            $pos = round($img->top_margin + $this->iVertHeaderSize  + $this->iTopPlotMargin + ($aPos+1)*$this->iVertSpacing);
2333        }
2334
2335        if( $atTop )
2336            $pos -= $this->iVertSpacing;
2337
2338        return $pos;
2339    }
2340
2341    // What is the vertical spacing?
2342    function GetVertSpacing() {
2343        return $this->iVertSpacing;
2344    }
2345
2346    // Convert a date to timestamp
2347    function NormalizeDate($aDate) {
2348        if( $aDate === false ) return false;
2349        if( is_string($aDate) ) {
2350            $t = strtotime($aDate);
2351            if( $t === FALSE || $t === -1 ) {
2352                JpGraphError::RaiseL(6016,$aDate);
2353                //("Date string ($aDate) specified for Gantt activity can not be interpretated. Please make sure it is a valid time string, e.g. 2005-04-23 13:30");
2354            }
2355            return $t;
2356        }
2357        elseif( is_int($aDate) || is_float($aDate) )
2358            return $aDate;
2359        else
2360            JpGraphError::RaiseL(6017,$aDate);
2361        //Unknown date format in GanttScale ($aDate).");
2362    }
2363
2364
2365    // Convert a time string to minutes
2366
2367    function TimeToMinutes($aTimeString) {
2368        // Split in hours and minutes
2369        $pos=strpos($aTimeString,':');
2370        $minint=60;
2371        if( $pos === false ) {
2372            $hourint = $aTimeString;
2373            $minint = 0;
2374        }
2375        else {
2376            $hourint = floor(substr($aTimeString,0,$pos));
2377            $minint = floor(substr($aTimeString,$pos+1));
2378        }
2379        $minint += 60 * $hourint;
2380        return $minint;
2381    }
2382
2383    // Stroke the day scale (including gridlines)
2384    function StrokeMinutes($aYCoord,$getHeight=false) {
2385        $img=$this->iImg;
2386        $xt=$img->left_margin+$this->iLabelWidth;
2387        $yt=$aYCoord+$img->top_margin;
2388        if( $this->minute->iShowLabels ) {
2389            $img->SetFont($this->minute->iFFamily,$this->minute->iFStyle,$this->minute->iFSize);
2390            $yb = $yt + $img->GetFontHeight() +
2391            $this->minute->iTitleVertMargin + $this->minute->iFrameWeight;
2392            if( $getHeight ) {
2393                return $yb - $img->top_margin;
2394            }
2395            $xb = $img->width-$img->right_margin+1;
2396            $img->SetColor($this->minute->iBackgroundColor);
2397            $img->FilledRectangle($xt,$yt,$xb,$yb);
2398
2399            $x = $xt;
2400            $img->SetTextAlign("center");
2401            $day = date('w',$this->iStartDate);
2402            $minint = $this->minute->GetIntervall() ;
2403
2404            if( 60 % $minint !== 0 ) {
2405                JpGraphError::RaiseL(6018,$minint);
2406                //'Intervall for minutes must divide the hour evenly, e.g. 1,5,10,12,15,20,30 etc You have specified an intervall of '.$minint.' minutes.');
2407            }
2408
2409
2410            $n = 60 / $minint;
2411            $datestamp = $this->iStartDate;
2412            $width = $this->GetHourWidth() / $n ;
2413            if( $width < 8 ) {
2414                // TO small width to draw minute scale
2415                JpGraphError::RaiseL(6019,$width);
2416                //('The available width ('.$width.') for minutes are to small for this scale to be displayed. Please use auto-sizing or increase the width of the graph.');
2417            }
2418
2419            $nh = ceil(24*60 / $this->TimeToMinutes($this->hour->GetIntervall()) );
2420            $nd = $this->GetNumberOfDays();
2421            // Convert to intervall to seconds
2422            $minint *= 60;
2423            for($j=0; $j < $nd; ++$j, $day += 1, $day %= 7) {
2424                for( $k=0; $k < $nh; ++$k ) {
2425                    for($i=0; $i < $n ;++$i, $x+=$width, $datestamp += $minint ) {
2426                        if( $day==6 || $day==0 ) {
2427
2428                            $img->PushColor($this->day->iWeekendBackgroundColor);
2429                            if( $this->iUsePlotWeekendBackground )
2430                            $img->FilledRectangle($x,$yt+$this->day->iFrameWeight,$x+$width,$img->height-$img->bottom_margin);
2431                            else
2432                            $img->FilledRectangle($x,$yt+$this->day->iFrameWeight,$x+$width,$yb-$this->day->iFrameWeight);
2433                            $img->PopColor();
2434
2435                        }
2436
2437                        if( $day==0 )
2438                        $img->SetColor($this->day->iSundayTextColor);
2439                        else
2440                        $img->SetColor($this->day->iTextColor);
2441
2442                        switch( $this->minute->iStyle ) {
2443                            case MINUTESTYLE_CUSTOM:
2444                                $txt = date($this->minute->iLabelFormStr,$datestamp);
2445                                break;
2446                            case MINUTESTYLE_MM:
2447                            default:
2448                                // 15
2449                                $txt = date('i',$datestamp);
2450                                break;
2451                        }
2452                        $img->StrokeText(round($x+$width/2),round($yb-$this->minute->iTitleVertMargin),$txt);
2453
2454                        // Fix a rounding problem the wrong way ..
2455                        // If we also have hour scale then don't draw the firsta or last
2456                        // gridline since that will be overwritten by the hour scale gridline if such exists.
2457                        // However, due to the propagation of rounding of the 'x+=width' term in the loop
2458                        // this might sometimes be one pixel of so we fix this by not drawing it.
2459                        // The proper way to fix it would be to re-calculate the scale for each step and
2460                        // not using the additive term.
2461                        if( !(($i == $n || $i==0) && $this->hour->iShowLabels && $this->hour->grid->iShow) ) {
2462                            $img->SetColor($this->minute->grid->iColor);
2463                            $img->SetLineWeight($this->minute->grid->iWeight);
2464                            $img->Line($x,$yt,$x,$yb);
2465                            $this->minute->grid->Stroke($img,$x,$yb,$x,$img->height-$img->bottom_margin);
2466                        }
2467                    }
2468                }
2469            }
2470            $img->SetColor($this->minute->iFrameColor);
2471            $img->SetLineWeight($this->minute->iFrameWeight);
2472            $img->Rectangle($xt,$yt,$xb,$yb);
2473            return $yb - $img->top_margin;
2474        }
2475        return $aYCoord;
2476    }
2477
2478    // Stroke the day scale (including gridlines)
2479    function StrokeHours($aYCoord,$getHeight=false) {
2480        $img=$this->iImg;
2481        $xt=$img->left_margin+$this->iLabelWidth;
2482        $yt=$aYCoord+$img->top_margin;
2483        if( $this->hour->iShowLabels ) {
2484            $img->SetFont($this->hour->iFFamily,$this->hour->iFStyle,$this->hour->iFSize);
2485            $yb = $yt + $img->GetFontHeight() +
2486            $this->hour->iTitleVertMargin + $this->hour->iFrameWeight;
2487            if( $getHeight ) {
2488                return $yb - $img->top_margin;
2489            }
2490            $xb = $img->width-$img->right_margin+1;
2491            $img->SetColor($this->hour->iBackgroundColor);
2492            $img->FilledRectangle($xt,$yt,$xb,$yb);
2493
2494            $x = $xt;
2495            $img->SetTextAlign("center");
2496            $tmp = $this->hour->GetIntervall() ;
2497            $minint = $this->TimeToMinutes($tmp);
2498            if( 1440 % $minint !== 0 ) {
2499                JpGraphError::RaiseL(6020,$tmp);
2500                //('Intervall for hours must divide the day evenly, e.g. 0:30, 1:00, 1:30, 4:00 etc. You have specified an intervall of '.$tmp);
2501            }
2502
2503            $n = ceil(24*60 / $minint );
2504            $datestamp = $this->iStartDate;
2505            $day = date('w',$this->iStartDate);
2506            $doback = !$this->minute->iShowLabels;
2507            $width = $this->GetDayWidth() / $n ;
2508            for($j=0; $j < $this->GetNumberOfDays(); ++$j, $day += 1,$day %= 7) {
2509                for($i=0; $i < $n ;++$i, $x+=$width) {
2510                    if( $day==6 || $day==0 ) {
2511
2512                        $img->PushColor($this->day->iWeekendBackgroundColor);
2513                        if( $this->iUsePlotWeekendBackground && $doback )
2514                        $img->FilledRectangle($x,$yt+$this->day->iFrameWeight,$x+$width,$img->height-$img->bottom_margin);
2515                        else
2516                        $img->FilledRectangle($x,$yt+$this->day->iFrameWeight,$x+$width,$yb-$this->day->iFrameWeight);
2517                        $img->PopColor();
2518
2519                    }
2520
2521                    if( $day==0 )
2522                    $img->SetColor($this->day->iSundayTextColor);
2523                    else
2524                    $img->SetColor($this->day->iTextColor);
2525
2526                    switch( $this->hour->iStyle ) {
2527                        case HOURSTYLE_HMAMPM:
2528                            // 1:35pm
2529                            $txt = date('g:ia',$datestamp);
2530                            break;
2531                        case HOURSTYLE_H24:
2532                            // 13
2533                            $txt = date('H',$datestamp);
2534                            break;
2535                        case HOURSTYLE_HAMPM:
2536                            $txt = date('ga',$datestamp);
2537                            break;
2538                        case HOURSTYLE_CUSTOM:
2539                            $txt = date($this->hour->iLabelFormStr,$datestamp);
2540                            break;
2541                        case HOURSTYLE_HM24:
2542                        default:
2543                            $txt = date('H:i',$datestamp);
2544                            break;
2545                    }
2546                    $img->StrokeText(round($x+$width/2),round($yb-$this->hour->iTitleVertMargin),$txt);
2547                    $img->SetColor($this->hour->grid->iColor);
2548                    $img->SetLineWeight($this->hour->grid->iWeight);
2549                    $img->Line($x,$yt,$x,$yb);
2550                    $this->hour->grid->Stroke($img,$x,$yb,$x,$img->height-$img->bottom_margin);
2551                    //$datestamp += $minint*60
2552                    $datestamp = mktime(date('H',$datestamp),date('i',$datestamp)+$minint,0,
2553                    date("m",$datestamp),date("d",$datestamp)+1,date("Y",$datestamp));
2554
2555                }
2556            }
2557            $img->SetColor($this->hour->iFrameColor);
2558            $img->SetLineWeight($this->hour->iFrameWeight);
2559            $img->Rectangle($xt,$yt,$xb,$yb);
2560            return $yb - $img->top_margin;
2561        }
2562        return $aYCoord;
2563    }
2564
2565
2566    // Stroke the day scale (including gridlines)
2567    function StrokeDays($aYCoord,$getHeight=false) {
2568        $img=$this->iImg;
2569        $daywidth=$this->GetDayWidth();
2570        $xt=$img->left_margin+$this->iLabelWidth;
2571        $yt=$aYCoord+$img->top_margin;
2572        if( $this->day->iShowLabels ) {
2573            $img->SetFont($this->day->iFFamily,$this->day->iFStyle,$this->day->iFSize);
2574            $yb=$yt + $img->GetFontHeight() + $this->day->iTitleVertMargin + $this->day->iFrameWeight;
2575            if( $getHeight ) {
2576                return $yb - $img->top_margin;
2577            }
2578            $xb=$img->width-$img->right_margin+1;
2579            $img->SetColor($this->day->iBackgroundColor);
2580            $img->FilledRectangle($xt,$yt,$xb,$yb);
2581
2582            $x = $xt;
2583            $img->SetTextAlign("center");
2584            $day = date('w',$this->iStartDate);
2585            $datestamp = $this->iStartDate;
2586
2587            $doback = !($this->hour->iShowLabels || $this->minute->iShowLabels);
2588
2589            setlocale(LC_TIME,$this->iDateLocale->iLocale);
2590
2591            for($i=0; $i < $this->GetNumberOfDays(); ++$i, $x+=$daywidth, $day += 1,$day %= 7) {
2592                if( $day==6 || $day==0 ) {
2593                    $img->SetColor($this->day->iWeekendBackgroundColor);
2594                    if( $this->iUsePlotWeekendBackground && $doback)
2595                        $img->FilledRectangle($x,$yt+$this->day->iFrameWeight,
2596                                              $x+$daywidth,$img->height-$img->bottom_margin);
2597                    else
2598                        $img->FilledRectangle($x,$yt+$this->day->iFrameWeight,
2599                    $x+$daywidth,$yb-$this->day->iFrameWeight);
2600                }
2601
2602                $mn = strftime('%m',$datestamp);
2603                if( $mn[0]=='0' )
2604                    $mn = $mn[1];
2605
2606                switch( $this->day->iStyle ) {
2607                    case DAYSTYLE_LONG:
2608                        // "Monday"
2609                        $txt = strftime('%A',$datestamp);
2610                        break;
2611                    case DAYSTYLE_SHORT:
2612                        // "Mon"
2613                        $txt = strftime('%a',$datestamp);
2614                        break;
2615                    case DAYSTYLE_SHORTDAYDATE1:
2616                        // "Mon 23/6"
2617                        $txt = strftime('%a %d/'.$mn,$datestamp);
2618                        break;
2619                    case DAYSTYLE_SHORTDAYDATE2:
2620                        // "Mon 23 Jun"
2621                        $txt = strftime('%a %d %b',$datestamp);
2622                        break;
2623                    case DAYSTYLE_SHORTDAYDATE3:
2624                        // "Mon 23 Jun 2003"
2625                        $txt = strftime('%a %d %b %Y',$datestamp);
2626                        break;
2627                    case DAYSTYLE_LONGDAYDATE1:
2628                        // "Monday 23 Jun"
2629                        $txt = strftime('%A %d %b',$datestamp);
2630                        break;
2631                    case DAYSTYLE_LONGDAYDATE2:
2632                        // "Monday 23 Jun 2003"
2633                        $txt = strftime('%A %d %b %Y',$datestamp);
2634                        break;
2635                    case DAYSTYLE_SHORTDATE1:
2636                        // "23/6"
2637                        $txt = strftime('%d/'.$mn,$datestamp);
2638                        break;
2639                    case DAYSTYLE_SHORTDATE2:
2640                        // "23 Jun"
2641                        $txt = strftime('%d %b',$datestamp);
2642                        break;
2643                    case DAYSTYLE_SHORTDATE3:
2644                        // "Mon 23"
2645                        $txt = strftime('%a %d',$datestamp);
2646                        break;
2647                    case DAYSTYLE_SHORTDATE4:
2648                        // "23"
2649                        $txt = strftime('%d',$datestamp);
2650                        break;
2651                    case DAYSTYLE_CUSTOM:
2652                        // Custom format
2653                        $txt = strftime($this->day->iLabelFormStr,$datestamp);
2654                        break;
2655                    case DAYSTYLE_ONELETTER:
2656                    default:
2657                        // "M"
2658                        $txt = strftime('%A',$datestamp);
2659                        $txt = strtoupper($txt[0]);
2660                        break;
2661                }
2662
2663                if( $day==0 )
2664                    $img->SetColor($this->day->iSundayTextColor);
2665                else
2666                    $img->SetColor($this->day->iTextColor);
2667                $img->StrokeText(round($x+$daywidth/2+1),
2668                round($yb-$this->day->iTitleVertMargin),$txt);
2669                $img->SetColor($this->day->grid->iColor);
2670                $img->SetLineWeight($this->day->grid->iWeight);
2671                $img->Line($x,$yt,$x,$yb);
2672                $this->day->grid->Stroke($img,$x,$yb,$x,$img->height-$img->bottom_margin);
2673                $datestamp = mktime(0,0,0,date("m",$datestamp),date("d",$datestamp)+1,date("Y",$datestamp));
2674                //$datestamp += SECPERDAY;
2675
2676            }
2677            $img->SetColor($this->day->iFrameColor);
2678            $img->SetLineWeight($this->day->iFrameWeight);
2679            $img->Rectangle($xt,$yt,$xb,$yb);
2680            return $yb - $img->top_margin;
2681        }
2682        return $aYCoord;
2683    }
2684
2685    // Stroke week header and grid
2686    function StrokeWeeks($aYCoord,$getHeight=false) {
2687        if( $this->week->iShowLabels ) {
2688            $img=$this->iImg;
2689            $yt=$aYCoord+$img->top_margin;
2690            $img->SetFont($this->week->iFFamily,$this->week->iFStyle,$this->week->iFSize);
2691            $yb=$yt + $img->GetFontHeight() + $this->week->iTitleVertMargin + $this->week->iFrameWeight;
2692
2693            if( $getHeight ) {
2694                return $yb - $img->top_margin;
2695            }
2696
2697            $xt=$img->left_margin+$this->iLabelWidth;
2698            $weekwidth=$this->GetDayWidth()*7;
2699            $wdays=$this->iDateLocale->GetDayAbb();
2700            $xb=$img->width-$img->right_margin+1;
2701            $week = $this->iStartDate;
2702            $weeknbr=$this->GetWeekNbr($week);
2703            $img->SetColor($this->week->iBackgroundColor);
2704            $img->FilledRectangle($xt,$yt,$xb,$yb);
2705            $img->SetColor($this->week->grid->iColor);
2706            $x = $xt;
2707            if( $this->week->iStyle==WEEKSTYLE_WNBR ) {
2708                $img->SetTextAlign("center");
2709                $txtOffset = $weekwidth/2+1;
2710            }
2711            elseif( $this->week->iStyle==WEEKSTYLE_FIRSTDAY  ||
2712                    $this->week->iStyle==WEEKSTYLE_FIRSTDAY2 ||
2713                    $this->week->iStyle==WEEKSTYLE_FIRSTDAYWNBR ||
2714                    $this->week->iStyle==WEEKSTYLE_FIRSTDAY2WNBR ) {
2715                $img->SetTextAlign("left");
2716                $txtOffset = 3;
2717            }
2718            else {
2719                JpGraphError::RaiseL(6021);
2720                //("Unknown formatting style for week.");
2721            }
2722
2723            for($i=0; $i<$this->GetNumberOfDays()/7; ++$i, $x+=$weekwidth) {
2724                $img->PushColor($this->week->iTextColor);
2725
2726                if( $this->week->iStyle==WEEKSTYLE_WNBR )
2727                    $txt = sprintf($this->week->iLabelFormStr,$weeknbr);
2728                elseif( $this->week->iStyle==WEEKSTYLE_FIRSTDAY ||
2729                        $this->week->iStyle==WEEKSTYLE_FIRSTDAYWNBR )
2730                    $txt = date("j/n",$week);
2731                elseif( $this->week->iStyle==WEEKSTYLE_FIRSTDAY2 ||
2732                        $this->week->iStyle==WEEKSTYLE_FIRSTDAY2WNBR ) {
2733                    $monthnbr = date("n",$week)-1;
2734                    $shortmonth = $this->iDateLocale->GetShortMonthName($monthnbr);
2735                    $txt = Date("j",$week)." ".$shortmonth;
2736                }
2737
2738                if( $this->week->iStyle==WEEKSTYLE_FIRSTDAYWNBR ||
2739                $this->week->iStyle==WEEKSTYLE_FIRSTDAY2WNBR ) {
2740                    $w = sprintf($this->week->iLabelFormStr,$weeknbr);
2741                    $txt .= ' '.$w;
2742                }
2743
2744                $img->StrokeText(round($x+$txtOffset),
2745                round($yb-$this->week->iTitleVertMargin),$txt);
2746
2747                $week = strtotime('+7 day',$week);
2748                $weeknbr = $this->GetWeekNbr($week);
2749                $img->PopColor();
2750                $img->SetLineWeight($this->week->grid->iWeight);
2751                $img->Line($x,$yt,$x,$yb);
2752                $this->week->grid->Stroke($img,$x,$yb,$x,$img->height-$img->bottom_margin);
2753            }
2754            $img->SetColor($this->week->iFrameColor);
2755            $img->SetLineWeight($this->week->iFrameWeight);
2756            $img->Rectangle($xt,$yt,$xb,$yb);
2757            return $yb-$img->top_margin;
2758        }
2759        return $aYCoord;
2760    }
2761
2762    // Format the mont scale header string
2763    function GetMonthLabel($aMonthNbr,$year) {
2764        $sn = $this->iDateLocale->GetShortMonthName($aMonthNbr);
2765        $ln = $this->iDateLocale->GetLongMonthName($aMonthNbr);
2766        switch($this->month->iStyle) {
2767            case MONTHSTYLE_SHORTNAME:
2768                $m=$sn;
2769                break;
2770            case MONTHSTYLE_LONGNAME:
2771                $m=$ln;
2772                break;
2773            case MONTHSTYLE_SHORTNAMEYEAR2:
2774                $m=$sn." '".substr("".$year,2);
2775                break;
2776            case MONTHSTYLE_SHORTNAMEYEAR4:
2777                $m=$sn." ".$year;
2778                break;
2779            case MONTHSTYLE_LONGNAMEYEAR2:
2780                $m=$ln." '".substr("".$year,2);
2781                break;
2782            case MONTHSTYLE_LONGNAMEYEAR4:
2783                $m=$ln." ".$year;
2784                break;
2785            case MONTHSTYLE_FIRSTLETTER:
2786                $m=$sn[0];
2787                break;
2788        }
2789        return $m;
2790    }
2791
2792    // Stroke month scale and gridlines
2793    function StrokeMonths($aYCoord,$getHeight=false) {
2794        if( $this->month->iShowLabels ) {
2795            $img=$this->iImg;
2796            $img->SetFont($this->month->iFFamily,$this->month->iFStyle,$this->month->iFSize);
2797            $yt=$aYCoord+$img->top_margin;
2798            $yb=$yt + $img->GetFontHeight() + $this->month->iTitleVertMargin + $this->month->iFrameWeight;
2799            if( $getHeight ) {
2800                return $yb - $img->top_margin;
2801            }
2802            $monthnbr = $this->GetMonthNbr($this->iStartDate)-1;
2803            $xt=$img->left_margin+$this->iLabelWidth;
2804            $xb=$img->width-$img->right_margin+1;
2805
2806            $img->SetColor($this->month->iBackgroundColor);
2807            $img->FilledRectangle($xt,$yt,$xb,$yb);
2808
2809            $img->SetLineWeight($this->month->grid->iWeight);
2810            $img->SetColor($this->month->iTextColor);
2811            $year = 0+strftime("%Y",$this->iStartDate);
2812            $img->SetTextAlign("center");
2813            if( $this->GetMonthNbr($this->iStartDate) == $this->GetMonthNbr($this->iEndDate)
2814                && $this->GetYear($this->iStartDate)==$this->GetYear($this->iEndDate) ) {
2815                $monthwidth=$this->GetDayWidth()*($this->GetMonthDayNbr($this->iEndDate) - $this->GetMonthDayNbr($this->iStartDate) + 1);
2816            }
2817            else {
2818                $monthwidth=$this->GetDayWidth()*($this->GetNumDaysInMonth($monthnbr,$year)-$this->GetMonthDayNbr($this->iStartDate)+1);
2819            }
2820            // Is it enough space to stroke the first month?
2821            $monthName = $this->GetMonthLabel($monthnbr,$year);
2822            if( $monthwidth >= 1.2*$img->GetTextWidth($monthName) ) {
2823                $img->SetColor($this->month->iTextColor);
2824                $img->StrokeText(round($xt+$monthwidth/2+1),
2825                round($yb-$this->month->iTitleVertMargin),
2826                $monthName);
2827            }
2828            $x = $xt + $monthwidth;
2829            while( $x < $xb ) {
2830                $img->SetColor($this->month->grid->iColor);
2831                $img->Line($x,$yt,$x,$yb);
2832                $this->month->grid->Stroke($img,$x,$yb,$x,$img->height-$img->bottom_margin);
2833                $monthnbr++;
2834                if( $monthnbr==12 ) {
2835                    $monthnbr=0;
2836                    $year++;
2837                }
2838                $monthName = $this->GetMonthLabel($monthnbr,$year);
2839                $monthwidth=$this->GetDayWidth()*$this->GetNumDaysInMonth($monthnbr,$year);
2840                if( $x + $monthwidth < $xb )
2841                    $w = $monthwidth;
2842                else
2843                    $w = $xb-$x;
2844                if( $w >= 1.2*$img->GetTextWidth($monthName) ) {
2845                    $img->SetColor($this->month->iTextColor);
2846                    $img->StrokeText(round($x+$w/2+1),
2847                    round($yb-$this->month->iTitleVertMargin),$monthName);
2848                }
2849                $x += $monthwidth;
2850            }
2851            $img->SetColor($this->month->iFrameColor);
2852            $img->SetLineWeight($this->month->iFrameWeight);
2853            $img->Rectangle($xt,$yt,$xb,$yb);
2854            return $yb-$img->top_margin;
2855        }
2856        return $aYCoord;
2857    }
2858
2859    // Stroke year scale and gridlines
2860    function StrokeYears($aYCoord,$getHeight=false) {
2861        if( $this->year->iShowLabels ) {
2862            $img=$this->iImg;
2863            $yt=$aYCoord+$img->top_margin;
2864            $img->SetFont($this->year->iFFamily,$this->year->iFStyle,$this->year->iFSize);
2865            $yb=$yt + $img->GetFontHeight() + $this->year->iTitleVertMargin + $this->year->iFrameWeight;
2866
2867            if( $getHeight ) {
2868                return $yb - $img->top_margin;
2869            }
2870
2871            $xb=$img->width-$img->right_margin+1;
2872            $xt=$img->left_margin+$this->iLabelWidth;
2873            $year = $this->GetYear($this->iStartDate);
2874            $img->SetColor($this->year->iBackgroundColor);
2875            $img->FilledRectangle($xt,$yt,$xb,$yb);
2876            $img->SetLineWeight($this->year->grid->iWeight);
2877            $img->SetTextAlign("center");
2878            if( $year == $this->GetYear($this->iEndDate) )
2879                $yearwidth=$this->GetDayWidth()*($this->GetYearDayNbr($this->iEndDate)-$this->GetYearDayNbr($this->iStartDate)+1);
2880            else
2881                $yearwidth=$this->GetDayWidth()*($this->GetNumDaysInYear($year)-$this->GetYearDayNbr($this->iStartDate)+1);
2882
2883            // The space for a year must be at least 20% bigger than the actual text
2884            // so we allow 10% margin on each side
2885            if( $yearwidth >= 1.20*$img->GetTextWidth("".$year) ) {
2886                $img->SetColor($this->year->iTextColor);
2887                $img->StrokeText(round($xt+$yearwidth/2+1),
2888                                 round($yb-$this->year->iTitleVertMargin),
2889                                 $year);
2890            }
2891            $x = $xt + $yearwidth;
2892            while( $x < $xb ) {
2893                $img->SetColor($this->year->grid->iColor);
2894                $img->Line($x,$yt,$x,$yb);
2895                $this->year->grid->Stroke($img,$x,$yb,$x,$img->height-$img->bottom_margin);
2896                $year += 1;
2897                $yearwidth=$this->GetDayWidth()*$this->GetNumDaysInYear($year);
2898                if( $x + $yearwidth < $xb )
2899                    $w = $yearwidth;
2900                else
2901                    $w = $xb-$x;
2902                if( $w >= 1.2*$img->GetTextWidth("".$year) ) {
2903                    $img->SetColor($this->year->iTextColor);
2904                    $img->StrokeText(round($x+$w/2+1),
2905                                     round($yb-$this->year->iTitleVertMargin),
2906                                     $year);
2907                }
2908                $x += $yearwidth;
2909            }
2910            $img->SetColor($this->year->iFrameColor);
2911            $img->SetLineWeight($this->year->iFrameWeight);
2912            $img->Rectangle($xt,$yt,$xb,$yb);
2913            return $yb-$img->top_margin;
2914        }
2915        return $aYCoord;
2916    }
2917
2918    // Stroke table title (upper left corner)
2919    function StrokeTableHeaders($aYBottom) {
2920        $img=$this->iImg;
2921        $xt=$img->left_margin;
2922        $yt=$img->top_margin;
2923        $xb=$xt+$this->iLabelWidth;
2924        $yb=$aYBottom+$img->top_margin;
2925
2926        if( $this->tableTitle->iShow ) {
2927            $img->SetColor($this->iTableHeaderBackgroundColor);
2928            $img->FilledRectangle($xt,$yt,$xb,$yb);
2929            $this->tableTitle->Align("center","top");
2930            $this->tableTitle->Stroke($img,$xt+($xb-$xt)/2+1,$yt+2);
2931            $img->SetColor($this->iTableHeaderFrameColor);
2932            $img->SetLineWeight($this->iTableHeaderFrameWeight);
2933            $img->Rectangle($xt,$yt,$xb,$yb);
2934        }
2935
2936        $this->actinfo->Stroke($img,$xt,$yt,$xb,$yb,$this->tableTitle->iShow);
2937
2938
2939        // Draw the horizontal dividing line
2940        $this->dividerh->Stroke($img,$xt,$yb,$img->width-$img->right_margin,$yb);
2941
2942        // Draw the vertical dividing line
2943        // We do the width "manually" since we want the line only to grow
2944        // to the left
2945        $fancy = $this->divider->iStyle == 'fancy' ;
2946        if( $fancy ) {
2947            $this->divider->iStyle = 'solid';
2948        }
2949
2950        $tmp = $this->divider->iWeight;
2951        $this->divider->iWeight=1;
2952        $y = $img->height-$img->bottom_margin;
2953        for($i=0; $i < $tmp; ++$i ) {
2954            $this->divider->Stroke($img,$xb-$i,$yt,$xb-$i,$y);
2955        }
2956
2957        // Should we draw "fancy" divider
2958        if( $fancy ) {
2959            $img->SetLineWeight(1);
2960            $img->SetColor($this->iTableHeaderFrameColor);
2961            $img->Line($xb,$yt,$xb,$y);
2962            $img->Line($xb-$tmp+1,$yt,$xb-$tmp+1,$y);
2963            $img->SetColor('white');
2964            $img->Line($xb-$tmp+2,$yt,$xb-$tmp+2,$y);
2965        }
2966    }
2967
2968    // Main entry point to stroke scale
2969    function Stroke() {
2970        if( !$this->IsRangeSet() ) {
2971        	JpGraphError::RaiseL(6022);
2972        	//("Gantt scale has not been specified.");
2973        }
2974        $img=$this->iImg;
2975
2976        // If minutes are displayed then hour interval must be 1
2977        if( $this->IsDisplayMinute() && $this->hour->GetIntervall() > 1 ) {
2978            JpGraphError::RaiseL(6023);
2979            //('If you display both hour and minutes the hour intervall must be 1 (Otherwise it doesn\' make sense to display minutes).');
2980        }
2981
2982        // Stroke all headers. As argument we supply the offset from the
2983        // top which depends on any previous headers
2984
2985        // First find out the height of each header
2986        $offy=$this->StrokeYears(0,true);
2987        $offm=$this->StrokeMonths($offy,true);
2988        $offw=$this->StrokeWeeks($offm,true);
2989        $offd=$this->StrokeDays($offw,true);
2990        $offh=$this->StrokeHours($offd,true);
2991        $offmin=$this->StrokeMinutes($offh,true);
2992
2993
2994        // ... then we can stroke them in the "backwards order to ensure that
2995        // the larger scale gridlines is stroked over the smaller scale gridline
2996        $this->StrokeMinutes($offh);
2997        $this->StrokeHours($offd);
2998        $this->StrokeDays($offw);
2999        $this->StrokeWeeks($offm);
3000        $this->StrokeMonths($offy);
3001        $this->StrokeYears(0);
3002
3003        // Now when we now the oaverall size of the scale headers
3004        // we can stroke the overall table headers
3005        $this->StrokeTableHeaders($offmin);
3006
3007        // Now we can calculate the correct scaling factor for each vertical position
3008        $this->iAvailableHeight = $img->height - $img->top_margin - $img->bottom_margin - $offd;
3009
3010        $this->iVertHeaderSize = $offmin;
3011        if( $this->iVertSpacing == -1 )
3012        	$this->iVertSpacing = $this->iAvailableHeight / $this->iVertLines;
3013    }
3014}
3015
3016
3017//===================================================
3018// CLASS GanttConstraint
3019// Just a structure to store all the values for a constraint
3020//===================================================
3021class GanttConstraint {
3022    public $iConstrainRow;
3023    public $iConstrainType;
3024    public $iConstrainColor;
3025    public $iConstrainArrowSize;
3026    public $iConstrainArrowType;
3027
3028    //---------------
3029    // CONSTRUCTOR
3030    function __construct($aRow,$aType,$aColor,$aArrowSize,$aArrowType){
3031        $this->iConstrainType = $aType;
3032        $this->iConstrainRow = $aRow;
3033        $this->iConstrainColor=$aColor;
3034        $this->iConstrainArrowSize=$aArrowSize;
3035        $this->iConstrainArrowType=$aArrowType;
3036    }
3037}
3038
3039
3040//===================================================
3041// CLASS GanttPlotObject
3042// The common signature for a Gantt object
3043//===================================================
3044class GanttPlotObject {
3045    public $title,$caption;
3046    public $csimarea='',$csimtarget='',$csimwintarget='',$csimalt='';
3047    public $constraints = array();
3048    public $iCaptionMargin=5;
3049    public $iConstrainPos=array();
3050    protected $iStart="";    // Start date
3051    public $iVPos=0;     // Vertical position
3052    protected $iLabelLeftMargin=2; // Title margin
3053
3054    function __construct() {
3055        $this->title = new TextProperty();
3056        $this->title->Align('left','center');
3057        $this->caption = new TextProperty();
3058    }
3059
3060    function GetCSIMArea() {
3061        return $this->csimarea;
3062    }
3063
3064    function SetCSIMTarget($aTarget,$aAlt='',$aWinTarget='') {
3065        if( !is_string($aTarget) ) {
3066            $tv = substr(var_export($aTarget,true),0,40);
3067            JpGraphError::RaiseL(6024,$tv);
3068            //('CSIM Target must be specified as a string.'."\nStart of target is:\n$tv");
3069        }
3070        if( !is_string($aAlt) ) {
3071            $tv = substr(var_export($aAlt,true),0,40);
3072            JpGraphError::RaiseL(6025,$tv);
3073            //('CSIM Alt text must be specified as a string.'."\nStart of alt text is:\n$tv");
3074        }
3075
3076        $this->csimtarget=$aTarget;
3077        $this->csimwintarget=$aWinTarget;
3078        $this->csimalt=$aAlt;
3079    }
3080
3081    function SetCSIMAlt($aAlt) {
3082        if( !is_string($aAlt) ) {
3083            $tv = substr(var_export($aAlt,true),0,40);
3084            JpGraphError::RaiseL(6025,$tv);
3085            //('CSIM Alt text must be specified as a string.'."\nStart of alt text is:\n$tv");
3086        }
3087        $this->csimalt=$aAlt;
3088    }
3089
3090    function SetConstrain($aRow,$aType,$aColor='black',$aArrowSize=ARROW_S2,$aArrowType=ARROWT_SOLID) {
3091        $this->constraints[] = new GanttConstraint($aRow, $aType, $aColor, $aArrowSize, $aArrowType);
3092    }
3093
3094    function SetConstrainPos($xt,$yt,$xb,$yb) {
3095        $this->iConstrainPos = array($xt,$yt,$xb,$yb);
3096    }
3097
3098    function GetMinDate() {
3099        return $this->iStart;
3100    }
3101
3102    function GetMaxDate() {
3103        return $this->iStart;
3104    }
3105
3106    function SetCaptionMargin($aMarg) {
3107        $this->iCaptionMargin=$aMarg;
3108    }
3109
3110    function GetAbsHeight($aImg) {
3111        return 0;
3112    }
3113
3114    function GetLineNbr() {
3115        return $this->iVPos;
3116    }
3117
3118    function SetLabelLeftMargin($aOff) {
3119        $this->iLabelLeftMargin=$aOff;
3120    }
3121
3122    function StrokeActInfo($aImg,$aScale,$aYPos) {
3123        $cols=array();
3124        $aScale->actinfo->GetColStart($aImg,$cols,true);
3125        $this->title->Stroke($aImg,$cols,$aYPos);
3126    }
3127}
3128
3129//===================================================
3130// CLASS Progress
3131// Holds parameters for the progress indicator
3132// displyed within a bar
3133//===================================================
3134class Progress {
3135    public $iProgress=-1;
3136    public $iPattern=GANTT_SOLID;
3137    public $iColor="black", $iFillColor='black';
3138    public $iDensity=98, $iHeight=0.65;
3139
3140    function Set($aProg) {
3141        if( $aProg < 0.0 || $aProg > 1.0 ) {
3142        	JpGraphError::RaiseL(6027);
3143        	//("Progress value must in range [0, 1]");
3144        }
3145        $this->iProgress = $aProg;
3146    }
3147
3148    function SetPattern($aPattern,$aColor="blue",$aDensity=98) {
3149        $this->iPattern = $aPattern;
3150        $this->iColor = $aColor;
3151        $this->iDensity = $aDensity;
3152    }
3153
3154    function SetFillColor($aColor) {
3155        $this->iFillColor = $aColor;
3156    }
3157
3158    function SetHeight($aHeight) {
3159        $this->iHeight = $aHeight;
3160    }
3161}
3162
3163define('GANTT_HGRID1',0);
3164define('GANTT_HGRID2',1);
3165
3166//===================================================
3167// CLASS HorizontalGridLine
3168// Responsible for drawinf horizontal gridlines and filled alternatibg rows
3169//===================================================
3170class HorizontalGridLine {
3171    private $iGraph=NULL;
3172    private $iRowColor1 = '', $iRowColor2 = '';
3173    private $iShow=false;
3174    private $line=null;
3175    private $iStart=0; // 0=from left margin, 1=just along header
3176
3177    function __construct() {
3178        $this->line = new LineProperty();
3179        $this->line->SetColor('gray@0.4');
3180        $this->line->SetStyle('dashed');
3181    }
3182
3183    function Show($aShow=true) {
3184        $this->iShow = $aShow;
3185    }
3186
3187    function SetRowFillColor($aColor1,$aColor2='') {
3188        $this->iRowColor1 = $aColor1;
3189        $this->iRowColor2 = $aColor2;
3190    }
3191
3192    function SetStart($aStart) {
3193        $this->iStart = $aStart;
3194    }
3195
3196    function Stroke($aImg,$aScale) {
3197
3198        if( ! $this->iShow ) return;
3199
3200        // Get horizontal width of line
3201        /*
3202        $limst = $aScale->iStartDate;
3203        $limen = $aScale->iEndDate;
3204        $xt = round($aScale->TranslateDate($aScale->iStartDate));
3205        $xb = round($aScale->TranslateDate($limen));
3206        */
3207
3208        if( $this->iStart === 0 ) {
3209            $xt = $aImg->left_margin-1;
3210        }
3211        else {
3212            $xt = round($aScale->TranslateDate($aScale->iStartDate))+1;
3213        }
3214
3215        $xb = $aImg->width-$aImg->right_margin;
3216
3217        $yt = round($aScale->TranslateVertPos(0));
3218        $yb = round($aScale->TranslateVertPos(1));
3219        $height = $yb - $yt;
3220
3221        // Loop around for all lines in the chart
3222        for($i=0; $i < $aScale->iVertLines; ++$i ) {
3223            $yb = $yt - $height;
3224            $this->line->Stroke($aImg,$xt,$yb,$xb,$yb);
3225            if( $this->iRowColor1 !== '' ) {
3226                if( $i % 2 == 0 ) {
3227                    $aImg->PushColor($this->iRowColor1);
3228                    $aImg->FilledRectangle($xt,$yt,$xb,$yb);
3229                    $aImg->PopColor();
3230                }
3231                elseif( $this->iRowColor2 !== '' ) {
3232                    $aImg->PushColor($this->iRowColor2);
3233                    $aImg->FilledRectangle($xt,$yt,$xb,$yb);
3234                    $aImg->PopColor();
3235                }
3236            }
3237            $yt = round($aScale->TranslateVertPos($i+1));
3238        }
3239        $yb = $yt - $height;
3240        $this->line->Stroke($aImg,$xt,$yb,$xb,$yb);
3241    }
3242}
3243
3244
3245//===================================================
3246// CLASS GanttBar
3247// Responsible for formatting individual gantt bars
3248//===================================================
3249class GanttBar extends GanttPlotObject {
3250    public $progress;
3251    public $leftMark,$rightMark;
3252    private $iEnd;
3253    private $iHeightFactor=0.5;
3254    private $iFillColor="white",$iFrameColor="black";
3255    private $iShadow=false,$iShadowColor="darkgray",$iShadowWidth=1,$iShadowFrame="black";
3256    private $iPattern=GANTT_RDIAG,$iPatternColor="blue",$iPatternDensity=95;
3257    private $iBreakStyle=false, $iBreakLineStyle='dotted',$iBreakLineWeight=1;
3258    //---------------
3259    // CONSTRUCTOR
3260    function __construct($aPos,$aLabel,$aStart,$aEnd,$aCaption="",$aHeightFactor=0.6) {
3261        parent::__construct();
3262        $this->iStart = $aStart;
3263        // Is the end date given as a date or as number of days added to start date?
3264        if( is_string($aEnd) ) {
3265            // If end date has been specified without a time we will asssume
3266            // end date is at the end of that date
3267            if( strpos($aEnd,':') === false ) {
3268            	$this->iEnd = strtotime($aEnd)+SECPERDAY-1;
3269            }
3270            else {
3271            	$this->iEnd = $aEnd;
3272            }
3273        }
3274        elseif(is_int($aEnd) || is_float($aEnd) ) {
3275        	$this->iEnd = strtotime($aStart)+round($aEnd*SECPERDAY);
3276        }
3277        $this->iVPos = $aPos;
3278        $this->iHeightFactor = $aHeightFactor;
3279        $this->title->Set($aLabel);
3280        $this->caption = new TextProperty($aCaption);
3281        $this->caption->Align("left","center");
3282        $this->leftMark =new PlotMark();
3283        $this->leftMark->Hide();
3284        $this->rightMark=new PlotMark();
3285        $this->rightMark->Hide();
3286        $this->progress = new Progress();
3287    }
3288
3289    //---------------
3290    // PUBLIC METHODS
3291    function SetShadow($aShadow=true,$aColor="gray") {
3292        $this->iShadow=$aShadow;
3293        $this->iShadowColor=$aColor;
3294    }
3295
3296    function SetBreakStyle($aFlg=true,$aLineStyle='dotted',$aLineWeight=1) {
3297    	$this->iBreakStyle = $aFlg;
3298    	$this->iBreakLineStyle = $aLineStyle;
3299    	$this->iBreakLineWeight = $aLineWeight;
3300    }
3301
3302    function GetMaxDate() {
3303        return $this->iEnd;
3304    }
3305
3306    function SetHeight($aHeight) {
3307        $this->iHeightFactor = $aHeight;
3308    }
3309
3310    function SetColor($aColor) {
3311        $this->iFrameColor = $aColor;
3312    }
3313
3314    function SetFillColor($aColor) {
3315        $this->iFillColor = $aColor;
3316    }
3317
3318    function GetAbsHeight($aImg) {
3319        if( is_int($this->iHeightFactor) || $this->leftMark->show || $this->rightMark->show ) {
3320            $m=-1;
3321            if( is_int($this->iHeightFactor) )
3322            	$m = $this->iHeightFactor;
3323            if( $this->leftMark->show )
3324            	$m = max($m,$this->leftMark->width*2);
3325            if( $this->rightMark->show )
3326            	$m = max($m,$this->rightMark->width*2);
3327            return $m;
3328        }
3329        else
3330        	return -1;
3331    }
3332
3333    function SetPattern($aPattern,$aColor="blue",$aDensity=95) {
3334        $this->iPattern = $aPattern;
3335        $this->iPatternColor = $aColor;
3336        $this->iPatternDensity = $aDensity;
3337    }
3338
3339    function Stroke($aImg,$aScale) {
3340        $factory = new RectPatternFactory();
3341        $prect = $factory->Create($this->iPattern,$this->iPatternColor);
3342        $prect->SetDensity($this->iPatternDensity);
3343
3344        // If height factor is specified as a float between 0,1 then we take it as meaning
3345        // percetage of the scale width between horizontal line.
3346        // If it is an integer > 1 we take it to mean the absolute height in pixels
3347        if( $this->iHeightFactor > -0.0 && $this->iHeightFactor <= 1.1)
3348        	$vs = $aScale->GetVertSpacing()*$this->iHeightFactor;
3349        elseif(is_int($this->iHeightFactor) && $this->iHeightFactor>2 && $this->iHeightFactor < 200 )
3350        	$vs = $this->iHeightFactor;
3351        else {
3352        	JpGraphError::RaiseL(6028,$this->iHeightFactor);
3353        	//	("Specified height (".$this->iHeightFactor.") for gantt bar is out of range.");
3354        }
3355
3356        // Clip date to min max dates to show
3357        $st = $aScale->NormalizeDate($this->iStart);
3358        $en = $aScale->NormalizeDate($this->iEnd);
3359
3360        $limst = max($st,$aScale->iStartDate);
3361        $limen = min($en,$aScale->iEndDate);
3362
3363        $xt = round($aScale->TranslateDate($limst));
3364        $xb = round($aScale->TranslateDate($limen));
3365        $yt = round($aScale->TranslateVertPos($this->iVPos)-$vs-($aScale->GetVertSpacing()/2-$vs/2));
3366        $yb = round($aScale->TranslateVertPos($this->iVPos)-($aScale->GetVertSpacing()/2-$vs/2));
3367        $middle = round($yt+($yb-$yt)/2);
3368        $this->StrokeActInfo($aImg,$aScale,$middle);
3369
3370        // CSIM for title
3371        if( ! empty($this->title->csimtarget) ) {
3372            $colwidth = $this->title->GetColWidth($aImg);
3373            $colstarts=array();
3374            $aScale->actinfo->GetColStart($aImg,$colstarts,true);
3375            $n = min(count($colwidth),count($this->title->csimtarget));
3376            for( $i=0; $i < $n; ++$i ) {
3377                $title_xt = $colstarts[$i];
3378                $title_xb = $title_xt + $colwidth[$i];
3379                $coords = "$title_xt,$yt,$title_xb,$yt,$title_xb,$yb,$title_xt,$yb";
3380
3381                if( ! empty($this->title->csimtarget[$i]) ) {
3382                    $this->csimarea .= "<area shape=\"poly\" coords=\"$coords\" href=\"".$this->title->csimtarget[$i]."\"";
3383
3384                    if( ! empty($this->title->csimwintarget[$i]) ) {
3385                        $this->csimarea .= "target=\"".$this->title->csimwintarget[$i]."\" ";
3386                    }
3387
3388                    if( ! empty($this->title->csimalt[$i]) ) {
3389                        $tmp = $this->title->csimalt[$i];
3390                        $this->csimarea .= " title=\"$tmp\" alt=\"$tmp\" ";
3391                    }
3392                    $this->csimarea .= " />\n";
3393                }
3394            }
3395        }
3396
3397        // Check if the bar is totally outside the current scale range
3398        if( $en <  $aScale->iStartDate || $st > $aScale->iEndDate )
3399        	return;
3400
3401
3402        // Remember the positions for the bar
3403        $this->SetConstrainPos($xt,$yt,$xb,$yb);
3404
3405
3406
3407        $prect->ShowFrame(false);
3408        $prect->SetBackground($this->iFillColor);
3409        if( $this->iBreakStyle ) {
3410        	$aImg->SetColor($this->iFrameColor);
3411        	$olds = $aImg->SetLineStyle($this->iBreakLineStyle);
3412        	$oldw = $aImg->SetLineWeight($this->iBreakLineWeight);
3413        	$aImg->StyleLine($xt,$yt,$xb,$yt);
3414        	$aImg->StyleLine($xt,$yb,$xb,$yb);
3415        	$aImg->SetLineStyle($olds);
3416        	$aImg->SetLineWeight($oldw);
3417        }
3418        else {
3419	        if( $this->iShadow ) {
3420	            $aImg->SetColor($this->iFrameColor);
3421	            $aImg->ShadowRectangle($xt,$yt,$xb,$yb,$this->iFillColor,$this->iShadowWidth,$this->iShadowColor);
3422	            $prect->SetPos(new Rectangle($xt+1,$yt+1,$xb-$xt-$this->iShadowWidth-2,$yb-$yt-$this->iShadowWidth-2));
3423	            $prect->Stroke($aImg);
3424	        }
3425	        else {
3426	            $prect->SetPos(new Rectangle($xt,$yt,$xb-$xt+1,$yb-$yt+1));
3427	            $prect->Stroke($aImg);
3428	            $aImg->SetColor($this->iFrameColor);
3429	            $aImg->Rectangle($xt,$yt,$xb,$yb);
3430	        }
3431        }
3432        // CSIM for bar
3433        if( ! empty($this->csimtarget) ) {
3434
3435            $coords = "$xt,$yt,$xb,$yt,$xb,$yb,$xt,$yb";
3436            $this->csimarea .= "<area shape=\"poly\" coords=\"$coords\" href=\"".$this->csimtarget."\"";
3437
3438            if( !empty($this->csimwintarget) ) {
3439                $this->csimarea .= " target=\"".$this->csimwintarget."\" ";
3440            }
3441
3442            if( $this->csimalt != '' ) {
3443                $tmp = $this->csimalt;
3444                $this->csimarea .= " title=\"$tmp\" alt=\"$tmp\" ";
3445            }
3446            $this->csimarea .= " />\n";
3447        }
3448
3449        // Draw progress bar inside activity bar
3450        if( $this->progress->iProgress > 0 ) {
3451
3452            $xtp = $aScale->TranslateDate($st);
3453            $xbp = $aScale->TranslateDate($en);
3454            $len = ($xbp-$xtp)*$this->progress->iProgress;
3455
3456            $endpos = $xtp+$len;
3457            if( $endpos > $xt ) {
3458
3459                // Take away the length of the progress that is not visible (before the start date)
3460                $len -= ($xt-$xtp);
3461
3462                // Is the the progress bar visible after the start date?
3463                if( $xtp < $xt )
3464                $xtp = $xt;
3465
3466                // Make sure that the progess bar doesn't extend over the end date
3467                if( $xtp+$len-1 > $xb )
3468                $len = $xb - $xtp ;
3469
3470                $prog = $factory->Create($this->progress->iPattern,$this->progress->iColor);
3471                $prog->SetDensity($this->progress->iDensity);
3472                $prog->SetBackground($this->progress->iFillColor);
3473                $barheight = ($yb-$yt+1);
3474                if( $this->iShadow )
3475                	$barheight -= $this->iShadowWidth;
3476                $progressheight = floor($barheight*$this->progress->iHeight);
3477                $marg = ceil(($barheight-$progressheight)/2);
3478                $pos = new Rectangle($xtp,$yt + $marg, $len,$barheight-2*$marg);
3479                $prog->SetPos($pos);
3480                $prog->Stroke($aImg);
3481            }
3482        }
3483
3484        // We don't plot the end mark if the bar has been capped
3485        if( $limst == $st ) {
3486            $y = $middle;
3487            // We treat the RIGHT and LEFT triangle mark a little bi
3488            // special so that these marks are placed right under the
3489            // bar.
3490            if( $this->leftMark->GetType() == MARK_LEFTTRIANGLE ) {
3491                $y = $yb ;
3492            }
3493            $this->leftMark->Stroke($aImg,$xt,$y);
3494        }
3495        if( $limen == $en ) {
3496            $y = $middle;
3497            // We treat the RIGHT and LEFT triangle mark a little bi
3498            // special so that these marks are placed right under the
3499            // bar.
3500            if( $this->rightMark->GetType() == MARK_RIGHTTRIANGLE ) {
3501                $y = $yb ;
3502            }
3503            $this->rightMark->Stroke($aImg,$xb,$y);
3504
3505            $margin = $this->iCaptionMargin;
3506            if( $this->rightMark->show )
3507            	$margin += $this->rightMark->GetWidth();
3508            $this->caption->Stroke($aImg,$xb+$margin,$middle);
3509        }
3510    }
3511}
3512
3513//===================================================
3514// CLASS MileStone
3515// Responsible for formatting individual milestones
3516//===================================================
3517class MileStone extends GanttPlotObject {
3518    public $mark;
3519
3520    //---------------
3521    // CONSTRUCTOR
3522    function __construct($aVPos,$aLabel,$aDate,$aCaption="") {
3523        GanttPlotObject::__construct();
3524        $this->caption->Set($aCaption);
3525        $this->caption->Align("left","center");
3526        $this->caption->SetFont(FF_FONT1,FS_BOLD);
3527        $this->title->Set($aLabel);
3528        $this->title->SetColor("darkred");
3529        $this->mark = new PlotMark();
3530        $this->mark->SetWidth(10);
3531        $this->mark->SetType(MARK_DIAMOND);
3532        $this->mark->SetColor("darkred");
3533        $this->mark->SetFillColor("darkred");
3534        $this->iVPos = $aVPos;
3535        $this->iStart = $aDate;
3536    }
3537
3538    //---------------
3539    // PUBLIC METHODS
3540
3541    function GetAbsHeight($aImg) {
3542        return max($this->title->GetHeight($aImg),$this->mark->GetWidth());
3543    }
3544
3545    function Stroke($aImg,$aScale) {
3546        // Put the mark in the middle at the middle of the day
3547        $d = $aScale->NormalizeDate($this->iStart)+SECPERDAY/2;
3548        $x = $aScale->TranslateDate($d);
3549        $y = $aScale->TranslateVertPos($this->iVPos)-($aScale->GetVertSpacing()/2);
3550
3551        $this->StrokeActInfo($aImg,$aScale,$y);
3552
3553        // CSIM for title
3554        if( ! empty($this->title->csimtarget) ) {
3555
3556            $yt = round($y - $this->title->GetHeight($aImg)/2);
3557            $yb = round($y + $this->title->GetHeight($aImg)/2);
3558
3559            $colwidth = $this->title->GetColWidth($aImg);
3560            $colstarts=array();
3561            $aScale->actinfo->GetColStart($aImg,$colstarts,true);
3562            $n = min(count($colwidth),count($this->title->csimtarget));
3563            for( $i=0; $i < $n; ++$i ) {
3564                $title_xt = $colstarts[$i];
3565                $title_xb = $title_xt + $colwidth[$i];
3566                $coords = "$title_xt,$yt,$title_xb,$yt,$title_xb,$yb,$title_xt,$yb";
3567
3568                if( !empty($this->title->csimtarget[$i]) ) {
3569
3570                    $this->csimarea .= "<area shape=\"poly\" coords=\"$coords\" href=\"".$this->title->csimtarget[$i]."\"";
3571
3572                    if( !empty($this->title->csimwintarget[$i]) ) {
3573                        $this->csimarea .= "target=\"".$this->title->csimwintarget[$i]."\"";
3574                    }
3575
3576                    if( ! empty($this->title->csimalt[$i]) ) {
3577                        $tmp = $this->title->csimalt[$i];
3578                        $this->csimarea .= " title=\"$tmp\" alt=\"$tmp\" ";
3579                    }
3580                    $this->csimarea .= " />\n";
3581                }
3582            }
3583        }
3584
3585        if( $d <  $aScale->iStartDate || $d > $aScale->iEndDate )
3586        	return;
3587
3588        // Remember the coordinates for any constrains linking to
3589        // this milestone
3590        $w = $this->mark->GetWidth()/2;
3591        $this->SetConstrainPos($x,round($y-$w),$x,round($y+$w));
3592
3593        // Setup CSIM
3594        if( $this->csimtarget != '' ) {
3595            $this->mark->SetCSIMTarget( $this->csimtarget );
3596            $this->mark->SetCSIMAlt( $this->csimalt );
3597        }
3598
3599        $this->mark->Stroke($aImg,$x,$y);
3600        $this->caption->Stroke($aImg,$x+$this->mark->width/2+$this->iCaptionMargin,$y);
3601
3602        $this->csimarea .= $this->mark->GetCSIMAreas();
3603    }
3604}
3605
3606
3607//===================================================
3608// CLASS GanttVLine
3609// Responsible for formatting individual milestones
3610//===================================================
3611
3612class TextPropertyBelow extends TextProperty {
3613    function __construct($aTxt='') {
3614        parent::__construct($aTxt);
3615    }
3616
3617    function GetColWidth($aImg,$aMargin=0) {
3618        // Since we are not stroking the title in the columns
3619        // but rather under the graph we want this to return 0.
3620        return array(0);
3621    }
3622}
3623
3624class GanttVLine extends GanttPlotObject {
3625
3626    private $iLine,$title_margin=3, $iDayOffset=0.5;
3627    private $iStartRow = -1, $iEndRow = -1;
3628
3629    //---------------
3630    // CONSTRUCTOR
3631    function __construct($aDate,$aTitle="",$aColor="darkred",$aWeight=2,$aStyle="solid") {
3632        GanttPlotObject::__construct();
3633        $this->iLine = new LineProperty();
3634        $this->iLine->SetColor($aColor);
3635        $this->iLine->SetWeight($aWeight);
3636        $this->iLine->SetStyle($aStyle);
3637        $this->iStart = $aDate;
3638        $this->title = new TextPropertyBelow();
3639        $this->title->Set($aTitle);
3640    }
3641
3642    //---------------
3643    // PUBLIC METHODS
3644
3645    // Set start and end rows for the VLine. By default the entire heigh of the
3646    // Gantt chart is used
3647    function SetRowSpan($aStart, $aEnd=-1) {
3648        $this->iStartRow = $aStart;
3649        $this->iEndRow = $aEnd;
3650    }
3651
3652    function SetDayOffset($aOff=0.5) {
3653        if( $aOff < 0.0 || $aOff > 1.0 ) {
3654        	JpGraphError::RaiseL(6029);
3655        	//("Offset for vertical line must be in range [0,1]");
3656        }
3657        $this->iDayOffset = $aOff;
3658    }
3659
3660    function SetTitleMargin($aMarg) {
3661        $this->title_margin = $aMarg;
3662    }
3663
3664    function SetWeight($aWeight) {
3665        $this->iLine->SetWeight($aWeight);
3666    }
3667
3668    function Stroke($aImg,$aScale) {
3669        $d = $aScale->NormalizeDate($this->iStart);
3670        if( $d <  $aScale->iStartDate || $d > $aScale->iEndDate )
3671            return;
3672        if($this->iDayOffset != 0.0)
3673            $d += 24*60*60*$this->iDayOffset;
3674        $x = $aScale->TranslateDate($d);//d=1006858800,
3675
3676        if( $this->iStartRow > -1 ) {
3677            $y1 = $aScale->TranslateVertPos($this->iStartRow,true) ;
3678        }
3679        else {
3680            $y1 = $aScale->iVertHeaderSize+$aImg->top_margin;
3681        }
3682
3683        if( $this->iEndRow > -1 ) {
3684            $y2 = $aScale->TranslateVertPos($this->iEndRow);
3685        }
3686        else {
3687            $y2 = $aImg->height - $aImg->bottom_margin;
3688        }
3689
3690        $this->iLine->Stroke($aImg,$x,$y1,$x,$y2);
3691        $this->title->Align("center","top");
3692        $this->title->Stroke($aImg,$x,$y2+$this->title_margin);
3693    }
3694}
3695
3696//===================================================
3697// CLASS LinkArrow
3698// Handles the drawing of a an arrow
3699//===================================================
3700class LinkArrow {
3701    private $ix,$iy;
3702    private $isizespec = array(
3703    array(2,3),array(3,5),array(3,8),array(6,15),array(8,22));
3704    private $iDirection=ARROW_DOWN,$iType=ARROWT_SOLID,$iSize=ARROW_S2;
3705    private $iColor='black';
3706
3707    function __construct($x,$y,$aDirection,$aType=ARROWT_SOLID,$aSize=ARROW_S2) {
3708        $this->iDirection = $aDirection;
3709        $this->iType = $aType;
3710        $this->iSize = $aSize;
3711        $this->ix = $x;
3712        $this->iy = $y;
3713    }
3714
3715    function SetColor($aColor) {
3716        $this->iColor = $aColor;
3717    }
3718
3719    function SetSize($aSize) {
3720        $this->iSize = $aSize;
3721    }
3722
3723    function SetType($aType) {
3724        $this->iType = $aType;
3725    }
3726
3727    function Stroke($aImg) {
3728        list($dx,$dy) = $this->isizespec[$this->iSize];
3729        $x = $this->ix;
3730        $y = $this->iy;
3731        switch ( $this->iDirection ) {
3732            case ARROW_DOWN:
3733                $c = array($x,$y,$x-$dx,$y-$dy,$x+$dx,$y-$dy,$x,$y);
3734                break;
3735            case ARROW_UP:
3736                $c = array($x,$y,$x-$dx,$y+$dy,$x+$dx,$y+$dy,$x,$y);
3737                break;
3738            case ARROW_LEFT:
3739                $c = array($x,$y,$x+$dy,$y-$dx,$x+$dy,$y+$dx,$x,$y);
3740                break;
3741            case ARROW_RIGHT:
3742                $c = array($x,$y,$x-$dy,$y-$dx,$x-$dy,$y+$dx,$x,$y);
3743                break;
3744            default:
3745                JpGraphError::RaiseL(6030);
3746                //('Unknown arrow direction for link.');
3747                die();
3748                break;
3749        }
3750        $aImg->SetColor($this->iColor);
3751        switch( $this->iType ) {
3752            case ARROWT_SOLID:
3753                $aImg->FilledPolygon($c);
3754                break;
3755            case ARROWT_OPEN:
3756                $aImg->Polygon($c);
3757                break;
3758            default:
3759                JpGraphError::RaiseL(6031);
3760                //('Unknown arrow type for link.');
3761                die();
3762                break;
3763        }
3764    }
3765}
3766
3767//===================================================
3768// CLASS GanttLink
3769// Handles the drawing of a link line between 2 points
3770//===================================================
3771
3772class GanttLink {
3773    private $ix1,$ix2,$iy1,$iy2;
3774    private $iPathType=2,$iPathExtend=15;
3775    private $iColor='black',$iWeight=1;
3776    private $iArrowSize=ARROW_S2,$iArrowType=ARROWT_SOLID;
3777
3778    function __construct($x1=0,$y1=0,$x2=0,$y2=0) {
3779        $this->ix1 = $x1;
3780        $this->ix2 = $x2;
3781        $this->iy1 = $y1;
3782        $this->iy2 = $y2;
3783    }
3784
3785    function SetPos($x1,$y1,$x2,$y2) {
3786        $this->ix1 = $x1;
3787        $this->ix2 = $x2;
3788        $this->iy1 = $y1;
3789        $this->iy2 = $y2;
3790    }
3791
3792    function SetPath($aPath) {
3793        $this->iPathType = $aPath;
3794    }
3795
3796    function SetColor($aColor) {
3797        $this->iColor = $aColor;
3798    }
3799
3800    function SetArrow($aSize,$aType=ARROWT_SOLID) {
3801        $this->iArrowSize = $aSize;
3802        $this->iArrowType = $aType;
3803    }
3804
3805    function SetWeight($aWeight) {
3806        $this->iWeight = $aWeight;
3807    }
3808
3809    function Stroke($aImg) {
3810        // The way the path for the arrow is constructed is partly based
3811        // on some heuristics. This is not an exact science but draws the
3812        // path in a way that, for me, makes esthetic sence. For example
3813        // if the start and end activities are very close we make a small
3814        // detour to endter the target horixontally. If there are more
3815        // space between axctivities then no suh detour is made and the
3816        // target is "hit" directly vertical. I have tried to keep this
3817        // simple. no doubt this could become almost infinitive complex
3818        // and have some real AI. Feel free to modify this.
3819        // This will no-doubt be tweaked as times go by. One design aim
3820        // is to avoid having the user choose what types of arrow
3821        // he wants.
3822
3823        // The arrow is drawn between (x1,y1) to (x2,y2)
3824        $x1 = $this->ix1 ;
3825        $x2 = $this->ix2 ;
3826        $y1 = $this->iy1 ;
3827        $y2 = $this->iy2 ;
3828
3829        // Depending on if the target is below or above we have to
3830        // handle thi different.
3831        if( $y2 > $y1 ) {
3832            $arrowtype = ARROW_DOWN;
3833            $midy = round(($y2-$y1)/2+$y1);
3834            if( $x2 > $x1 ) {
3835                switch ( $this->iPathType  ) {
3836                    case 0:
3837                        $c = array($x1,$y1,$x1,$midy,$x2,$midy,$x2,$y2);
3838                        break;
3839                    case 1:
3840                    case 2:
3841                    case 3:
3842                        $c = array($x1,$y1,$x2,$y1,$x2,$y2);
3843                        break;
3844                    default:
3845                        JpGraphError::RaiseL(6032,$this->iPathType);
3846                        //('Internal error: Unknown path type (='.$this->iPathType .') specified for link.');
3847                        exit(1);
3848                        break;
3849                }
3850            }
3851            else {
3852                switch ( $this->iPathType  ) {
3853                    case 0:
3854                    case 1:
3855                        $c = array($x1,$y1,$x1,$midy,$x2,$midy,$x2,$y2);
3856                        break;
3857                    case 2:
3858                        // Always extend out horizontally a bit from the first point
3859                        // If we draw a link back in time (end to start) and the bars
3860                        // are very close we also change the path so it comes in from
3861                        // the left on the activity
3862                        $c = array($x1,$y1,$x1+$this->iPathExtend,$y1,
3863                        $x1+$this->iPathExtend,$midy,
3864                        $x2,$midy,$x2,$y2);
3865                        break;
3866                    case 3:
3867                        if( $y2-$midy < 6 ) {
3868                            $c = array($x1,$y1,$x1,$midy,
3869                            $x2-$this->iPathExtend,$midy,
3870                            $x2-$this->iPathExtend,$y2,
3871                            $x2,$y2);
3872                            $arrowtype = ARROW_RIGHT;
3873                        }
3874                        else {
3875                            $c = array($x1,$y1,$x1,$midy,$x2,$midy,$x2,$y2);
3876                        }
3877                        break;
3878                    default:
3879                        JpGraphError::RaiseL(6032,$this->iPathType);
3880                        //('Internal error: Unknown path type specified for link.');
3881                        exit(1);
3882                        break;
3883                }
3884            }
3885            $arrow = new LinkArrow($x2,$y2,$arrowtype);
3886        }
3887        else {
3888            // Y2 < Y1
3889            $arrowtype = ARROW_UP;
3890            $midy = round(($y1-$y2)/2+$y2);
3891            if( $x2 > $x1 ) {
3892                switch ( $this->iPathType  ) {
3893                    case 0:
3894                    case 1:
3895                        $c = array($x1,$y1,$x1,$midy,$x2,$midy,$x2,$y2);
3896                        break;
3897                    case 3:
3898                        if( $midy-$y2 < 8 ) {
3899                            $arrowtype = ARROW_RIGHT;
3900                            $c = array($x1,$y1,$x1,$y2,$x2,$y2);
3901                        }
3902                        else {
3903                            $c = array($x1,$y1,$x1,$midy,$x2,$midy,$x2,$y2);
3904                        }
3905                        break;
3906                    default:
3907                        JpGraphError::RaiseL(6032,$this->iPathType);
3908                        //('Internal error: Unknown path type specified for link.');
3909                        break;
3910                }
3911            }
3912            else {
3913                switch ( $this->iPathType  ) {
3914                    case 0:
3915                    case 1:
3916                        $c = array($x1,$y1,$x1,$midy,$x2,$midy,$x2,$y2);
3917                        break;
3918                    case 2:
3919                        // Always extend out horizontally a bit from the first point
3920                        $c = array($x1,$y1,$x1+$this->iPathExtend,$y1,
3921                        $x1+$this->iPathExtend,$midy,
3922                        $x2,$midy,$x2,$y2);
3923                        break;
3924                    case 3:
3925                        if( $midy-$y2 < 16 ) {
3926                            $arrowtype = ARROW_RIGHT;
3927                            $c = array($x1,$y1,$x1,$midy,$x2-$this->iPathExtend,$midy,
3928                            $x2-$this->iPathExtend,$y2,
3929                            $x2,$y2);
3930                        }
3931                        else {
3932                            $c = array($x1,$y1,$x1,$midy,$x2,$midy,$x2,$y2);
3933                        }
3934                        break;
3935                    default:
3936                        JpGraphError::RaiseL(6032,$this->iPathType);
3937                        //('Internal error: Unknown path type specified for link.');
3938                        break;
3939                }
3940            }
3941            $arrow = new LinkArrow($x2,$y2,$arrowtype);
3942        }
3943        $aImg->SetColor($this->iColor);
3944        $aImg->SetLineWeight($this->iWeight);
3945        $aImg->Polygon($c);
3946        $aImg->SetLineWeight(1);
3947        $arrow->SetColor($this->iColor);
3948        $arrow->SetSize($this->iArrowSize);
3949        $arrow->SetType($this->iArrowType);
3950        $arrow->Stroke($aImg);
3951    }
3952}
3953
3954// <EOF>
3955?>
3956