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