2 /*=======================================================================
3 // File: JPGRAPH_GANTT.PHP
4 // Description: JpGraph Gantt plot extension
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 $
9 // License: This code is released under QPL
10 // Copyright (c) 2002 Johan Persson
11 //========================================================================
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);
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
32 // Conversion constant
33 DEFINE("SECPERDAY",3600*24);
35 // Locales. ONLY KEPT FOR BACKWARDS COMPATIBILITY
36 // You should use the proper locale strings directly
38 DEFINE("LOCALE_EN","en_UK");
39 DEFINE("LOCALE_SV","sv_SE");
42 DEFINE("GANTT_EVEN",1);
43 DEFINE("GANTT_FROMTOP",2);
45 // Style for minute header
46 DEFINE("MINUTESTYLE_MM",0); // 15
47 DEFINE("MINUTESTYLE_CUSTOM",2); // Custom format
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
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"
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);
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);
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);
93 // Arrow direction for constrain links
94 DEFINE('ARROW_DOWN',0);
96 DEFINE('ARROW_LEFT',2);
97 DEFINE('ARROW_RIGHT',3);
99 // Arrow type for constrain type
100 DEFINE('ARROWT_SOLID',0);
101 DEFINE('ARROWT_OPEN',1);
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);
110 // Activity types for use with utility method CreateSimple()
111 DEFINE('ACTYPE_NORMAL',0);
112 DEFINE('ACTYPE_GROUP',1);
113 DEFINE('ACTYPE_MILESTONE',2);
116 DEFINE('ACTINFO_3D',1);
117 DEFINE('ACTINFO_2D',0);
119 //===================================================
120 // CLASS GanttActivityInfo
122 //===================================================
123 class GanttActivityInfo {
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;
134 var $iHeaderAlign='center';
136 function GanttActivityInfo() {
137 $this->vgrid = new LineProperty();
140 function Hide($aF=true) {
144 function Show($aF=true) {
149 function SetFont($aFFamily,$aFStyle=FS_NORMAL,$aFSize=10) {
150 $this->iFFamily = $aFFamily;
151 $this->iFStyle = $aFStyle;
152 $this->iFSize = $aFSize;
155 function SetStyle($aStyle) {
156 $this->iStyle = $aStyle;
159 function SetColumnMargin($aLeft,$aRight) {
160 $this->iLeftColMargin = $aLeft;
161 $this->iRightColMargin = $aRight;
164 function SetFontColor($aFontColor) {
165 $this->iFontColor = $aFontColor;
168 function SetColor($aColor) {
169 $this->iColor = $aColor;
172 function SetBackgroundColor($aColor) {
173 $this->iBackgroundColor = $aColor;
176 function SetColTitles($aTitles,$aWidth=null) {
177 $this->iTitles = $aTitles;
178 $this->iWidth = $aWidth;
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];
189 $this->iWidth[$i] = max($this->iWidth[$i],$aWidths[$i]);
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;
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]);
208 $w1 = $txt->GetWidth($aImg)+$rm;
210 $this->iWidth[$i] = $w1;
212 $h = max($h,$txt->GetHeight($aImg));
214 $this->iHeight = $h+$this->iTopHeaderMargin;
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];
228 // Adjust headers left, right or centered
229 function SetHeaderAlign($aAlign) {
230 $this->iHeaderAlign=$aAlign;
233 function Stroke($aImg,$aXLeft,$aYTop,$aXRight,$aYBottom,$aUseTextHeight=false) {
235 if( !$this->iShow ) return;
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);
248 $yTop = $aUseTextHeight ? $aYBottom-$h-$this->iTopColMargin-$this->iBottomColMargin : $aYTop ;
251 JpGraphError::Raise('Internal error. Height for ActivityTitles is < 0');
254 $aImg->SetLineWeight(1);
255 // Set background color
256 $aImg->SetColor($this->iBackgroundColor);
257 $aImg->FilledRectangle($aXLeft,$yTop,$aXRight,$aYBottom-1);
259 if( $this->iStyle == 1 ) {
261 $aImg->SetColor('white');
262 $aImg->Line($aXLeft,$yTop+1,
266 for($i=0; $i < $n; ++$i ) {
267 if( $this->iStyle == 1 ) {
269 $aImg->SetColor('white');
270 $aImg->Line($x+1,$yTop,$x+1,$aYBottom);
272 $x += $this->iLeftColMargin;
273 $txt->Set($this->iTitles[$i]);
275 // Adjust the text anchor position according to the choosen alignment
277 if( $this->iHeaderAlign == 'center' ) {
278 $xp = (($x-$this->iLeftColMargin)+($x+$this->iWidth[$i]))/2;
280 elseif( $this->iHeaderAlign == 'right' ) {
281 $xp = $x +$this->iWidth[$i]-$this->iRightColMargin;
284 $txt->Stroke($aImg,$xp,$yTop+$this->iTopHeaderMargin);
285 $x += $this->iWidth[$i];
287 $aImg->SetColor($this->iColor);
288 $aImg->Line($x,$yTop,$x,$aYBottom);
292 $aImg->SetColor($this->iColor);
293 $aImg->Line($aXLeft,$yTop, $aXRight,$yTop);
295 // Stroke vertical column dividers
297 $this->GetColStart($aImg,$cols);
299 for( $i=1; $i < $n; ++$i ) {
300 $this->vgrid->Stroke($aImg,$cols[$i],$aYBottom,$cols[$i],
301 $aImg->height - $aImg->bottom_margin);
307 //===================================================
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;
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);
328 $this->img->SetMargin($aWidth/17,$aWidth/17,$aHeight/7,$aHeight/10);
330 $this->scale->ShowHeaders(GANTT_HWEEK|GANTT_HDAY);
339 function SetSimpleFont($aFont,$aSize) {
340 $this->iSimpleFont = $aFont;
341 $this->iSimpleFontSize = $aSize;
344 function SetSimpleStyle($aBand,$aColor,$aBkgColor) {
345 $this->iSimpleStyle = $aBand;
346 $this->iSimpleColor = $aColor;
347 $this->iSimpleBkgColor = $aSimpleBkgColor;
350 // A utility function to help create basic Gantt charts
351 function CreateSimple($data,$constrains=array(),$progress=array()) {
353 for( $i=0; $i < count($data); ++$i) {
354 switch( $data[$i][1] ) {
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');
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');
372 $a->SetPattern(BAND_SOLID,'black');
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);
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]);
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);
411 die('Unknown activity type');
416 $a->caption->Set($data[$i][$csimpos-1]);
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]);
423 if( !empty($data[$i][$csimpos+2]) ) {
424 $a->title->SetCSIMTarget($data[$i][$csimpos+2]);
425 $a->title->SetCSIMAlt($data[$i][$csimpos+3]);
433 // Set what headers should be shown
434 function ShowHeaders($aFlg) {
435 $this->scale->ShowHeaders($aFlg);
438 // Specify the fraction of the font height that should be added
439 // as vertical margin
440 function SetLabelVMarginFactor($aVal) {
441 $this->iLabelVMarginFactor = $aVal;
444 // Synonym to the method above
445 function SetVMarginFactor($aVal) {
446 $this->iLabelVMarginFactor = $aVal;
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];
457 $this->iObj[] = $aObject;
460 // Override inherit method from Graph and give a warning message
461 function SetScale() {
462 JpGraphError::Raise("SetScale() is not meaningfull with Gantt charts.");
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);
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() {
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);
492 $m=max($m,$this->iObj[$i]->title->GetWidth($this->img));
498 // Get the maximum height of the titles for the bars
499 function GetMaxLabelHeight() {
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));
510 function GetMaxBarAbsHeight() {
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));
521 // Get the maximum used line number (vertical position) for bars
522 function GetBarMaxLineNumber() {
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());
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()));
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);
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
557 $lm=30;$rm=30;$tm=20;$bm=30;
558 if( BRAND_TIMING ) $bm += 10;
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);
565 // Add the height of the scale titles
566 $h=$this->scale->GetHeaderHeight();
569 // Calculate the top margin needed for title and subtitle
570 if( $this->title->t != "" ) {
571 $tm += $this->title->GetFontHeight($this->img);
573 if( $this->subtitle->t != "" ) {
574 $tm += $this->subtitle->GetFontHeight($this->img);
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
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.
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;
591 elseif( $this->scale->IsDisplayWeek() ) {
594 elseif( $this->scale->IsDisplayMonth() ) {
600 $nd=$this->scale->GetNumberOfDays();
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 ) {
609 case DAYSTYLE_LONGDAYDATE1 :
610 $txt = "Monday 23 Jun";
612 case DAYSTYLE_LONGDAYDATE2 :
613 $txt = "Monday 23 Jun 2003";
615 case DAYSTYLE_SHORT :
618 case DAYSTYLE_SHORTDAYDATE1 :
621 case DAYSTYLE_SHORTDAYDATE2 :
624 case DAYSTYLE_SHORTDAYDATE3 :
627 case DAYSTYLE_SHORTDATE1 :
630 case DAYSTYLE_SHORTDATE2 :
633 case DAYSTYLE_SHORTDATE3 :
636 case DAYSTYLE_CUSTOM :
637 $txt = date($this->scale->day->iLabelFormStr,
638 strtotime('2003-12-20 18:00'));
640 case DAYSTYLE_ONELETTER :
645 $fw = $this->scale->day->GetStrWidth($this->img,$txt)+6;
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:
662 case HOURSTYLE_HAMPM:
665 case HOURSTYLE_CUSTOM:
666 $txt = date($this->scale->hour->iLabelFormStr,strtotime('2003-12-20 18:00'));
674 $hfw = $this->scale->hour->GetStrWidth($this->img,$txt)+6;
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'));
690 $mfw = $this->scale->minute->GetStrWidth($this->img,$txt2)+6;
691 $n2 = ceil(60 / $this->scale->minute->GetIntervall() );
694 $hfw = $hfw < $mw ? $mw : $hfw ;
695 $n = ceil(24*60 / $this->scale->TimeToMinutes($this->scale->hour->GetIntervall()) );
697 $fw = $fw < $hw ? $hw : $fw ;
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.
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'));
719 $mfw = $this->scale->minute->GetStrWidth($this->img,$txt)+6;
720 $n = ceil(60 / $this->scale->TimeToMinutes($this->scale->minute->GetIntervall()) );
722 $fw = $fw < $mw ? $mw : $fw ;
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
730 $fsw = strlen($this->scale->week->iLabelFormStr);
731 if( $this->scale->week->iStyle==WEEKSTYLE_FIRSTDAY2WNBR ) {
734 elseif( $this->scale->week->iStyle==WEEKSTYLE_FIRSTDAYWNBR ) {
741 $ww = $fsw*$this->scale->week->GetFontWidth($this->img);
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.
757 if( !$this->scale->IsDisplayWeek() ) {
762 // Has the user specified a width or do we need to
764 if( $this->img->width <= 0 ) {
765 // Now determine the width for the activity titles column
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));
774 // Add the width of the vertivcal divider line
775 $titlewidth += $this->scale->divider->iWeight*2;
778 // Now get the total width taking
779 // titlewidth, left and rigt margin, dayfont size
781 $width = $titlewidth + $nd*$fw + $lm+$rm;
784 $width = $this->img->width;
786 $this->img->CreateImgCanvas($width,$height);
787 $this->img->SetMargin($lm,$rm,$tm,$bm);
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;
799 $m = $this->scale->actinfo->iLeftColMargin + $this->scale->actinfo->iRightColMargin;
801 for( $i=0; $i < $n; ++$i ) {
802 $tmp = $this->iObj[$i]->title->GetColWidth($this->img,$m);
804 for( $j=0; $j < $nn; ++$j ) {
808 $w[$j] = max($w[$j],$tmp[$j]);
814 // Stroke the gantt chart
815 function Stroke($aStrokeFileName="") {
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
824 $_csim = ($aStrokeFileName===_CSIM_SPECIALFILE);
826 // Should we autoscale dates?
827 if( !$this->scale->IsRangeSet() ) {
828 list($min,$max) = $this->GetBarMinMax();
829 $this->scale->SetRange($min,$max);
832 $this->scale->AdjustStartEndDay();
834 // Check if we should autoscale the image
837 // Should we start from the top or just spread the bars out even over the
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));
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;
848 $maxwidth=max($this->scale->actinfo->GetWidth($this->img),
849 max($this->GetMaxLabelWidth(),
850 $this->scale->tableTitle->GetWidth($this->img)));
852 $this->scale->SetLabelWidth($maxwidth+$this->scale->divider->iWeight);//*(1+$this->iLabelHMarginFactor));
855 $this->StrokePlotArea();
857 $this->scale->Stroke();
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++;
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);
873 $this->StrokeConstrains();
874 $this->StrokeTitles();
875 $this->footer->Stroke($this->img);
877 // If the filename is given as the special "__handle"
878 // then the image handler is returned and the image is NOT
880 if( $aStrokeFileName == _IMG_HANDLER ) {
881 return $this->img->img;
884 // Finally stream the generated picture
885 $this->cache->PutAndStream($this->img,$this->cache_name,$this->inline,
891 function StrokeConstrains() {
892 $n = count($this->iObj);
894 // Stroke all constrains
895 for($i=0; $i < $n; ++$i) {
897 $numConstrains = count($this->iObj[$i]->constraints);
899 for( $k = 0; $k < $numConstrains; $k++ ) {
900 $vpos = $this->iObj[$i]->constraints[$k]->iConstrainRow;
902 $c1 = $this->iObj[$i]->iConstrainPos;
904 // Find out which object is on the target row
906 for( $j=0; $j < $n && $targetobj == -1; ++$j ) {
907 if( $this->iObj[$j]->iVPos == $vpos ) {
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.');
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]);
925 $link = new GanttLink($c1[2],$c1[1],$c2[0],$c2[3]);
929 case CONSTRAIN_STARTEND:
930 if( $c1[1] < $c2[1] ) {
931 $link = new GanttLink($c1[0],$c1[3],$c2[2],$c2[1]);
934 $link = new GanttLink($c1[0],$c1[1],$c2[2],$c2[3]);
938 case CONSTRAIN_ENDEND:
939 if( $c1[1] < $c2[1] ) {
940 $link = new GanttLink($c1[2],$c1[3],$c2[2],$c2[1]);
943 $link = new GanttLink($c1[2],$c1[1],$c2[2],$c2[3]);
947 case CONSTRAIN_STARTSTART:
948 if( $c1[1] < $c2[1] ) {
949 $link = new GanttLink($c1[0],$c1[3],$c2[0],$c2[1]);
952 $link = new GanttLink($c1[0],$c1[1],$c2[0],$c2[3]);
957 JpGraphError::Raise('Unknown constrain type specified from row='.
958 $this->iObj[$i]->iVPos.
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);
967 $link->Stroke($this->img);
974 function GetCSIMAreas() {
975 if( !$this->iHasStroked )
976 $this->Stroke(_CSIM_SPECIALFILE);
978 $n = count($this->iObj);
979 for( $i=$n-1; $i >= 0; --$i )
980 $csim .= $this->iObj[$i]->GetCSIMArea();
985 //===================================================
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);
1004 var $iBuiltinIcon = null;
1008 return $this->iLen ;
1011 function GetImg($aIdx) {
1012 if( $aIdx < 0 || $aIdx >= $this->iLen ) {
1013 JpGraphError::Raise('Illegal icon index for Gantt builtin icon ['.$aIdx.']');
1015 return Image::CreateFromString(base64_decode($this->iBuiltinIcon[$aIdx][1]));
1018 function PredefIcons() {
1019 //==========================================================
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=' ;
1039 //==========================================================
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=' ;
1058 //==========================================================
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' ;
1073 //==========================================================
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' ;
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=' ;
1110 //==========================================================
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==' ;
1124 //==========================================================
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=' ;
1149 //==========================================================
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' ;
1168 //==========================================================
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==' ;
1186 //==========================================================
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==' ;
1200 //==========================================================
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' ;
1234 //==========================================================
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' ;
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' ;
1295 $this->iLen = count($this->iBuiltinIcon);
1299 //===================================================
1300 // Global cache for builtin images
1301 //===================================================
1302 $_gPredefIcons = new PredefIcons();
1304 //===================================================
1306 // Description: Holds properties for an icon image
1307 //===================================================
1310 var $iWidth,$iHeight;
1311 var $ixalign='left',$iyalign='center';
1314 function IconImage($aIcon,$aScale=1) {
1315 GLOBAL $_gPredefIcons ;
1316 if( is_string($aIcon) ) {
1317 $this->iGDImage = Graph::LoadBkgImage('',$aIcon);
1319 elseif( is_integer($aIcon) ) {
1321 $this->iGDImage = $_gPredefIcons->GetImg($aIcon);
1324 JpGraphError::Raise('Argument to IconImage must be string or integer');
1326 $this->iScale = $aScale;
1327 $this->iWidth = Image::GetWidth($this->iGDImage);
1328 $this->iHeight = Image::GetHeight($this->iGDImage);
1331 function GetWidth() {
1332 return round($this->iScale*$this->iWidth);
1335 function GetHeight() {
1336 return round($this->iScale*$this->iHeight);
1339 function SetAlign($aX='left',$aY='center') {
1341 $this->ixalign = $aX;
1342 $this->iyalign = $aY;
1346 function Stroke($aImg,$x,$y) {
1348 if( $this->ixalign == 'right' ) {
1349 $x -= $this->iWidth;
1351 elseif( $this->ixalign == 'center' ) {
1352 $x -= round($this->iWidth/2*$this->iScale);
1355 if( $this->iyalign == 'bottom' ) {
1356 $y -= $this->iHeight;
1358 elseif( $this->iyalign == 'center' ) {
1359 $y -= round($this->iHeight/2*$this->iScale);
1362 $aImg->Copy($this->iGDImage,
1364 round($this->iWidth*$this->iScale),round($this->iHeight*$this->iScale),
1365 $this->iWidth,$this->iHeight);
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";
1379 var $iHAlign="left",$iVAlign="bottom";
1380 var $csimtarget='',$csimalt='';
1384 function TextProperty($aTxt='') {
1385 $this->iText = $aTxt;
1390 function Set($aTxt) {
1391 $this->iText = $aTxt;
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;
1403 function SetCSIMAlt($aAlt) {
1404 $this->csimalt=$aAlt;
1408 function SetColor($aColor) {
1409 $this->iColor = $aColor;
1412 function HasTabs() {
1413 if( is_string($this->iText) ) {
1414 return substr_count($this->iText,"\t") > 0;
1416 elseif( is_array($this->iText) ) {
1421 // Get number of tabs in string
1422 function GetNbrTabs() {
1423 if( is_string($this->iText) ) {
1424 return substr_count($this->iText,"\t") ;
1432 function Align($aHAlign,$aVAlign="bottom") {
1433 $this->iHAlign=$aHAlign;
1434 $this->iVAlign=$aVAlign;
1438 function SetAlign($aHAlign,$aVAlign="bottom") {
1439 $this->iHAlign=$aHAlign;
1440 $this->iVAlign=$aVAlign;
1444 function SetFont($aFFamily,$aFStyle=FS_NORMAL,$aFSize=10) {
1445 $this->iFFamily = $aFFamily;
1446 $this->iFStyle = $aFStyle;
1447 $this->iFSize = $aFSize;
1450 function IsColumns() {
1451 return is_array($this->iText) ;
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';
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;
1469 for($i=0; $i < count($tmp); ++$i) {
1470 $res[$i] = $aImg->GetTextWidth($tmp[$i]);
1471 $tot += $res[$i]*$aTabExtraMargin;
1473 return array(round($tot),$res);
1476 elseif( is_object($this->iText) ) {
1478 return $this->iText->GetWidth()+2*$extra_margin;
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;
1490 if( is_object($tmp) === false ) {
1491 JpGraphError::Raise($errmsg);
1493 $w += $tmp->GetWidth()+$extra_margin;
1499 JpGraphError::Raise($errmsg);
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;
1517 if( is_object($tmp) === false ) {
1518 JpGraphError::Raise($errmsg);
1520 $w[$i] = $tmp->GetWidth()+$aMargin;
1526 return array($this->GetWidth($aImg));
1530 // Get total height of text
1531 function GetHeight($aImg) {
1532 $aImg->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
1533 return $aImg->GetFontHeight();
1536 // Unhide/hide the text
1537 function Show($aShow=true) {
1538 $this->iShow=$aShow;
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));
1557 $n = count($this->iText);
1558 $ax = is_array($aX) ;
1559 $ay = is_array($aY) ;
1561 // Nothing; both are already arrays
1564 $aY = array_fill(0,$n,$aY);
1567 $aX = array_fill(0,$n,$aX);
1570 $aX = array_fill(0,$n,$aX);
1571 $aY = array_fill(0,$n,$aY);
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]);
1581 $aImg->StrokeText($aX[$i],$aY[$i],str_replace("\t"," ",$tmp));
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]);
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;
1614 function HeaderProperty() {
1615 $this->grid = new LineProperty();
1620 function Show($aShow=true) {
1621 $this->iShowLabels = $aShow;
1624 function SetIntervall($aInt) {
1625 $this->iIntervall = $aInt;
1628 function GetIntervall() {
1629 return $this->iIntervall ;
1632 function SetFont($aFFamily,$aFStyle=FS_NORMAL,$aFSize=10) {
1633 $this->iFFamily = $aFFamily;
1634 $this->iFStyle = $aFStyle;
1635 $this->iFSize = $aFSize;
1638 function SetFontColor($aColor) {
1639 $this->iTextColor = $aColor;
1642 function GetFontHeight($aImg) {
1643 $aImg->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
1644 return $aImg->GetFontHeight();
1647 function GetFontWidth($aImg) {
1648 $aImg->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
1649 return $aImg->GetFontWidth();
1652 function GetStrWidth($aImg,$aStr) {
1653 $aImg->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
1654 return $aImg->GetTextWidth($aStr);
1657 function SetStyle($aStyle) {
1658 $this->iStyle = $aStyle;
1661 function SetBackgroundColor($aColor) {
1662 $this->iBackgroundColor=$aColor;
1665 function SetFrameWeight($aWeight) {
1666 $this->iFrameWeight=$aWeight;
1669 function SetFrameColor($aColor) {
1670 $this->iFrameColor=$aColor;
1673 // Only used by day scale
1674 function SetWeekendColor($aColor) {
1675 $this->iWeekendBackgroundColor=$aColor;
1678 // Only used by day scale
1679 function SetSundayFontColor($aColor) {
1680 $this->iSundayTextColor=$aColor;
1683 function SetTitleVertMargin($aMargin) {
1684 $this->iTitleVertMargin=$aMargin;
1687 function SetLabelFormatString($aStr) {
1688 $this->iLabelFormStr=$aStr;
1691 function SetFormatString($aStr) {
1692 $this->SetLabelFormatString($aStr);
1698 //===================================================
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 //===================================================
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
1713 // The width of the labels (defaults to the widest of all labels)
1715 // Out image to stroke the scale to
1717 var $iTableHeaderBackgroundColor="white",$iTableHeaderFrameColor="black";
1718 var $iTableHeaderFrameWeight=1;
1719 var $iAvailableHeight=-1,$iVertSpacing=-1,$iVertHeaderSize=-1;
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
1729 function GanttScale(&$aImg) {
1730 $this->iImg = &$aImg;
1731 $this->iDateLocale = new DateLocale();
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");
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");
1746 $this->day = new HeaderProperty();
1747 $this->day->grid->SetColor("gray");
1748 $this->day->SetLabelFormatString('l');
1750 $this->week = new HeaderProperty();
1751 $this->week->SetLabelFormatString("w%d");
1752 $this->week->SetFont(FF_FONT1);
1754 $this->month = new HeaderProperty();
1755 $this->month->SetFont(FF_FONT1,FS_BOLD);
1757 $this->year = new HeaderProperty();
1758 $this->year->SetFont(FF_FONT1,FS_BOLD);
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');
1767 $this->tableTitle=new TextProperty();
1768 $this->tableTitle->Show(false);
1769 $this->actinfo = new GanttActivityInfo();
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);
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);
1788 if( $aFlg & GANTT_HHOUR ) {
1789 $this->day->grid->SetColor("black");
1793 // Should the weekend background stretch all the way down in the plotarea
1794 function UseWeekendBackground($aShow) {
1795 $this->iUsePlotWeekendBackground = $aShow;
1798 // Have a range been specified?
1799 function IsRangeSet() {
1800 return $this->iStartDate!=-1 && $this->iEndDate!=-1;
1803 // Should the layout be from top or even?
1804 function SetVertLayout($aLayout) {
1805 $this->iVertLayout = $aLayout;
1808 // Which locale should be used?
1809 function SetDateLocale($aLocale) {
1810 $this->iDateLocale->Set($aLocale);
1813 // Number of days we are showing
1814 function GetNumberOfDays() {
1815 return round(($this->iEndDate-$this->iStartDate)/SECPERDAY);
1818 // The width of the actual plot area
1819 function GetPlotWidth() {
1821 return $img->width - $img->left_margin - $img->right_margin;
1824 // Specify the width of the titles(labels) for the activities
1825 // (This is by default set to the minimum width enought for the
1827 function SetLabelWidth($aLabelWidth) {
1828 $this->iLabelWidth=$aLabelWidth;
1831 // Which day should the week start?
1832 // 0==Sun, 1==Monday, 2==Tuesday etc
1833 function SetWeekStart($aStartDay) {
1834 $this->iWeekStart = $aStartDay % 7;
1836 //Recalculate the startday since this will change the week start
1837 $this->SetRange($this->iStartDate,$this->iEndDate);
1840 // Do we show min scale?
1841 function IsDisplayMinute() {
1842 return $this->minute->iShowLabels;
1845 // Do we show day scale?
1846 function IsDisplayHour() {
1847 return $this->hour->iShowLabels;
1851 // Do we show day scale?
1852 function IsDisplayDay() {
1853 return $this->day->iShowLabels;
1856 // Do we show week scale?
1857 function IsDisplayWeek() {
1858 return $this->week->iShowLabels;
1861 // Do we show month scale?
1862 function IsDisplayMonth() {
1863 return $this->month->iShowLabels;
1866 // Do we show year scale?
1867 function IsDisplayYear() {
1868 return $this->year->iShowLabels;
1871 // Specify spacing (in percent of bar height) between activity bars
1872 function SetVertSpacing($aSpacing) {
1873 $this->iVertSpacing = $aSpacing;
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);
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() {
1888 if( !($this->IsDisplayYear() ||$this->IsDisplayMonth() || $this->IsDisplayWeek()) ) {
1893 // Get day in week for start and ending date (Sun==0)
1894 $ds=strftime("%w",$this->iStartDate);
1895 $de=strftime("%w",$this->iEndDate);
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);
1905 $d = $this->iStartDate;
1906 $adjdate = strtotime(($this->iWeekStart-$ds).' day',$d /*$this->iStartDate*/ );
1907 $this->iStartDate = $adjdate;
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;
1919 // Specify background for the table title area (upper left corner of the table)
1920 function SetTableTitleBackground($aColor) {
1921 $this->iTableHeaderBackgroundColor = $aColor;
1924 ///////////////////////////////////////
1927 // Determine the height of all the scale headers combined
1928 function GetHeaderHeight() {
1931 if( $this->minute->iShowLabels ) {
1932 $height += $this->minute->GetFontHeight($img);
1933 $height += $this->minute->iTitleVertMargin;
1935 if( $this->hour->iShowLabels ) {
1936 $height += $this->hour->GetFontHeight($img);
1937 $height += $this->hour->iTitleVertMargin;
1939 if( $this->day->iShowLabels ) {
1940 $height += $this->day->GetFontHeight($img);
1941 $height += $this->day->iTitleVertMargin;
1943 if( $this->week->iShowLabels ) {
1944 $height += $this->week->GetFontHeight($img);
1945 $height += $this->week->iTitleVertMargin;
1947 if( $this->month->iShowLabels ) {
1948 $height += $this->month->GetFontHeight($img);
1949 $height += $this->month->iTitleVertMargin;
1951 if( $this->year->iShowLabels ) {
1952 $height += $this->year->GetFontHeight($img);
1953 $height += $this->year->iTitleVertMargin;
1958 // Get width (in pixels) for a single day
1959 function GetDayWidth() {
1960 return ($this->GetPlotWidth()-$this->iLabelWidth+1)/$this->GetNumberOfDays();
1963 // Get width (in pixels) for a single hour
1964 function GetHourWidth() {
1965 return $this->GetDayWidth() / 24 ;
1968 function GetMinuteWidth() {
1969 return $this->GetHourWidth() / 60 ;
1972 // Nuber of days in a year
1973 function GetNumDaysInYear($aYear) {
1974 if( $this->IsLeap($aYear) )
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)
1989 // Credit to Nicolas Hoizey <nhoizey@phpheaven.net> for this elegant
1990 // version of Week Nbr calculation.
1992 $day = $this->NormalizeDate($aDate);
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."
2001 Be carefull, with PHP, -3 % 7 = -3, instead of 4 !!!
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 ---------------------------------------------------------------------------*/
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;
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) )
2026 function GetYear($aDate) {
2027 return 0+Date("Y",$aDate);
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];
2037 return $days[$aMonth];
2041 function GetMonthDayNbr($aDate) {
2042 return 0+strftime("%d",$aDate);
2046 function GetYearDayNbr($aDate) {
2047 return 0+strftime("%j",$aDate);
2051 function GetMonthNbr($aDate) {
2052 return 0+strftime("%m",$aDate);
2055 // Translate a date to screen coordinates (horizontal scale)
2056 function TranslateDate($aDate) {
2057 $aDate = $this->NormalizeDate($aDate);
2059 return ($aDate-$this->iStartDate)/SECPERDAY*$this->GetDayWidth()+$img->left_margin+$this->iLabelWidth;;
2062 // Get screen coordinatesz for the vertical position for a bar
2063 function TranslateVertPos($aPos) {
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);
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);
2078 // What is the vertical spacing?
2079 function GetVertSpacing() {
2080 return $this->iVertSpacing;
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) )
2090 JpGraphError::Raise("Unknown date format in GanttScale ($aDate).");
2094 // Convert a time string to minutes
2096 function TimeToMinutes($aTimeString) {
2097 // Split in hours and minutes
2098 $pos=strpos($aTimeString,':');
2100 if( $pos === false ) {
2101 $hourint = $aTimeString;
2105 $hourint = floor(substr($aTimeString,0,$pos));
2106 $minint = floor(substr($aTimeString,$pos+1));
2108 $minint += 60 * $hourint;
2112 // Stroke the day scale (including gridlines)
2113 function StrokeMinutes($aYCoord,$getHeight=false) {
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;
2122 return $yb - $img->top_margin;
2124 $xb = $img->width-$img->right_margin+1;
2125 $img->SetColor($this->minute->iBackgroundColor);
2126 $img->FilledRectangle($xt,$yt,$xb,$yb);
2129 $img->SetTextAlign("center");
2130 $day = date('w',$this->iStartDate);
2131 $minint = $this->minute->GetIntervall() ;
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.');
2139 $datestamp = $this->iStartDate;
2140 $width = $this->GetHourWidth() / $n ;
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.');
2146 $nh = ceil(24*60 / $this->TimeToMinutes($this->hour->GetIntervall()) );
2147 $nd = $this->GetNumberOfDays();
2148 // Convert to intervall to seconds
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 ) {
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);
2159 $img->FilledRectangle($x,$yt+$this->day->iFrameWeight,$x+$width,$yb-$this->day->iFrameWeight);
2165 $img->SetColor($this->day->iSundayTextColor);
2167 $img->SetColor($this->day->iTextColor);
2169 switch( $this->minute->iStyle ) {
2170 case MINUTESTYLE_CUSTOM:
2171 $txt = date($this->minute->iLabelFormStr,$datestamp);
2173 case MINUTESTYLE_MM:
2176 $txt = date('i',$datestamp);
2179 $img->StrokeText(round($x+$width/2),round($yb-$this->minute->iTitleVertMargin),$txt);
2181 // FIXME: The rounding problem needs to be solved properly ...
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);
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;
2206 // Stroke the day scale (including gridlines)
2207 function StrokeHours($aYCoord,$getHeight=false) {
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;
2216 return $yb - $img->top_margin;
2218 $xb = $img->width-$img->right_margin+1;
2219 $img->SetColor($this->hour->iBackgroundColor);
2220 $img->FilledRectangle($xt,$yt,$xb,$yb);
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);
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 ) {
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);
2243 $img->FilledRectangle($x,$yt+$this->day->iFrameWeight,$x+$width,$yb-$this->day->iFrameWeight);
2249 $img->SetColor($this->day->iSundayTextColor);
2251 $img->SetColor($this->day->iTextColor);
2253 switch( $this->hour->iStyle ) {
2254 case HOURSTYLE_HMAMPM:
2256 $txt = date('g:ia',$datestamp);
2260 $txt = date('H',$datestamp);
2262 case HOURSTYLE_HAMPM:
2263 $txt = date('ga',$datestamp);
2265 case HOURSTYLE_CUSTOM:
2266 $txt = date($this->hour->iLabelFormStr,$datestamp);
2268 case HOURSTYLE_HM24:
2270 $txt = date('H:i',$datestamp);
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);
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;
2288 // Stroke the day scale (including gridlines)
2289 function StrokeDays($aYCoord,$getHeight=false) {
2290 $wdays=$this->iDateLocale->GetDayAbb();
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;
2299 return $yb - $img->top_margin;
2301 $xb=$img->width-$img->right_margin+1;
2302 $img->SetColor($this->day->iBackgroundColor);
2303 $img->FilledRectangle($xt,$yt,$xb,$yb);
2306 $img->SetTextAlign("center");
2307 $day = date('w',$this->iStartDate);
2308 $datestamp = $this->iStartDate;
2310 $doback = !($this->hour->iShowLabels || $this->minute->iShowLabels);
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);
2319 $img->FilledRectangle($x,$yt+$this->day->iFrameWeight,
2320 $x+$daywidth,$yb-$this->day->iFrameWeight);
2322 switch( $this->day->iStyle ) {
2325 $txt = date('l',$datestamp);
2327 case DAYSTYLE_SHORT:
2329 $txt = date('D',$datestamp);
2331 case DAYSTYLE_SHORTDAYDATE1:
2333 $txt = date('D j/n',$datestamp);
2335 case DAYSTYLE_SHORTDAYDATE2:
2337 $txt = date('D j M',$datestamp);
2339 case DAYSTYLE_SHORTDAYDATE3:
2340 // "Mon 23 Jun 2003"
2341 $txt = date('D j M Y',$datestamp);
2343 case DAYSTYLE_LONGDAYDATE1:
2345 $txt = date('l j M',$datestamp);
2347 case DAYSTYLE_LONGDAYDATE2:
2348 // "Monday 23 Jun 2003"
2349 $txt = date('l j M Y',$datestamp);
2351 case DAYSTYLE_SHORTDATE1:
2353 $txt = date('j/n',$datestamp);
2355 case DAYSTYLE_SHORTDATE2:
2357 $txt = date('j M',$datestamp);
2359 case DAYSTYLE_SHORTDATE3:
2361 $txt = date('D j',$datestamp);
2363 case DAYSTYLE_CUSTOM:
2365 $txt = date($this->day->iLabelFormStr,$datestamp);
2367 case DAYSTYLE_ONELETTER:
2370 $txt = substr(date('D',$datestamp),0,1);
2375 $img->SetColor($this->day->iSundayTextColor);
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);
2384 $datestamp += SECPERDAY;
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;
2394 // Stroke week header and grid
2395 function StrokeWeeks($aYCoord,$getHeight=false) {
2396 if( $this->week->iShowLabels ) {
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;
2403 return $yb - $img->top_margin;
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);
2416 if( $this->week->iStyle==WEEKSTYLE_WNBR ) {
2417 $img->SetTextAlign("center");
2418 $txtOffset = $weekwidth/2+1;
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");
2428 JpGraphError::Raise("Unknown formatting style for week.");
2430 for($i=0; $i<$this->GetNumberOfDays()/7; ++$i, $x+=$weekwidth) {
2431 $img->PushColor($this->week->iTextColor);
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;
2445 if( $this->week->iStyle==WEEKSTYLE_FIRSTDAYWNBR ||
2446 $this->week->iStyle==WEEKSTYLE_FIRSTDAY2WNBR ) {
2447 $w = sprintf($this->week->iLabelFormStr,$weeknbr);
2451 $img->StrokeText(round($x+$txtOffset),
2452 round($yb-$this->week->iTitleVertMargin),$txt);
2454 $week = strtotime('+7 day',$week);
2455 $weeknbr = $this->GetWeekNbr($week);
2457 $img->Line($x,$yt,$x,$yb);
2458 $this->week->grid->Stroke($img,$x,$yb,$x,$img->height-$img->bottom_margin);
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;
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:
2476 case MONTHSTYLE_LONGNAME:
2479 case MONTHSTYLE_SHORTNAMEYEAR2:
2480 $m=$sn." '".substr("".$year,2);
2482 case MONTHSTYLE_SHORTNAMEYEAR4:
2485 case MONTHSTYLE_LONGNAMEYEAR2:
2486 $m=$ln." '".substr("".$year,2);
2488 case MONTHSTYLE_LONGNAMEYEAR4:
2495 // Stroke month scale and gridlines
2496 function StrokeMonths($aYCoord,$getHeight=false) {
2497 if( $this->month->iShowLabels ) {
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;
2503 return $yb - $img->top_margin;
2505 $monthnbr = $this->GetMonthNbr($this->iStartDate)-1;
2506 $xt=$img->left_margin+$this->iLabelWidth;
2507 $xb=$img->width-$img->right_margin+1;
2509 $img->SetColor($this->month->iBackgroundColor);
2510 $img->FilledRectangle($xt,$yt,$xb,$yb);
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);
2521 $monthwidth=$this->GetDayWidth()*($this->GetNumDaysInMonth($monthnbr,$year)-$this->GetMonthDayNbr($this->iStartDate)+1);
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),
2531 $x = $xt + $monthwidth;
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);
2537 if( $monthnbr==12 ) {
2541 $monthName = $this->GetMonthLabel($monthnbr,$year);
2542 $monthwidth=$this->GetDayWidth()*$this->GetNumDaysInMonth($monthnbr,$year);
2543 if( $x + $monthwidth < $xb )
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);
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;
2562 // Stroke year scale and gridlines
2563 function StrokeYears($aYCoord,$getHeight=false) {
2564 if( $this->year->iShowLabels ) {
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;
2571 return $yb - $img->top_margin;
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);
2584 $yearwidth=$this->GetDayWidth()*($this->GetNumDaysInYear($year)-$this->GetYearDayNbr($this->iStartDate)+1);
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),
2594 $x = $xt + $yearwidth;
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);
2600 $yearwidth=$this->GetDayWidth()*$this->GetNumDaysInYear($year);
2601 if( $x + $yearwidth < $xb )
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),
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;
2621 // Stroke table title (upper left corner)
2622 function StrokeTableHeaders($aYBottom) {
2624 $xt=$img->left_margin;
2625 $yt=$img->top_margin;
2626 $xb=$xt+$this->iLabelWidth;
2627 $yb=$aYBottom+$img->top_margin;
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);
2639 $this->actinfo->Stroke($img,$xt,$yt,$xb,$yb,$this->tableTitle->iShow);
2642 // Draw the horizontal dividing line
2643 $this->dividerh->Stroke($img,$xt,$yb,$img->width-$img->right_margin,$yb);
2645 // Draw the vertical dividing line
2646 // We do the width "manually" since we want the line only to grow
2648 $fancy = $this->divider->iStyle == 'fancy' ;
2650 $this->divider->iStyle = 'solid';
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);
2660 // Should we draw "fancy" divider
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);
2673 // Main entry point to stroke scale
2675 if( !$this->IsRangeSet() )
2676 JpGraphError::Raise("Gantt scale has not been specified.");
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).');
2684 // Stroke all headers. As argument we supply the offset from the
2685 // top which depends on any previous headers
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);
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);
2704 // Now when we now the oaverall size of the scale headers
2705 // we can stroke the overall table headers
2706 $this->StrokeTableHeaders($offmin);
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;
2717 //===================================================
2718 // CLASS GanttConstraint
2719 // Just a structure to store all the values for a constraint
2720 //===================================================
2721 class GanttConstraint {
2722 var $iConstrainType;
2724 var $iConstrainColor;
2725 var $iConstrainArrowSize;
2726 var $iConstrainArrowType;
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;
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='';
2752 var $constraints = array();
2753 var $iConstrainPos=array();
2755 function GanttPlotObject() {
2756 $this->title = new TextProperty();
2757 $this->title->Align("left","center");
2758 $this->caption = new TextProperty();
2761 function GetCSIMArea() {
2762 return $this->csimarea;
2765 function SetCSIMTarget($aTarget,$aAltText='') {
2766 $this->csimtarget=$aTarget;
2767 $this->csimalt=$aAltText;
2770 function SetCSIMAlt($aAlt) {
2771 $this->csimalt=$aAlt;
2774 function SetConstrain($aRow,$aType,$aColor='black',$aArrowSize=ARROW_S2,$aArrowType=ARROWT_SOLID) {
2775 $this->constraints[] = new GanttConstraint($aRow, $aType, $aColor, $aArrowSize, $aArrowType);
2778 function SetConstrainPos($xt,$yt,$xb,$yb) {
2779 $this->iConstrainPos = array($xt,$yt,$xb,$yb);
2783 function GetConstrain() {
2784 return array($this->iConstrainRow,$this->iConstrainType);
2788 function GetMinDate() {
2789 return $this->iStart;
2792 function GetMaxDate() {
2793 return $this->iStart;
2796 function SetCaptionMargin($aMarg) {
2797 $this->iCaptionMargin=$aMarg;
2800 function GetAbsHeight($aImg) {
2804 function GetLineNbr() {
2805 return $this->iVPos;
2808 function SetLabelLeftMargin($aOff) {
2809 $this->iLabelLeftMargin=$aOff;
2812 function StrokeActInfo($aImg,$aScale,$aYPos) {
2814 $aScale->actinfo->GetColStart($aImg,$cols,true);
2815 $this->title->Stroke($aImg,$cols,$aYPos);
2819 //===================================================
2821 // Holds parameters for the progress indicator
2822 // displyed within a bar
2823 //===================================================
2825 var $iProgress=-1, $iColor="black", $iFillColor='black';
2826 var $iPattern=GANTT_SOLID;
2827 var $iDensity=98, $iHeight=0.65;
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;
2835 function SetPattern($aPattern,$aColor="blue",$aDensity=98) {
2836 $this->iPattern = $aPattern;
2837 $this->iColor = $aColor;
2838 $this->iDensity = $aDensity;
2841 function SetFillColor($aColor) {
2842 $this->iFillColor = $aColor;
2845 function SetHeight($aHeight) {
2846 $this->iHeight = $aHeight;
2850 //===================================================
2852 // Responsible for formatting individual gantt bars
2853 //===================================================
2854 class GanttBar extends GanttPlotObject {
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;
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;
2874 $this->iEnd = $aEnd;
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();
2892 function SetShadow($aShadow=true,$aColor="gray") {
2893 $this->iShadow=$aShadow;
2894 $this->iShadowColor=$aColor;
2897 function GetMaxDate() {
2901 function SetHeight($aHeight) {
2902 $this->iHeightFactor = $aHeight;
2905 function SetColor($aColor) {
2906 $this->iFrameColor = $aColor;
2909 function SetFillColor($aColor) {
2910 $this->iFillColor = $aColor;
2913 function GetAbsHeight($aImg) {
2914 if( is_int($this->iHeightFactor) || $this->leftMark->show || $this->rightMark->show ) {
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);
2928 function SetPattern($aPattern,$aColor="blue",$aDensity=95) {
2929 $this->iPattern = $aPattern;
2930 $this->iPatternColor = $aColor;
2931 $this->iPatternDensity = $aDensity;
2934 function Stroke($aImg,$aScale) {
2935 $factory = new RectPatternFactory();
2936 $prect = $factory->Create($this->iPattern,$this->iPatternColor);
2937 $prect->SetDensity($this->iPatternDensity);
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;
2947 JpGraphError::Raise("Specified height (".$this->iHeightFactor.") for gantt bar is out of range.");
2949 // Clip date to min max dates to show
2950 $st = $aScale->NormalizeDate($this->iStart);
2951 $en = $aScale->NormalizeDate($this->iEnd);
2954 $limst = max($st,$aScale->iStartDate);
2955 $limen = min($en,$aScale->iEndDate);
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);
2965 if( ! empty($this->title->csimtarget) ) {
2966 $colwidth = $this->title->GetColWidth($aImg);
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\"";
2979 $this->csimarea .= ">\n";
2983 // Check if the bar is totally outside the current scale range
2984 if( $en < $aScale->iStartDate || $st > $aScale->iEndDate )
2988 // Remember the positions for the bar
2989 $this->SetConstrainPos($xt,$yt,$xb,$yb);
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);
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);
3007 if( $this->csimtarget != '' ) {
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\"";
3016 $this->csimarea .= ">\n";
3019 // Draw progress bar inside activity bar
3020 if( $this->progress->iProgress > 0 ) {
3022 $xtp = $aScale->TranslateDate($st);
3023 $xbp = $aScale->TranslateDate($en);
3024 $len = ($xbp-$xtp)*$this->progress->iProgress;
3026 $endpos = $xtp+$len;
3027 if( $endpos > $xt ) {
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;
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);
3051 // We don't plot the end mark if the bar has been capped
3052 if( $limst == $st ) {
3054 // We treat the RIGHT and LEFT triangle mark a little bi
3055 // special so that these marks are placed right under the
3057 if( $this->leftMark->GetType() == MARK_LEFTTRIANGLE ) {
3060 $this->leftMark->Stroke($aImg,$xt,$y);
3062 if( $limen == $en ) {
3064 // We treat the RIGHT and LEFT triangle mark a little bi
3065 // special so that these marks are placed right under the
3067 if( $this->rightMark->GetType() == MARK_RIGHTTRIANGLE ) {
3070 $this->rightMark->Stroke($aImg,$xb,$y);
3072 $margin = $this->iCaptionMargin;
3073 if( $this->rightMark->show )
3074 $margin += $this->rightMark->GetWidth();
3075 $this->caption->Stroke($aImg,$xb+$margin,$middle);
3080 //===================================================
3082 // Responsible for formatting individual milestones
3083 //===================================================
3084 class MileStone extends GanttPlotObject {
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;
3108 function GetAbsHeight($aImg) {
3109 return max($this->title->GetHeight($aImg),$this->mark->GetWidth());
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);
3118 $this->StrokeActInfo($aImg,$aScale,$y);
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\"";
3132 $this->csimarea .= ">\n";
3136 if( $d < $aScale->iStartDate || $d > $aScale->iEndDate )
3139 // Remember the coordinates for any constrains linking to
3141 $w = $this->mark->GetWidth()/2;
3142 $this->SetConstrainPos($x,round($y-$w),$x,round($y+$w));
3145 if( $this->csimtarget != '' ) {
3146 $this->mark->SetCSIMTarget( $this->csimtarget );
3147 $this->mark->SetCSIMAlt( $this->csimalt );
3150 $this->mark->Stroke($aImg,$x,$y);
3151 $this->caption->Stroke($aImg,$x+$this->mark->width/2+$this->iCaptionMargin,$y);
3153 $this->csimarea .= $this->mark->GetCSIMAreas();
3158 //===================================================
3160 // Responsible for formatting individual milestones
3161 //===================================================
3163 class TextPropertyBelow extends TextProperty {
3164 function TextPropertyBelow($aTxt='') {
3165 parent::TextProperty($aTxt);
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.
3175 class GanttVLine extends GanttPlotObject {
3177 var $iLine,$title_margin=3;
3178 var $iDayOffset=1; // Defult to right edge of day
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);
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;
3202 function SetTitleMargin($aMarg) {
3203 $this->title_margin = $aMarg;
3206 function Stroke($aImg,$aScale) {
3207 $d = $aScale->NormalizeDate($this->iStart);
3208 if( $d < $aScale->iStartDate || $d > $aScale->iEndDate )
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);
3219 //===================================================
3221 // Handles the drawing of a an arrow
3222 //===================================================
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';
3230 function LinkArrow($x,$y,$aDirection,$aType=ARROWT_SOLID,$aSize=ARROW_S2) {
3231 $this->iDirection = $aDirection;
3232 $this->iType = $aType;
3233 $this->iSize = $aSize;
3238 function SetColor($aColor) {
3239 $this->iColor = $aColor;
3242 function SetSize($aSize) {
3243 $this->iSize = $aSize;
3246 function SetType($aType) {
3247 $this->iType = $aType;
3250 function Stroke($aImg) {
3251 list($dx,$dy) = $this->isizespec[$this->iSize];
3254 switch ( $this->iDirection ) {
3256 $c = array($x,$y,$x-$dx,$y-$dy,$x+$dx,$y-$dy,$x,$y);
3259 $c = array($x,$y,$x-$dx,$y+$dy,$x+$dx,$y+$dy,$x,$y);
3262 $c = array($x,$y,$x+$dy,$y-$dx,$x+$dy,$y+$dx,$x,$y);
3265 $c = array($x,$y,$x-$dy,$y-$dx,$x-$dy,$y+$dx,$x,$y);
3268 JpGraphError::Raise('Unknown arrow direction for link.');
3272 $aImg->SetColor($this->iColor);
3273 switch( $this->iType ) {
3275 $aImg->FilledPolygon($c);
3281 JpGraphError::Raise('Unknown arrow type for link.');
3288 //===================================================
3290 // Handles the drawing of a link line between 2 points
3291 //===================================================
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;
3300 function GanttLink($x1=0,$y1=0,$x2=0,$y2=0) {
3307 function SetPos($x1,$y1,$x2,$y2) {
3314 function SetPath($aPath) {
3315 $this->iPathType = $aPath;
3318 function SetColor($aColor) {
3319 $this->iColor = $aColor;
3322 function SetArrow($aSize,$aType=ARROWT_SOLID) {
3323 $this->iArrowSize = $aSize;
3324 $this->iArrowType = $aType;
3327 function SetWeight($aWeight) {
3328 $this->iWeight = $aWeight;
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
3345 // The arrow is drawn between (x1,y1) to (x2,y2)
3351 // Depending on if the target is below or above we have to
3352 // handle thi different.
3354 $arrowtype = ARROW_DOWN;
3355 $midy = round(($y2-$y1)/2+$y1);
3357 switch ( $this->iPathType ) {
3359 $c = array($x1,$y1,$x1,$midy,$x2,$midy,$x2,$y2);
3364 $c = array($x1,$y1,$x2,$y1,$x2,$y2);
3367 JpGraphError::Raise('Internal error: Unknown path type (='.$this->iPathType .') specified for link.');
3373 switch ( $this->iPathType ) {
3376 $c = array($x1,$y1,$x1,$midy,$x2,$midy,$x2,$y2);
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,
3388 if( $y2-$midy < 6 ) {
3389 $c = array($x1,$y1,$x1,$midy,
3390 $x2-$this->iPathExtend,$midy,
3391 $x2-$this->iPathExtend,$y2,
3393 $arrowtype = ARROW_RIGHT;
3396 $c = array($x1,$y1,$x1,$midy,$x2,$midy,$x2,$y2);
3400 JpGraphError::Raise('Internal error: Unknown path type specified for link.');
3405 $arrow = new LinkArrow($x2,$y2,$arrowtype);
3409 $arrowtype = ARROW_UP;
3410 $midy = round(($y1-$y2)/2+$y2);
3412 switch ( $this->iPathType ) {
3415 $c = array($x1,$y1,$x1,$midy,$x2,$midy,$x2,$y2);
3418 if( $midy-$y2 < 8 ) {
3419 $arrowtype = ARROW_RIGHT;
3420 $c = array($x1,$y1,$x1,$y2,$x2,$y2);
3423 $c = array($x1,$y1,$x1,$midy,$x2,$midy,$x2,$y2);
3427 JpGraphError::Raise('Internal error: Unknown path type specified for link.');
3432 switch ( $this->iPathType ) {
3435 $c = array($x1,$y1,$x1,$midy,$x2,$midy,$x2,$y2);
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,
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,
3451 $c = array($x1,$y1,$x1,$midy,$x2,$midy,$x2,$y2);
3455 JpGraphError::Raise('Internal error: Unknown path type specified for link.');
3460 $arrow = new LinkArrow($x2,$y2,$arrowtype);
3462 $aImg->SetColor($this->iColor);
3463 $aImg->SetLineWeight($this->iWeight);
3465 $aImg->SetLineWeight(1);
3466 $arrow->SetColor($this->iColor);
3467 $arrow->SetSize($this->iArrowSize);
3468 $arrow->SetType($this->iArrowType);
3469 $arrow->Stroke($aImg);