home *** CD-ROM | disk | FTP | other *** search
/ Chip 2004 April / CMCD0404.ISO / Software / Freeware / Programare / dotproject / lib / jpgraph / src / jpgraph_gantt.php < prev    next >
PHP Script  |  2002-12-13  |  58KB  |  1,856 lines

  1. <?php
  2. /*=======================================================================
  3. // File:    JPGRAPH_GANTT.PHP
  4. // Description:    JpGraph Gantt plot extension
  5. // Created:     2001-11-12
  6. // Author:    Johan Persson (johanp@aditus.nu)
  7. // Ver:        $Id: jpgraph_gantt.php,v 1.2 2002/12/12 22:47:14 kripper Exp $
  8. //
  9. // License:    This code is released under QPL 
  10. // Copyright (c) 2002 Johan Persson
  11. //========================================================================
  12. */
  13.  
  14. // Scale Header types
  15. DEFINE("GANTT_HDAY",1);
  16. DEFINE("GANTT_HWEEK",2);
  17. DEFINE("GANTT_HMONTH",4);
  18. DEFINE("GANTT_HYEAR",8);
  19.  
  20. // Bar patterns
  21. DEFINE("GANTT_RDIAG",BAND_RDIAG);    // Right diagonal lines
  22. DEFINE("GANTT_LDIAG",BAND_LDIAG); // Left diagonal lines
  23. DEFINE("GANTT_SOLID",BAND_SOLID); // Solid one color
  24. DEFINE("GANTT_VLINE",BAND_VLINE); // Vertical lines
  25. DEFINE("GANTT_HLINE",BAND_HLINE);  // Horizontal lines
  26. DEFINE("GANTT_3DPLANE",BAND_3DPLANE);  // "3D" Plane
  27. DEFINE("GANTT_HVCROSS",BAND_HVCROSS);  // Vertical/Hor crosses
  28. DEFINE("GANTT_DIAGCROSS",BAND_DIAGCROSS); // Diagonal crosses
  29.  
  30. // Conversion constant
  31. DEFINE("SECPERDAY",3600*24);
  32.  
  33. // Locales. ONLY KEPT FOR BACKWARDS COMPATIBILITY
  34. // You should use the proper locale strings directly 
  35. // from now on. 
  36. DEFINE("LOCALE_EN","en_UK");
  37. DEFINE("LOCALE_SV","sv_SE");
  38.  
  39. // Layout of bars
  40. DEFINE("GANTT_EVEN",1);
  41. DEFINE("GANTT_FROMTOP",2);
  42.  
  43. // Styles for week header
  44. DEFINE("WEEKSTYLE_WNBR",0);
  45. DEFINE("WEEKSTYLE_FIRSTDAY",1);
  46. DEFINE("WEEKSTYLE_FIRSTDAY2",1);
  47.  
  48. // Styles for month header
  49. DEFINE("MONTHSTYLE_SHORTNAME",0);
  50. DEFINE("MONTHSTYLE_LONGNAME",1);
  51. DEFINE("MONTHSTYLE_LONGNAMEYEAR2",2);
  52. DEFINE("MONTHSTYLE_SHORTNAMEYEAR2",3);
  53. DEFINE("MONTHSTYLE_LONGNAMEYEAR4",4);
  54. DEFINE("MONTHSTYLE_SHORTNAMEYEAR4",5);
  55.  
  56. // Types of constrain links
  57. DEFINE('CONSTRAIN_STARTSTART',0);
  58. DEFINE('CONSTRAIN_STARTEND',1);
  59. DEFINE('CONSTRAIN_ENDSTART',2);
  60. DEFINE('CONSTRAIN_ENDEND',3);
  61.  
  62. // Types of constrain paths styles
  63. DEFINE('PATH_RECTANGULAR',0);
  64. DEFINE('PATH_BLEND_ON_MIDDLE',1);
  65. DEFINE('PATH_DIAGONAL',2);
  66.  
  67. // Arrow direction for constrain links
  68. DEFINE('ARROW_DOWN',0);
  69. DEFINE('ARROW_UP',1);
  70. DEFINE('ARROW_LEFT',2);
  71. DEFINE('ARROW_RIGHT',3);
  72.  
  73. // Arrow type for constrain type
  74. DEFINE('ARROWT_SOLID',0);
  75. DEFINE('ARROWT_OPEN',1);
  76.  
  77. // Arrow size for constrain lines
  78. DEFINE('ARROW_S1',0);
  79. DEFINE('ARROW_S2',1);
  80. DEFINE('ARROW_S3',2);
  81. DEFINE('ARROW_S4',3);
  82. DEFINE('ARROW_S5',4);
  83.  
  84.  
  85. //===================================================
  86. // CLASS GanttGraph
  87. // Description: Main class to handle gantt graphs
  88. //===================================================
  89. class GanttGraph extends Graph {
  90.     var $scale;                            // Public accessible
  91.     var $iObj=array();                // Gantt objects
  92.     var $iLabelHMarginFactor=0.2;    // 10% margin on each side of the labels
  93.     var $iLabelVMarginFactor=0.4;    // 40% margin on top and bottom of label
  94.     var $iLayout=GANTT_FROMTOP;    // Could also be GANTT_EVEN
  95.  
  96. //---------------
  97. // CONSTRUCTOR    
  98.     // Create a new gantt graph
  99.     function GanttGraph($aWidth=0,$aHeight=0,$aCachedName="",$aTimeOut=0,$aInline=true) {
  100.     Graph::Graph($aWidth,$aHeight,$aCachedName,$aTimeOut,$aInline);        
  101.     $this->scale = new GanttScale($this->img);
  102.     if( $aWidth > 0 )
  103.         $this->img->SetMargin($aWidth/17,$aWidth/17,$aHeight/7,$aHeight/10);
  104.         
  105.     $this->scale->ShowHeaders(GANTT_HWEEK|GANTT_HDAY);
  106.     $this->SetBox();
  107.     }
  108.     
  109. //---------------
  110. // PUBLIC METHODS    
  111.     // Set what headers should be shown
  112.     function ShowHeaders($aFlg) {
  113.     $this->scale->ShowHeaders($aFlg);
  114.     }
  115.     
  116.     // Specify the fraction of the font height that should be added 
  117.     // as vertical margin
  118.     function SetLabelVMarginFactor($aVal) {
  119.     $this->iLabelVMarginFactor = $aVal;
  120.     }
  121.     
  122.     // Add a new Gantt object
  123.     function Add(&$aObject) {
  124.     if( is_array($aObject) ) {
  125.         for($i=0; $i<count($aObject); ++$i)
  126.         $this->iObj[] = $aObject[$i];
  127.     }
  128.     else
  129.         $this->iObj[] = $aObject;
  130.     }
  131.  
  132.     // Override inherit method from Graph and give a warning message
  133.     function SetScale() {
  134.     JpGraphError::Raise("SetScale() is not meaningfull with Gantt charts.");
  135.     // Empty
  136.     }
  137.  
  138.     // Specify the date range for Gantt graphs (if this is not set it will be
  139.     // automtically determined from the input data)
  140.     function SetDateRange($aStart,$aEnd) {
  141.     $this->scale->SetRange($aStart,$aEnd);
  142.     }
  143.     
  144.     // Get the maximum width of the titles for the bars    
  145.     function GetMaxLabelWidth() {
  146.     $m=0;
  147.     if( $this->iObj != null ) {
  148.         $m = $this->iObj[0]->title->GetWidth($this->img);
  149.         for($i=1; $i<count($this->iObj); ++$i) {
  150.         if( $this->iObj[$i]->title->HasTabs() ) {
  151.             list($tot,$w) = $this->iObj[$i]->title->GetWidth($this->img,true);
  152.             $m=max($m,$tot);
  153.         }
  154.         else 
  155.             $m=max($m,$this->iObj[$i]->title->GetWidth($this->img));
  156.         }
  157.     }
  158.     return $m;
  159.     }
  160.     
  161.     // Get the maximum height of the titles for the bars
  162.     function GetMaxLabelHeight() {
  163.     $m=0;
  164.     if( $this->iObj != null ) {
  165.         $m = $this->iObj[0]->title->GetHeight($this->img);
  166.         for($i=1; $i<count($this->iObj); ++$i) {
  167.         $m=max($m,$this->iObj[$i]->title->GetHeight($this->img));
  168.         }
  169.     }
  170.     return $m;
  171.     }
  172.  
  173.     function GetMaxBarAbsHeight() {
  174.     $m=0;
  175.     if( $this->iObj != null ) {
  176.         $m = $this->iObj[0]->GetAbsHeight($this->img);
  177.         for($i=1; $i<count($this->iObj); ++$i) {
  178.         $m=max($m,$this->iObj[$i]->GetAbsHeight($this->img));
  179.         }
  180.     }
  181.     return $m;        
  182.     }
  183.     
  184.     // Get the maximum used line number (vertical position) for bars
  185.     function GetBarMaxLineNumber() {
  186.     $m=0;
  187.     if( $this->iObj != null ) {
  188.         $m = $this->iObj[0]->GetLineNbr();
  189.         for($i=1; $i<count($this->iObj); ++$i) {
  190.         $m=max($m,$this->iObj[$i]->GetLineNbr());
  191.         }
  192.     }
  193.     return $m;
  194.     }
  195.     
  196.     // Get the minumum and maximum used dates for all bars
  197.     function GetBarMinMax() {
  198.     $max=$this->scale->NormalizeDate($this->iObj[0]->GetMaxDate());
  199.     $min=$this->scale->NormalizeDate($this->iObj[0]->GetMinDate());
  200.     for($i=1; $i<count($this->iObj); ++$i) {
  201.         $max=Max($max,$this->scale->NormalizeDate($this->iObj[$i]->GetMaxDate()));
  202.         $min=Min($min,$this->scale->NormalizeDate($this->iObj[$i]->GetMinDate()));
  203.     }
  204.     $minDate = date("Y-m-d",$min);
  205.     $min = strtotime($minDate);
  206.     $maxDate = date("Y-m-d",$max);
  207.     $max = strtotime($maxDate);    
  208.     return array($min,$max);
  209.     }
  210.  
  211.     // Stroke the gantt chart
  212.     function Stroke($aStrokeFileName="") {    
  213.  
  214.     // Should we autoscale dates?
  215.     if( !$this->scale->IsRangeSet() ) {
  216.         list($min,$max) = $this->GetBarMinMax();
  217.         $this->scale->SetRange($min,$max);
  218.     }
  219.  
  220.     $this->scale->AdjustStartEndDay();
  221.             
  222.     if( $this->img->img == null ) {
  223.         // The predefined left, right, top, bottom margins.
  224.         // Note that the top margin might incease depending on
  225.         // the title.
  226.         $lm=30;$rm=30;$tm=20;$bm=30;            
  227.         if( BRAND_TIMING ) $bm += 10;
  228.             
  229.         // First find out the height            
  230.         $n=$this->GetBarMaxLineNumber()+1;
  231.         $m=max($this->GetMaxLabelHeight(),$this->GetMaxBarAbsHeight());
  232.         $height=$n*((1+$this->iLabelVMarginFactor)*$m);            
  233.             
  234.         // Add the height of the scale titles            
  235.         $h=$this->scale->GetHeaderHeight();
  236.         $height += $h;
  237.  
  238.         // Calculate the top margin needed for title and subtitle
  239.         if( $this->title->t != "" ) {
  240.         $tm += $this->title->GetFontHeight($this->img);
  241.         }
  242.         if( $this->subtitle->t != "" ) {
  243.         $tm += $this->subtitle->GetFontHeight($this->img);
  244.         }
  245.  
  246.         // ...and then take the bottom and top plot margins into account
  247.         $height += $tm + $bm + $this->scale->iTopPlotMargin + $this->scale->iBottomPlotMargin;
  248.             
  249.         // Now find the minimum width for the chart required
  250.         $fw=$this->scale->day->GetFontWidth($this->img)+4;
  251.         $nd=$this->scale->GetNumberOfDays();
  252.         if( !$this->scale->IsDisplayDay() ) {
  253.                 // If we don't display the individual days we can shrink the
  254.                 // scale a little bit. This is a little bit pragmatic at the 
  255.                 // moment and should be re-written to take into account
  256.                 // a) What scales exactly are shown and 
  257.                 // b) what format do they use so we know how wide we need to
  258.                 // make each scale text space at minimum.
  259.         $fw /= 2;
  260.         if( !$this->scale->IsDisplayWeek() ) {
  261.             $fw /= 1.8;
  262.         }
  263.         }
  264.  
  265.         // Has the user specified a width or do we need to
  266.         // determine it?
  267.         if( $this->img->width <= 0 ) {
  268.         // Now determine the width for the activity titles column
  269.         // This is complicated by the fact that the titles may have
  270.         // tabs. In that case we also need to calculate the individual
  271.         // tab positions based on the width of the individual columns
  272.         
  273.         $titlewidth = $this->GetMaxLabelWidth();
  274.         
  275.         // Now get the total width taking 
  276.         // titlewidth, left and rigt margin, dayfont size 
  277.         // into account
  278.         $width = $titlewidth + $nd*$fw + $lm+$rm;
  279.         }
  280.         else
  281.         $width = $this->img->width;
  282.                         
  283.         $this->img->CreateImgCanvas($width,$height);            
  284.         $this->img->SetMargin($lm,$rm,$tm,$bm);
  285.     }
  286.         
  287.     // Should we start from the top or just spread the bars out even over the
  288.     // available height
  289.     $this->scale->SetVertLayout($this->iLayout);            
  290.     if( $this->iLayout == GANTT_FROMTOP ) {
  291.         $maxheight=max($this->GetMaxLabelHeight(),$this->GetMaxBarAbsHeight());
  292.         $this->scale->SetVertSpacing($maxheight*(1+$this->iLabelVMarginFactor));
  293.     }
  294.     // If it hasn't been set find out the maximum line number
  295.     if( $this->scale->iVertLines == -1 ) 
  296.         $this->scale->iVertLines = $this->GetBarMaxLineNumber()+1;     
  297.         
  298.     $maxwidth=max($this->GetMaxLabelWidth(),$this->scale->tableTitle->GetWidth($this->img));
  299.     $this->scale->SetLabelWidth($maxwidth*(1+$this->iLabelHMarginFactor));
  300.     $this->StrokePlotArea();
  301.     $this->scale->Stroke();
  302.     $this->StrokePlotBox();
  303.     
  304.     $n = count($this->iObj);
  305.     for($i=0; $i < $n; ++$i) {
  306.         $this->iObj[$i]->SetLabelLeftMargin(round($maxwidth*$this->iLabelHMarginFactor/2));
  307.         $this->iObj[$i]->Stroke($this->img,$this->scale);
  308.     }
  309.  
  310.     $this->StrokeConstrains();
  311.     $this->StrokeTitles();
  312.     $this->cache->PutAndStream($this->img,$this->cache_name,$this->inline,$aStrokeFileName);
  313.     }
  314.  
  315.     function StrokeConstrains() {
  316.     $n = count($this->iObj);
  317.  
  318.     // Stroke all constrains
  319.     for($i=0; $i < $n; ++$i) {
  320.         $carr = $this->iObj[$i]->iConstrainsArray;
  321.                 
  322.         foreach($carr as $constrain) {
  323.             $vpos = $constrain->iConstrainRow;
  324.  
  325.             if( $vpos > 0 ) {
  326.             $c1 = $this->iObj[$i]->iConstrainPos;
  327.  
  328.             // Find out which object is on the target row
  329.             $targetobj = -1;
  330.             for( $j=0; $j < $n && $targetobj == -1; ++$j ) {
  331.                 if( $this->iObj[$j]->iVPos == $vpos ) {
  332.                 $targetobj = $j;
  333.                 }
  334.             }
  335.             if( $targetobj == -1 ) {
  336.                 JpGraphError::Raise('You have specifed a constrain from row='.
  337.                     $this->iObj[$i]->iVPos.
  338.                     ' to row='.$vpos.' which does not have any activity.');
  339.                 exit();
  340.             }
  341.             $c2 = $this->iObj[$targetobj]->iConstrainPos;
  342.             if( count($c1) == 4 && count($c2 ) == 4) {
  343.                 switch( $constrain->iConstrainType ) {
  344.                 case CONSTRAIN_ENDSTART:
  345.                     $y = ($c1[1] + $c1[3]) / 2;
  346.                     if( $c1[1] < $c2[1] ) {
  347.                     $link = new GanttLink($c1[2], $y ,$c2[0],$c2[1]);
  348.                     }
  349.                     else {
  350.                     $link = new GanttLink($c1[2], $y, $c2[0],$c2[3]);
  351.                     }
  352.                     $link->SetPath(PATH_RECTANGULAR);
  353.                     break;
  354.                 case CONSTRAIN_STARTEND:
  355.                     if( $c1[1] < $c2[1] ) {
  356.                     $link = new GanttLink($c1[0],$c1[3],$c2[2],$c2[1]);
  357.                     }
  358.                     else {
  359.                     $link = new GanttLink($c1[0],$c1[1],$c2[2],$c2[3]);
  360.                     }
  361.                     $link->SetPath(0);
  362.                     break;
  363.                 case CONSTRAIN_ENDEND:
  364.                     if( $c1[1] < $c2[1] ) {
  365.                     $link = new GanttLink($c1[2],$c1[3],$c2[2],$c2[1]);
  366.                     }
  367.                     else {
  368.                     $link = new GanttLink($c1[2],$c1[1],$c2[2],$c2[3]);
  369.                     }
  370.                     $link->SetPath(1);
  371.                     break;
  372.                 case CONSTRAIN_STARTSTART:
  373.                     if( $c1[1] < $c2[1] ) {
  374.                     $link = new GanttLink($c1[0],$c1[3],$c2[0],$c2[1]);
  375.                     }
  376.                     else {
  377.                     $link = new GanttLink($c1[0],$c1[1],$c2[0],$c2[3]);
  378.                     }
  379.                     $link->SetPath(0);
  380.                     break;
  381.                 default:
  382.                     JpGraphError::Raise('Unknown constrain type specified from row='.
  383.                     $this->iObj[$i]->iVPos.
  384.                     ' to row='.$vpos);
  385.                     break;
  386.                 }
  387.                 $link->SetColor($constrain->iConstrainColor);
  388.                 $link->SetArrow($constrain->iConstrainArrowSize,
  389.                     $constrain->iConstrainArrowType);
  390.                 $link->Stroke($this->img);
  391.             }
  392.             }
  393.         }
  394.     }
  395.     }
  396.  
  397.     function GetCSIMAreas() {
  398.     if( !$this->iHasStroked )
  399.         $this->Stroke(_CSIM_SPECIALFILE);
  400.     $csim='';
  401.     $n = count($this->iObj);
  402.     for( $i=0; $i < $n; ++$i ) 
  403.         $csim .= $this->iObj[$i]->GetCSIMArea();
  404.     return $csim;
  405.     }
  406. }
  407.  
  408. //===================================================
  409. // CLASS TextProperty
  410. // Description: Holds properties for a text
  411. //===================================================
  412. class TextProperty {
  413.     var $iFFamily=FF_FONT1,$iFStyle=FS_NORMAL,$iFSize=10;
  414.     var $iColor="black";
  415.     var $iShow=true;
  416.     var $iText="";
  417.     var $iHAlign="left",$iVAlign="bottom";
  418.     var $csimtarget='',$csimalt='';
  419.     
  420. //---------------
  421. // CONSTRUCTOR    
  422.     function TextProperty($aTxt="") {
  423.     $this->iText = $aTxt;
  424.     }        
  425.     
  426. //---------------
  427. // PUBLIC METHODS    
  428.     function Set($aTxt) {
  429.     $this->iText = $aTxt;
  430.     }
  431.  
  432.     function SetCSIMTarget($aTarget) {
  433.         $this->csimtarget=$aTarget;
  434.     }
  435.     
  436.     function SetCSIMAlt($aAlt) {
  437.         $this->csimalt=$aAlt;
  438.     }
  439.  
  440.     // Set text color
  441.     function SetColor($aColor) {
  442.     $this->iColor = $aColor;
  443.     }
  444.     
  445.     function HasTabs() {
  446.     return substr_count($this->iText,"\t") > 0;
  447.     }
  448.     
  449.     // Get number of tabs in string
  450.     function GetNbrTabs() {
  451.     substr_count($this->iText,"\t");
  452.     }
  453.     
  454.     // Set alignment
  455.     function Align($aHAlign,$aVAlign="bottom") {
  456.     $this->iHAlign=$aHAlign;
  457.     $this->iVAlign=$aVAlign;
  458.     }
  459.     
  460.     // Specify font
  461.     function SetFont($aFFamily,$aFStyle=FS_NORMAL,$aFSize=10) {
  462.     $this->iFFamily = $aFFamily;
  463.     $this->iFStyle     = $aFStyle;
  464.     $this->iFSize     = $aFSize;
  465.     }
  466.     
  467.     // Get width of text. If text contains several columns separated by
  468.     // tabs then return both the total width as well as an array with a 
  469.     // width for each column.
  470.     function GetWidth($aImg,$aUseTabs=false,$aTabExtraMargin=1.1) {
  471.     if( strlen($this->iText)== 0 ) return;
  472.     $tmp = split("\t",$this->iText);
  473.     $aImg->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
  474.     if( count($tmp) <= 1 || !$aUseTabs ) {
  475.         return $aImg->GetTextWidth($this->iText);
  476.     }
  477.     else {
  478.         $tot=0;
  479.         for($i=0; $i<count($tmp); ++$i) {
  480.         $res[$i] = $aImg->GetTextWidth($tmp[$i]);
  481.         $tot += $res[$i]*$aTabExtraMargin;
  482.         }
  483.         return array($tot,$res);
  484.     }
  485.     }
  486.     
  487.     // Get total height of text
  488.     function GetHeight($aImg) {
  489.     $aImg->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
  490.     return $aImg->GetFontHeight();
  491.     }
  492.     
  493.     // Unhide/hide the text    
  494.     function Show($aShow) {
  495.     $this->iShow=$aShow;
  496.     }
  497.     
  498.     // Stroke text at (x,y) coordinates. If the text contains tabs then the
  499.     // x parameter should be an array of positions to be used for each successive
  500.     // tab mark. If no array is supplied then the tabs will be ignored.
  501.     function Stroke($aImg,$aX,$aY) {
  502.     if( $this->iShow ) {
  503.         $aImg->SetColor($this->iColor);
  504.         $aImg->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
  505.         $aImg->SetTextAlign($this->iHAlign,$this->iVAlign);            
  506.         if( $this->GetNbrTabs() <= 1 || !is_array($aX) ) {
  507.                 // Get rid of any "\t" characters and stroke string
  508.         $aImg->StrokeText($aX,$aY,str_replace("\t"," ",$this->iText));
  509.         }
  510.         else {
  511.         $tmp = split("\t",$this->iText);
  512.         $n = min(count($tmp),count($aX));
  513.         for($i=0; $i<$n; ++$i) {
  514.             $aImg->StrokeText($aX[$i],$aY,$tmp[$i]);
  515.         }    
  516.         }
  517.     }
  518.     }
  519. }
  520.  
  521. //===================================================
  522. // CLASS HeaderProperty
  523. // Description: Data encapsulating class to hold property 
  524. // for each type of the scale headers
  525. //===================================================
  526. class HeaderProperty {
  527.     var $iTitleVertMargin=3,$iFFamily=FF_FONT0,$iFStyle=FS_NORMAL,$iFSize=8;
  528.     var $iFrameColor="black",$iFrameWeight=1;
  529.     var $iShowLabels=true,$iShowGrid=true;
  530.     var $iBackgroundColor="white";
  531.     var $iWeekendBackgroundColor="lightgray",$iSundayTextColor="red"; // these are only used with day scale
  532.     var $iTextColor="black";
  533.     var $iLabelFormStr="%d";
  534.     var $grid,$iStyle=0;
  535.  
  536. //---------------
  537. // CONSTRUCTOR    
  538.     function HeaderProperty() {
  539.     $this->grid = new LineProperty();
  540.     }
  541.  
  542. //---------------
  543. // PUBLIC METHODS        
  544.     function Show($aShow) {
  545.     $this->iShowLabels = $aShow;
  546.     }
  547.     
  548.     function SetFont($aFFamily,$aFStyle=FS_NORMAL,$aFSize=10) {
  549.     $this->iFFamily = $aFFamily;
  550.     $this->iFStyle     = $aFStyle;
  551.     $this->iFSize     = $aFSize;
  552.     }
  553.  
  554.     function SetFontColor($aColor) {
  555.     $this->iTextColor = $aColor;
  556.     }
  557.     
  558.     function GetFontHeight($aImg) {
  559.     $aImg->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
  560.     return $aImg->GetFontHeight();
  561.     }
  562.  
  563.     function GetFontWidth($aImg) {
  564.     $aImg->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
  565.     return $aImg->GetFontWidth();
  566.     }
  567.     
  568.     function SetStyle($aStyle) {
  569.     $this->iStyle = $aStyle;
  570.     }
  571.     
  572.     function SetBackgroundColor($aColor) {
  573.     $this->iBackgroundColor=$aColor;
  574.     }
  575.  
  576.     function SetFrameWeight($aWeight) {
  577.     $this->iFrameWeight=$aWeight;
  578.     }
  579.  
  580.     function SetFrameColor($aColor) {
  581.     $this->iFrameColor=$aColor;
  582.     }
  583.     
  584.     // Only used by day scale
  585.     function SetWeekendColor($aColor) {
  586.     $this->iWeekendBackgroundColor=$aColor;
  587.     }
  588.     
  589.     // Only used by day scale
  590.     function SetSundayFontColor($aColor) {
  591.     $this->iSundayTextColor=$aColor;
  592.     }
  593.     
  594.     function SetTitleVertMargin($aMargin) {
  595.     $this->iTitleVertMargin=$aMargin;
  596.     }
  597.     
  598.     function SetLabelFormatString($aStr) {
  599.     $this->iLabelFormStr=$aStr;
  600.     }
  601. }
  602.  
  603. //===================================================
  604. // CLASS GanttScale
  605. // Description: Responsible for calculating and showing
  606. // the scale in a gantt chart. This includes providing methods for
  607. // converting dates to position in the chart as well as stroking the
  608. // date headers (days, week, etc).
  609. //===================================================
  610. class GanttScale {
  611.     var $day,$week,$month,$year;
  612.     var $divider,$dividerh,$tableTitle;
  613.     var $iStartDate=-1,$iEndDate=-1;
  614.     // Number of gantt bar position (n.b not necessariliy the same as the number of bars)
  615.     // we could have on bar in position 1, and one bar in position 5 then there are two
  616.     // bars but the number of bar positions is 5
  617.     var $iVertLines=-1;    
  618.     // The width of the labels (defaults to the widest of all labels)
  619.     var $iLabelWidth;    
  620.     // Out image to stroke the scale to
  621.     var $iImg;    
  622.     var $iTableHeaderBackgroundColor="white",$iTableHeaderFrameColor="black";
  623.     var $iTableHeaderFrameWeight=1;
  624.     var $iAvailableHeight=-1,$iVertSpacing=-1,$iVertHeaderSize=-1;
  625.     var $iDateLocale;
  626.     var $iVertLayout=GANTT_EVEN;
  627.     var $iTopPlotMargin=10,$iBottomPlotMargin=15;
  628.     var $iUsePlotWeekendBackground=true;
  629.     var $iWeekStart = 1;    // Default to have weekends start on Monday
  630.     
  631. //---------------
  632. // CONSTRUCTOR    
  633.     function GanttScale(&$aImg) {
  634.     $this->iImg = &$aImg;        
  635.     $this->iDateLocale = new DateLocale();
  636.     $this->day = new HeaderProperty();
  637.     $this->day->grid->SetColor("gray");
  638.  
  639.     $this->week = new HeaderProperty();
  640.     $this->week->SetLabelFormatString("w%d");
  641.     $this->week->SetFont(FF_FONT1);
  642.  
  643.     $this->month = new HeaderProperty();
  644.     $this->month->SetFont(FF_FONT1,FS_BOLD);
  645.  
  646.     $this->year = new HeaderProperty();
  647.     $this->year->SetFont(FF_FONT1,FS_BOLD);        
  648.         
  649.     $this->divider=new LineProperty();
  650.     $this->dividerh=new LineProperty();        
  651.     $this->tableTitle=new TextProperty();
  652.     }
  653.     
  654. //---------------
  655. // PUBLIC METHODS    
  656.     // Specify what headers should be visible
  657.     function ShowHeaders($aFlg) {
  658.     $this->day->Show($aFlg & GANTT_HDAY);
  659.     $this->week->Show($aFlg & GANTT_HWEEK);
  660.     $this->month->Show($aFlg & GANTT_HMONTH);
  661.     $this->year->Show($aFlg & GANTT_HYEAR);
  662.  
  663.     // Make some default settings of gridlines whihc makes sense
  664.     if( $aFlg & GANTT_HWEEK ) {
  665.         $this->month->grid->Show(false);
  666.         $this->year->grid->Show(false);
  667.     }
  668.     }
  669.     
  670.     // Should the weekend background stretch all the way down in the plotarea
  671.     function UseWeekendBackground($aShow) {
  672.     $this->iUsePlotWeekendBackground = $aShow;
  673.     }
  674.     
  675.     // Have a range been specified?
  676.     function IsRangeSet() {
  677.     return $this->iStartDate!=-1 && $this->iEndDate!=-1;
  678.     }
  679.     
  680.     // Should the layout be from top or even?
  681.     function SetVertLayout($aLayout) {
  682.     $this->iVertLayout = $aLayout;
  683.     }
  684.     
  685.     // Which locale should be used?
  686.     function SetDateLocale($aLocale) {
  687.     $this->iDateLocale->Set($aLocale);
  688.     }
  689.     
  690.     // Number of days we are showing
  691.     function GetNumberOfDays() {
  692.     return round(($this->iEndDate-$this->iStartDate)/SECPERDAY)+1;
  693.     }
  694.     
  695.     // The width of the actual plot area
  696.     function GetPlotWidth() {
  697.     $img=$this->iImg;
  698.     return $img->width - $img->left_margin - $img->right_margin;
  699.     }
  700.  
  701.     // Specify the width of the titles(labels) for the activities
  702.     // (This is by default set to the minimum width enought for the
  703.     // widest title)
  704.     function SetLabelWidth($aLabelWidth) {
  705.     $this->iLabelWidth=$aLabelWidth;
  706.     }
  707.  
  708.     // Which day should the week start?
  709.     // 0==Sun, 1==Monday, 2==Tuesday etc
  710.     function SetWeekStart($aStartDay) {
  711.         $this->iWeekStart = $aStartDay % 7;
  712.         
  713.         //Recalculate the startday since this will change the week start
  714.         $this->SetRange($this->iStartDate,$this->iEndDate);
  715.     }
  716.     
  717.     // Do we show day scale?
  718.     function IsDisplayDay() {
  719.     return $this->day->iShowLabels;
  720.     }
  721.     
  722.     // Do we show week scale?
  723.     function IsDisplayWeek() {
  724.     return $this->week->iShowLabels;
  725.     }
  726.     
  727.     // Do we show month scale?
  728.     function IsDisplayMonth() {
  729.     return $this->month->iShowLabels;
  730.     }
  731.     
  732.     // Do we show year scale?
  733.     function IsDisplayYear() {
  734.     return $this->year->iShowLabels;
  735.     }
  736.  
  737.     // Specify spacing (in percent of bar height) between activity bars
  738.     function SetVertSpacing($aSpacing) {
  739.     $this->iVertSpacing = $aSpacing;
  740.     }
  741.  
  742.     // Specify scale min and max date either as timestamp or as date strings
  743.     // Always round to the nearest week boundary
  744.     function SetRange($aMin,$aMax) {
  745.     $this->iStartDate = $this->NormalizeDate($aMin);
  746.     $this->iEndDate = $this->NormalizeDate($aMax);    
  747.     }
  748.  
  749.  
  750.     // Adjust the start and end date so they fit to beginning/ending
  751.     // of the week taking the specified week start day into account.
  752.     function AdjustStartEndDay() {
  753.         // Get day in week for start and ending date (Sun==0)
  754.         $ds=strftime("%w",$this->iStartDate);
  755.         $de=strftime("%w",$this->iEndDate);    
  756.     
  757.         // We want to start on iWeekStart day. But first we subtract a week
  758.         // if the startdate is "behind" the day the week start at. 
  759.         // This way we ensure that the given start date is always included 
  760.         // in the range. If we don't do this the nearest correct weekday in the week 
  761.         // to start at might be later than the start date.
  762.         if( $ds < $this->iWeekStart )
  763.             $d = strtotime('-7 day',$this->iStartDate);
  764.         else
  765.             $d = $this->iStartDate;
  766.         $adjdate = strtotime(($this->iWeekStart-$ds).' day',$d /*$this->iStartDate*/ );
  767.         $this->iStartDate = $adjdate;
  768.     
  769.         // We want to end on the last day of the week
  770.         $preferredEndDay = ($this->iWeekStart+6)%7;
  771.         if( $preferredEndDay != $de ) { 
  772.             // Solve equivalence eq:    $de + x ~ $preferredDay (mod 7)
  773.             $adj = (7+($preferredEndDay - $de)) % 7;
  774.             $adjdate = strtotime("+$adj day",$this->iEndDate);
  775.             $this->iEndDate = $adjdate;    
  776.         }    
  777.     }
  778.  
  779.     // Specify background for the table title area (upper left corner of the table)    
  780.     function SetTableTitleBackground($aColor) {
  781.     $this->iTableHeaderBackgroundColor = $aColor;
  782.     }
  783.  
  784. ///////////////////////////////////////
  785. // PRIVATE Methods
  786.     
  787.     // Determine the height of all the scale headers combined
  788.     function GetHeaderHeight() {
  789.     $img=$this->iImg;
  790.     $height=1;
  791.     if( $this->day->iShowLabels ) {
  792.         $height += $this->day->GetFontHeight($img);
  793.         $height += $this->day->iTitleVertMargin;
  794.     }
  795.     if( $this->week->iShowLabels ) {
  796.         $height += $this->week->GetFontHeight($img);
  797.         $height += $this->week->iTitleVertMargin;
  798.     }
  799.     if( $this->month->iShowLabels ) {
  800.         $height += $this->month->GetFontHeight($img);
  801.         $height += $this->month->iTitleVertMargin;
  802.     }
  803.     if( $this->year->iShowLabels ) {
  804.         $height += $this->year->GetFontHeight($img);
  805.         $height += $this->year->iTitleVertMargin;
  806.     }
  807.     return $height;
  808.     }
  809.     
  810.     // Get width (in pisels) for a single day
  811.     function GetDayWidth() {
  812.     return ($this->GetPlotWidth()-$this->iLabelWidth+1)/$this->GetNumberOfDays();    
  813.     }
  814.  
  815.     // Nuber of days in a year
  816.     function GetNumDaysInYear($aYear) {
  817.     if( $this->IsLeap($aYear) )
  818.         return 366;
  819.     else
  820.         return 365;
  821.     }
  822.     
  823.     // Get week number 
  824.     function GetWeekNbr($aDate) {
  825.     // We can't use the internal strftime() since it gets the weeknumber
  826.     // wrong since it doesn't follow ISO.
  827.     // Even worse is that this works differently if we are on a Windows
  828.     // or UNIX box (it even differs between UNIX boxes how strftime()
  829.     // is natively implemented)
  830.     //
  831.     // Credit to Nicolas Hoizey <nhoizey@phpheaven.net> for this elegant
  832.     // version of Week Nbr calculation. 
  833.         
  834.     $day = $this->NormalizeDate($aDate);
  835.         
  836.     /*-------------------------------------------------------------------------
  837.       According to ISO-8601 :
  838.       "Week 01 of a year is per definition the first week that has the Thursday in this year,
  839.       which is equivalent to the week that contains the fourth day of January.
  840.       In other words, the first week of a new year is the week that has the majority of its
  841.       days in the new year."
  842.           
  843.       Be carefull, with PHP, -3 % 7 = -3, instead of 4 !!!
  844.           
  845.       day of year             = date("z", $day) + 1
  846.       offset to thursday      = 3 - (date("w", $day) + 6) % 7
  847.       first thursday of year  = 1 + (11 - date("w", mktime(0, 0, 0, 1, 1, date("Y", $day)))) % 7
  848.       week number             = (thursday's day of year - first thursday's day of year) / 7 + 1
  849.       ---------------------------------------------------------------------------*/
  850.          
  851.     $thursday = $day + 60 * 60 * 24 * (3 - (date("w", $day) + 6) % 7);              // take week's thursday
  852.     $week = 1 + (date("z", $thursday) - (11 - date("w", mktime(0, 0, 0, 1, 1, date("Y", $thursday)))) % 7) / 7;
  853.           
  854.     return $week;
  855.     }
  856.     
  857.     // Is year a leap year?
  858.     function IsLeap($aYear) {
  859.     // Is the year a leap year?
  860.     //$year = 0+date("Y",$aDate);
  861.     if( $aYear % 4 == 0)
  862.         if( !($aYear % 100 == 0) || ($aYear % 400 == 0) )
  863.         return true;
  864.     return false;
  865.     }
  866.  
  867.     // Get current year
  868.     function GetYear($aDate) {
  869.     return 0+Date("Y",$aDate);
  870.     }
  871.     
  872.     // Return number of days in a year
  873.     function GetNumDaysInMonth($aMonth,$aYear) {
  874.     $days=array(31,28,31,30,31,30,31,31,30,31,30,31);
  875.     $daysl=array(31,29,31,30,31,30,31,31,30,31,30,31);
  876.     if( $this->IsLeap($aYear))
  877.         return $daysl[$aMonth];
  878.     else
  879.         return $days[$aMonth];
  880.     }
  881.     
  882.     // Get day in month
  883.     function GetMonthDayNbr($aDate) {
  884.     return 0+strftime("%d",$aDate);
  885.     }
  886.  
  887.     // Get day in year
  888.     function GetYearDayNbr($aDate) {
  889.     return 0+strftime("%j",$aDate);
  890.     }
  891.     
  892.     // Get month number
  893.     function GetMonthNbr($aDate) {
  894.     return 0+strftime("%m",$aDate);
  895.     }
  896.     
  897.     // Translate a date to screen coordinates    (horizontal scale)
  898.     function TranslateDate($aDate) {
  899.     $aDate = $this->NormalizeDate($aDate);
  900.     $img=$this->iImg;        
  901.     return ($aDate-$this->iStartDate)/SECPERDAY*$this->GetDayWidth()+$img->left_margin+$this->iLabelWidth;;
  902.     }
  903.  
  904.     // Get screen coordinatesz for the vertical position for a bar        
  905.     function TranslateVertPos($aPos) {
  906.     $img=$this->iImg;
  907.     $ph=$this->iAvailableHeight;
  908.     if( $aPos > $this->iVertLines ) 
  909.         JpGraphError::Raise("Illegal vertical position $aPos");
  910.     if( $this->iVertLayout == GANTT_EVEN ) {
  911.         // Position the top bar at 1 vert spacing from the scale
  912.         return round($img->top_margin + $this->iVertHeaderSize +  ($aPos+1)*$this->iVertSpacing);
  913.     }
  914.     else {
  915.         // position the top bar at 1/2 a vert spacing from the scale
  916.         return round($img->top_margin + $this->iVertHeaderSize  + $this->iTopPlotMargin + ($aPos+1)*$this->iVertSpacing);        
  917.     }
  918.     }
  919.     
  920.     // What is the vertical spacing?
  921.     function GetVertSpacing() {
  922.     return $this->iVertSpacing;
  923.     }
  924.                     
  925.     // Convert a date to timestamp
  926.     function NormalizeDate($aDate) {
  927.     if( is_string($aDate) )
  928.         return strtotime($aDate);
  929.     elseif( is_int($aDate) || is_float($aDate) )
  930.         return $aDate;
  931.     else
  932.         JpGraphError::Raise("Unknown date format in GanttScale ($aDate).");
  933.     }
  934.  
  935.     // Stroke the day scale (including gridlines)            
  936.     function StrokeDays($aYCoord) {
  937.     $wdays=$this->iDateLocale->GetDayAbb();    
  938.     $img=$this->iImg;    
  939.     $daywidth=$this->GetDayWidth();
  940.     $xt=$img->left_margin+$this->iLabelWidth;
  941.     $yt=$aYCoord+$img->top_margin;        
  942.     if( $this->day->iShowLabels ) {
  943.         $img->SetFont($this->day->iFFamily,$this->day->iFStyle,$this->day->iFSize);
  944.         $xb=$img->width-$img->right_margin;
  945.         $yb=$yt + $img->GetFontHeight() + $this->day->iTitleVertMargin + $this->day->iFrameWeight;
  946.         $img->SetColor($this->day->iBackgroundColor);
  947.         $img->FilledRectangle($xt,$yt,$xb,$yb);
  948.  
  949.         $img->SetColor($this->day->grid->iColor);
  950.         $x = $xt;   
  951.         $img->SetTextAlign("center");
  952.         $day = $this->iWeekStart;
  953.         //echo "n=".$this->GetNumberOfDays()."<p>";
  954.         for($i=0; $i<$this->GetNumberOfDays(); ++$i, $x+=$daywidth, $day += 1,$day %= 7) {
  955.         if( $day==6 || $day==0 ) {
  956.             $img->PushColor($this->day->iWeekendBackgroundColor);
  957.             if( $this->iUsePlotWeekendBackground )
  958.             $img->FilledRectangle($x,$yt+$this->day->iFrameWeight,$x+$daywidth,$img->height-$img->bottom_margin);                        
  959.             else
  960.             $img->FilledRectangle($x,$yt+$this->day->iFrameWeight,$x+$daywidth,$yb-$this->day->iFrameWeight);
  961.             $img->PopColor();
  962.         }
  963.         if( $day==0 ) 
  964.             $img->PushColor($this->day->iSundayTextColor);
  965.         else
  966.             $img->PushColor($this->day->iTextColor);
  967.         $img->StrokeText(round($x+$daywidth/2+1),
  968.                  round($yb-$this->day->iTitleVertMargin),
  969.                  $wdays[$day]);
  970.         $img->PopColor();                        
  971.         $img->Line($x,$yt,$x,$yb);
  972.         $this->day->grid->Stroke($img,$x,$yb,$x,$img->height-$img->bottom_margin);
  973.         }            
  974.         $img->SetColor($this->day->iFrameColor);
  975.         $img->SetLineWeight($this->day->iFrameWeight);
  976.         $img->Rectangle($xt,$yt,$xb,$yb);
  977.         return $yb - $img->top_margin;
  978.     }
  979.     return $aYCoord;
  980.     }
  981.     
  982.     // Stroke week header and grid
  983.     function StrokeWeeks($aYCoord) {
  984.     $wdays=$this->iDateLocale->GetDayAbb();    
  985.     $img=$this->iImg;    
  986.     $weekwidth=$this->GetDayWidth()*7;
  987.     $xt=$img->left_margin+$this->iLabelWidth;
  988.     $yt=$aYCoord+$img->top_margin;        
  989.     $img->SetFont($this->week->iFFamily,$this->week->iFStyle,$this->week->iFSize);
  990.     $xb=$img->width-$img->right_margin;
  991.     $yb=$yt + $img->GetFontHeight() + $this->week->iTitleVertMargin + $this->week->iFrameWeight;
  992.         
  993.     $week = $this->iStartDate;
  994.     $weeknbr=$this->GetWeekNbr($week);
  995.     if( $this->week->iShowLabels ) {
  996.         $img->SetColor($this->week->iBackgroundColor);
  997.         $img->FilledRectangle($xt,$yt,$xb,$yb);
  998.         $img->SetColor($this->week->grid->iColor);
  999.         $x = $xt;
  1000.         if( $this->week->iStyle==WEEKSTYLE_WNBR ) {
  1001.         $img->SetTextAlign("center");
  1002.         $txtOffset = $weekwidth/2+1;
  1003.         }
  1004.         elseif( $this->week->iStyle==WEEKSTYLE_FIRSTDAY || $this->week->iStyle==WEEKSTYLE_FIRSTDAY2 ) {
  1005.         $img->SetTextAlign("left");
  1006.         $txtOffset = 2;
  1007.         }
  1008.         else
  1009.         JpGraphError::Raise("Unknown formatting style for week.");
  1010.                 
  1011.         for($i=0; $i<$this->GetNumberOfDays()/7; ++$i, $x+=$weekwidth) {
  1012.         $img->PushColor($this->week->iTextColor);
  1013.                 
  1014.         if( $this->week->iStyle==WEEKSTYLE_WNBR )
  1015.             $txt = sprintf($this->week->iLabelFormStr,$weeknbr);
  1016.         elseif( $this->week->iStyle==WEEKSTYLE_FIRSTDAY )
  1017.             $txt = date("j/n",$week);
  1018.         elseif( $this->week->iStyle==WEEKSTYLE_FIRSTDAY2 ) {
  1019.             $monthnbr = date("n",$week)-1;
  1020.             $shortmonth = $this->iDateLocale->GetShortMonthName($monthnbr);
  1021.             $txt = Date("j",$week)." ".$shortmonth;
  1022.         }
  1023.                 
  1024.         $img->StrokeText(round($x+$txtOffset),round($yb-$this->week->iTitleVertMargin),$txt);
  1025.                 
  1026.         $week = strtotime('+7 day',$week); 
  1027.         $weeknbr = $this->GetWeekNbr($week);
  1028.         $img->PopColor();                        
  1029.         $img->Line($x,$yt,$x,$yb);
  1030.         $this->week->grid->Stroke($img,$x,$yb,$x,$img->height-$img->bottom_margin);
  1031.         }            
  1032.         $img->SetColor($this->week->iFrameColor);
  1033.         $img->SetLineWeight($this->week->iFrameWeight);
  1034.         $img->Rectangle($xt,$yt,$xb,$yb);
  1035.         return $yb-$img->top_margin;
  1036.     }
  1037.     return $aYCoord;
  1038.     }    
  1039.     
  1040.     // Format the mont scale header string
  1041.     function GetMonthLabel($aMonthNbr,$year) {
  1042.     $sn = $this->iDateLocale->GetShortMonthName($aMonthNbr);
  1043.     $ln = $this->iDateLocale->GetLongMonthName($aMonthNbr);
  1044.     switch($this->month->iStyle) {
  1045.         case MONTHSTYLE_SHORTNAME:
  1046.         $m=$sn;
  1047.         break;
  1048.         case MONTHSTYLE_LONGNAME:
  1049.         $m=$ln;
  1050.         break;
  1051.         case MONTHSTYLE_SHORTNAMEYEAR2:
  1052.         $m=$sn." '".substr("".$year,2);
  1053.         break;
  1054.         case MONTHSTYLE_SHORTNAMEYEAR4:
  1055.         $m=$sn." ".$year;
  1056.         break;
  1057.         case MONTHSTYLE_LONGNAMEYEAR2:
  1058.         $m=$ln." '".substr("".$year,2);
  1059.         break;
  1060.         case MONTHSTYLE_LONGNAMEYEAR4:
  1061.         $m=$ln." ".$year;
  1062.         break;
  1063.     }
  1064.     return $m;
  1065.     }
  1066.     
  1067.     // Stroke month scale and gridlines
  1068.     function StrokeMonths($aYCoord) {
  1069.     if( $this->month->iShowLabels ) {
  1070.         $monthnbr = $this->GetMonthNbr($this->iStartDate)-1; 
  1071.         $img=$this->iImg;    
  1072.         
  1073.         $xt=$img->left_margin+$this->iLabelWidth;
  1074.         $yt=$aYCoord+$img->top_margin;        
  1075.         $img->SetFont($this->month->iFFamily,$this->month->iFStyle,$this->month->iFSize);
  1076.         $xb=$img->width-$img->right_margin;
  1077.         $yb=$yt + $img->GetFontHeight() + $this->month->iTitleVertMargin + $this->month->iFrameWeight;
  1078.             
  1079.         $img->SetColor($this->month->iBackgroundColor);
  1080.         $img->FilledRectangle($xt,$yt,$xb,$yb);
  1081.  
  1082.         $img->SetLineWeight($this->month->grid->iWeight);
  1083.         $img->SetColor($this->month->iTextColor);
  1084.         $year = 0+strftime("%Y",$this->iStartDate);
  1085.         $img->SetTextAlign("center");
  1086.         if( $this->GetMonthNbr($this->iStartDate) == $this->GetMonthNbr($this->iEndDate)  
  1087.         && $this->GetYear($this->iStartDate)==$this->GetYear($this->iEndDate) ) {
  1088.             $monthwidth=$this->GetDayWidth()*($this->GetMonthDayNbr($this->iEndDate) - $this->GetMonthDayNbr($this->iStartDate) + 1);
  1089.         } 
  1090.         else {
  1091.             $monthwidth=$this->GetDayWidth()*($this->GetNumDaysInMonth($monthnbr,$year)-$this->GetMonthDayNbr($this->iStartDate)+1);
  1092.         }
  1093.         // Is it enough space to stroke the first month?
  1094.         $monthName = $this->GetMonthLabel($monthnbr,$year);
  1095.         if( $monthwidth >= 1.2*$img->GetTextWidth($monthName) ) {
  1096.         $img->SetColor($this->month->iTextColor);                
  1097.         $img->StrokeText(round($xt+$monthwidth/2+1),
  1098.                  round($yb-$this->month->iTitleVertMargin),
  1099.                  $monthName);
  1100.         }
  1101.         $x = $xt + $monthwidth;
  1102.         while( $x < $xb ) {
  1103.         $img->SetColor($this->month->grid->iColor);                
  1104.         $img->Line($x,$yt,$x,$yb);
  1105.         $this->month->grid->Stroke($img,$x,$yb,$x,$img->height-$img->bottom_margin);
  1106.         $monthnbr++;
  1107.         if( $monthnbr==12 ) {
  1108.             $monthnbr=0;
  1109.             $year++;
  1110.         }
  1111.         $monthName = $this->GetMonthLabel($monthnbr,$year);
  1112.         $monthwidth=$this->GetDayWidth()*$this->GetNumDaysInMonth($monthnbr,$year);                
  1113.         if( $x + $monthwidth < $xb )
  1114.             $w = $monthwidth;
  1115.         else
  1116.             $w = $xb-$x;
  1117.         if( $w >= 1.2*$img->GetTextWidth($monthName) ) {
  1118.             $img->SetColor($this->month->iTextColor);                
  1119.             $img->StrokeText(round($x+$w/2+1),
  1120.                      round($yb-$this->month->iTitleVertMargin),$monthName);
  1121.         }
  1122.         $x += $monthwidth;
  1123.         }    
  1124.         $img->SetColor($this->month->iFrameColor);
  1125.         $img->SetLineWeight($this->month->iFrameWeight);
  1126.         $img->Rectangle($xt,$yt,$xb,$yb);            
  1127.         return $yb-$img->top_margin;
  1128.     }
  1129.     return $aYCoord;
  1130.     }
  1131.  
  1132.     // Stroke year scale and gridlines
  1133.     function StrokeYears($aYCoord) {
  1134.     if( $this->year->iShowLabels ) {
  1135.         $year = $this->GetYear($this->iStartDate); 
  1136.         $img=$this->iImg;    
  1137.         
  1138.         $xt=$img->left_margin+$this->iLabelWidth;
  1139.         $yt=$aYCoord+$img->top_margin;        
  1140.         $img->SetFont($this->year->iFFamily,$this->year->iFStyle,$this->year->iFSize);
  1141.         $xb=$img->width-$img->right_margin;
  1142.         $yb=$yt + $img->GetFontHeight() + $this->year->iTitleVertMargin + $this->year->iFrameWeight;
  1143.             
  1144.         $img->SetColor($this->year->iBackgroundColor);
  1145.         $img->FilledRectangle($xt,$yt,$xb,$yb);
  1146.         $img->SetLineWeight($this->year->grid->iWeight);
  1147.         $img->SetTextAlign("center");
  1148.         if( $year == $this->GetYear($this->iEndDate) )
  1149.         $yearwidth=$this->GetDayWidth()*($this->GetYearDayNbr($this->iEndDate)-$this->GetYearDayNbr($this->iStartDate)+1);
  1150.         else
  1151.         $yearwidth=$this->GetDayWidth()*($this->GetNumDaysInYear($year)-$this->GetYearDayNbr($this->iStartDate)+1);
  1152.             
  1153.         // The space for a year must be at least 20% bigger than the actual text 
  1154.         // so we allow 10% margin on each side
  1155.         if( $yearwidth >= 1.20*$img->GetTextWidth("".$year) ) {
  1156.         $img->SetColor($this->year->iTextColor);                
  1157.         $img->StrokeText(round($xt+$yearwidth/2+1),
  1158.                  round($yb-$this->year->iTitleVertMargin),
  1159.                  $year);
  1160.         }
  1161.         $x = $xt + $yearwidth;
  1162.         while( $x < $xb ) {
  1163.         $img->SetColor($this->year->grid->iColor);                
  1164.         $img->Line($x,$yt,$x,$yb);
  1165.         $this->year->grid->Stroke($img,$x,$yb,$x,$img->height-$img->bottom_margin);
  1166.         $year += 1;
  1167.         $yearwidth=$this->GetDayWidth()*$this->GetNumDaysInYear($year);                
  1168.         if( $x + $yearwidth < $xb )
  1169.             $w = $yearwidth;
  1170.         else
  1171.             $w = $xb-$x;
  1172.         if( $w >= 1.2*$img->GetTextWidth("".$year) ) {
  1173.             $img->SetColor($this->year->iTextColor);
  1174.             $img->StrokeText(round($x+$w/2+1),
  1175.                      round($yb-$this->year->iTitleVertMargin),
  1176.                      $year);
  1177.         }
  1178.         $x += $yearwidth;
  1179.         }
  1180.         $img->SetColor($this->year->iFrameColor);
  1181.         $img->SetLineWeight($this->year->iFrameWeight);
  1182.         $img->Rectangle($xt,$yt,$xb,$yb);            
  1183.         return $yb-$img->top_margin;
  1184.     }
  1185.     return $aYCoord;
  1186.     }
  1187.     
  1188.     // Stroke table title (upper left corner)
  1189.     function StrokeTableHeaders($aYBottom) {
  1190.     $img=$this->iImg;
  1191.     $xt=$img->left_margin;
  1192.     $yt=$img->top_margin;
  1193.     $xb=$xt+$this->iLabelWidth;
  1194.     $yb=$aYBottom+$img->top_margin;
  1195.         
  1196.     $img->SetColor($this->iTableHeaderBackgroundColor);
  1197.     $img->FilledRectangle($xt,$yt,$xb,$yb);
  1198.     $this->tableTitle->Align("center","center");
  1199.     $this->tableTitle->Stroke($img,$xt+($xb-$xt)/2+1,$yt+($yb-$yt)/2);        
  1200.     $img->SetColor($this->iTableHeaderFrameColor);
  1201.     $img->SetLineWeight($this->iTableHeaderFrameWeight);
  1202.     $img->Rectangle($xt,$yt,$xb,$yb);
  1203.         
  1204.     // Draw the vertical dividing line
  1205.     $this->divider->Stroke($img,$xb,$yt,$xb,$img->height-$img->bottom_margin);
  1206.         
  1207.     // Draw the horizontal dividing line        
  1208.     $this->dividerh->Stroke($img,$xt,$yb,$img->width-$img->right_margin,$yb);        
  1209.     }
  1210.  
  1211.     // Main entry point to stroke scale
  1212.     function Stroke() {
  1213.     if( !$this->IsRangeSet() )
  1214.         JpGraphError::Raise("Gantt scale has not been specified.");
  1215.     $img=$this->iImg;
  1216.         
  1217.     // Stroke all headers. Aa argument we supply the offset from the
  1218.     // top which depends on any previous headers
  1219.     $offy=$this->StrokeYears(0);
  1220.     $offm=$this->StrokeMonths($offy);
  1221.     $offw=$this->StrokeWeeks($offm);
  1222.     $offd=$this->StrokeDays($offw);
  1223.  
  1224.     // We stroke again in case days also have gridlines that may have
  1225.     // overwritten the weeks gridline (or month/year). It may seem that we should have logic
  1226.     // in the days routine instead but this is much easier and wont make to much
  1227.     // of an performance impact.
  1228.     $this->StrokeWeeks($offm);        
  1229.     $this->StrokeMonths($offy);        
  1230.     $this->StrokeYears(0);
  1231.     $this->StrokeTableHeaders($offd);
  1232.         
  1233.     // Now we can calculate the correct scaling factor for each vertical position
  1234.     $this->iAvailableHeight = $img->height - $img->top_margin - $img->bottom_margin - $offd;        
  1235.     $this->iVertHeaderSize = $offd;
  1236.     if( $this->iVertSpacing == -1 )
  1237.         $this->iVertSpacing = $this->iAvailableHeight / $this->iVertLines;
  1238.     }    
  1239. }
  1240.  
  1241. //===================================================
  1242. // CLASS Constrain
  1243. // Used by GanttPlotObject
  1244. //===================================================
  1245. class Constrain {
  1246.     var $iConstrainType=CONSTRAIN_ENDSTART, $iConstrainRow=-1;
  1247.     var $iConstrainColor='black', $iConstrainArrowSize=ARROW_S2, $iConstrainArrowType=ARROWT_SOLID;
  1248.  
  1249.     function Constrain($aRow, $aType, $aColor='black', $aArrowSize=ARROW_S2, $aArrowType=ARROWT_SOLID) {
  1250.         $this->iConstrainRow = $aRow;
  1251.         $this->iConstrainType = $aType;
  1252.         $this->iConstrainColor = $aColor;
  1253.         $this->iConstrainArrowSize = $aArrowSize;
  1254.         $this->iConstrainArrowType = $aArrowType;
  1255.     }
  1256. }
  1257.  
  1258. //===================================================
  1259. // CLASS GanttPlotObject
  1260. // The common signature for a Gantt object
  1261. //===================================================
  1262. class GanttPlotObject {
  1263.     var $iVPos=0;                    // Vertical position
  1264.     var $iLabelLeftMargin=2;    // Title margin
  1265.     var $iStart="";                // Start date
  1266.     var $title,$caption;
  1267.     var $iCaptionMargin=5;
  1268.     var $csimarea='',$csimtarget='',$csimalt='';
  1269.     var $iConstrainsArray=array();
  1270.     var $iConstrainPos=array();
  1271.         
  1272.     function GanttPlotObject() {
  1273.      $this->title = new TextProperty();
  1274.     $this->title->Align("left","center");
  1275.     $this->caption = new TextProperty();
  1276.     }
  1277.  
  1278.     function GetCSIMArea() {
  1279.     return $this->csimarea;
  1280.     }
  1281.  
  1282.     function SetCSIMTarget($aTarget) {
  1283.         $this->csimtarget=$aTarget;
  1284.     }
  1285.     
  1286.     function SetCSIMAlt($aAlt) {
  1287.         $this->csimalt=$aAlt;
  1288.     }
  1289.     
  1290.     function SetConstrain($aRow,$aType,$aColor='black',$aArrowSize=ARROW_S2,$aArrowType=ARROWT_SOLID) {
  1291.         // TODO: Mabye rename to AddConstrain
  1292.         $this->iConstrainsArray[] = new Constrain($aRow, $aType, $aColor, $aArrowSize, $aArrowType);
  1293.     }
  1294.     
  1295.     function SetConstrainPos($xt,$yt,$xb,$yb) {
  1296.     $this->iConstrainPos = array($xt,$yt,$xb,$yb);
  1297.     }
  1298.  
  1299.     function GetConstrain() {
  1300.     return array($this->iConstrainRow,$this->iConstrainType);
  1301.     }
  1302.     
  1303.     function GetMinDate() {
  1304.     return $this->iStart;
  1305.     }
  1306.  
  1307.     function GetMaxDate() {
  1308.     return $this->iStart;
  1309.     }
  1310.     
  1311.     function SetCaptionMargin($aMarg) {
  1312.     $this->iCaptionMargin=$aMarg;
  1313.     }
  1314.  
  1315.     function GetAbsHeight($aImg) {
  1316.     return 0; 
  1317.     }
  1318.     
  1319.     function GetLineNbr() {
  1320.     return $this->iVPos;
  1321.     }
  1322.  
  1323.     function SetLabelLeftMargin($aOff) {
  1324.     $this->iLabelLeftMargin=$aOff;
  1325.     }        
  1326. }
  1327.  
  1328. //===================================================
  1329. // CLASS Progress
  1330. // Holds parameters for the progress indicator 
  1331. // displyed within a bar
  1332. //===================================================
  1333. class Progress {
  1334.     var $iProgress=-1, $iColor="black", $iPattern=GANTT_SOLID;
  1335.     var $iDensity=98, $iHeight=0.65; 
  1336.     
  1337.     function Set($aProg) {
  1338.     if( $aProg < 0.0 || $aProg > 1.0 )
  1339.         JpGraphError::Raise("Progress value must in range [0, 1]");
  1340.     $this->iProgress = $aProg;
  1341.     }
  1342.  
  1343.     function SetPattern($aPattern,$aColor="blue",$aDensity=98) {        
  1344.     $this->iPattern = $aPattern;
  1345.     $this->iColor = $aColor;
  1346.     $this->iDensity = $aDensity;
  1347.     }
  1348.     
  1349.     function SetHeight($aHeight) {
  1350.     $this->iHeight = $aHeight;
  1351.     }
  1352. }
  1353.  
  1354. //===================================================
  1355. // CLASS GanttBar
  1356. // Responsible for formatting individual gantt bars
  1357. //===================================================
  1358. class GanttBar extends GanttPlotObject {
  1359.     var $iEnd;
  1360.     var $iHeightFactor=0.5;
  1361.     var $iFillColor="white",$iFrameColor="blue";
  1362.     var $iShadow=false,$iShadowColor="darkgray",$iShadowWidth=1,$iShadowFrame="black";
  1363.     var $iPattern=GANTT_RDIAG,$iPatternColor="blue",$iPatternDensity=95;
  1364.     var $leftMark,$rightMark;
  1365.     var $progress;
  1366. //---------------
  1367. // CONSTRUCTOR    
  1368.     function GanttBar($aPos,$aLabel,$aStart,$aEnd,$aCaption="",$aHeightFactor=0.6) {
  1369.     parent::GanttPlotObject();    
  1370.     $this->iStart = $aStart;    
  1371.     // Is the end date given as a date or as number of days added to start date?
  1372.     if( is_string($aEnd) )
  1373.         $this->iEnd = strtotime($aEnd)+SECPERDAY;
  1374.     elseif(is_int($aEnd) || is_float($aEnd) ) 
  1375.         $this->iEnd = strtotime($aStart)+round($aEnd*SECPERDAY);
  1376.     $this->iVPos = $aPos;
  1377.     $this->iHeightFactor = $aHeightFactor;
  1378.     $this->title->Set($aLabel);
  1379.     $this->caption = new TextProperty($aCaption);
  1380.     $this->caption->Align("left","center");
  1381.     $this->leftMark =new PlotMark();
  1382.     $this->leftMark->Hide();
  1383.     $this->rightMark=new PlotMark();
  1384.     $this->rightMark->Hide();
  1385.     $this->progress = new Progress();
  1386.     }
  1387.     
  1388. //---------------
  1389. // PUBLIC METHODS    
  1390.     function SetShadow($aShadow=true,$aColor="gray") {
  1391.     $this->iShadow=$aShadow;
  1392.     $this->iShadowColor=$aColor;
  1393.     }
  1394.     
  1395.     function GetMaxDate() {
  1396.     return $this->iEnd;
  1397.     }
  1398.     
  1399.     function SetHeight($aHeight) {
  1400.     $this->iHeightFactor = $aHeight;
  1401.     }
  1402.  
  1403.     function SetColor($aColor) {
  1404.     $this->iFrameColor = $aColor;
  1405.     }
  1406.  
  1407.     function SetFillColor($aColor) {
  1408.     $this->iFillColor = $aColor;
  1409.     }
  1410.  
  1411.     function GetAbsHeight($aImg) {
  1412.     if( is_int($this->iHeightFactor) || $this->leftMark->show || $this->rightMark->show ) {
  1413.         $m=-1;
  1414.         if( is_int($this->iHeightFactor) )
  1415.         $m = $this->iHeightFactor;
  1416.         if( $this->leftMark->show ) 
  1417.         $m = max($m,$this->leftMark->width*2);
  1418.         if( $this->rightMark->show ) 
  1419.         $m = max($m,$this->rightMark->width*2);
  1420.         return $m;
  1421.     }
  1422.     else
  1423.         return -1;
  1424.     }
  1425.     
  1426.     function SetPattern($aPattern,$aColor="blue",$aDensity=95) {        
  1427.     $this->iPattern = $aPattern;
  1428.     $this->iPatternColor = $aColor;
  1429.     $this->iPatternDensity = $aDensity;
  1430.     }
  1431.  
  1432.     function Stroke($aImg,$aScale) {
  1433.     $factory = new RectPatternFactory();
  1434.     $prect = $factory->Create($this->iPattern,$this->iPatternColor);
  1435.     $prect->SetDensity($this->iPatternDensity);
  1436.  
  1437.     // If height factor is specified as a float between 0,1 then we take it as meaning
  1438.     // percetage of the scale width between horizontal line.
  1439.     // If it is an integer > 1 we take it to mean the absolute height in pixels
  1440.     if( $this->iHeightFactor > -0.0 && $this->iHeightFactor <= 1.1)
  1441.         $vs = $aScale->GetVertSpacing()*$this->iHeightFactor;
  1442.     elseif(is_int($this->iHeightFactor) && $this->iHeightFactor>2 && $this->iHeightFactor<200)
  1443.         $vs = $this->iHeightFactor;
  1444.     else
  1445.         JpGraphError::Raise("Specified height (".$this->iHeightFactor.") for gantt bar is out of range.");
  1446.     
  1447.     // Clip date to min max dates to show
  1448.     $st = $aScale->NormalizeDate($this->iStart);
  1449.     $en = $aScale->NormalizeDate($this->iEnd);
  1450.     
  1451.  
  1452.     $limst = max($st,$aScale->iStartDate);
  1453.     $limen = min($en,$aScale->iEndDate+SECPERDAY);
  1454.             
  1455.     $xt = round($aScale->TranslateDate($limst));
  1456.     $xb = round($aScale->TranslateDate($limen)-1); 
  1457.     $yt = round($aScale->TranslateVertPos($this->iVPos)-$vs-($aScale->GetVertSpacing()/2-$vs/2));
  1458.     $yb = round($aScale->TranslateVertPos($this->iVPos)-($aScale->GetVertSpacing()/2-$vs/2));
  1459.     $middle = round($yt+($yb-$yt)/2);
  1460.     $this->title->Stroke($aImg,$aImg->left_margin+$this->iLabelLeftMargin,$middle);        
  1461.  
  1462.     // CSIM for title
  1463.     if( $this->title->csimtarget != '' ) {
  1464.         $title_xt = $aImg->left_margin+$this->iLabelLeftMargin;
  1465.         $title_xb = $title_xt + $this->title->GetWidth($aImg);
  1466.  
  1467.         $coords = "$title_xt,$yt,$title_xb,$yt,$title_xb,$yb,$title_xt,$yb";
  1468.         $this->csimarea .= "<area shape=\"poly\" coords=\"$coords\" href=\"".$this->title->csimtarget."\"";
  1469.         if( $this->title->csimalt != '' ) {
  1470.         $tmp = $this->title->csimalt;
  1471.         $this->csimarea .= " alt=\"$tmp\" title=\"$tmp\"";
  1472.         }
  1473.         $this->csimarea .= ">\n";
  1474.     }
  1475.  
  1476.     // Check if the bar is totally outside the current scale range
  1477.     if( $en <  $aScale->iStartDate+SECPERDAY || $st > $aScale->iEndDate )
  1478.         return;
  1479.             
  1480.  
  1481.     // Remember the positions for the bar
  1482.     $this->SetConstrainPos($xt,$yt,$xb,$yb);
  1483.         
  1484.     $prect->ShowFrame(false);
  1485.     $prect->SetBackground($this->iFillColor);
  1486.     if( $this->iShadow ) {
  1487.         $aImg->SetColor($this->iFrameColor);
  1488.         $aImg->ShadowRectangle($xt,$yt,$xb,$yb,$this->iFillColor,$this->iShadowWidth,$this->iShadowColor);                
  1489.         $prect->SetPos(new Rectangle($xt+1,$yt+1,$xb-$xt-$this->iShadowWidth-2,$yb-$yt-$this->iShadowWidth-2));                
  1490.         $prect->Stroke($aImg);
  1491.     }
  1492.     else {    
  1493.         $prect->SetPos(new Rectangle($xt,$yt,$xb-$xt+1,$yb-$yt+1));                
  1494.         $prect->Stroke($aImg);
  1495.         $aImg->SetColor($this->iFrameColor);
  1496.         $aImg->Rectangle($xt,$yt,$xb,$yb);
  1497.     }
  1498.  
  1499.     // CSIM for bar
  1500.     if( $this->csimtarget != '' ) {
  1501.  
  1502.         $coords = "$xt,$yt,$xb,$yt,$xb,$yb,$xt,$yb";
  1503.         $this->csimarea .= "<area shape=\"poly\" coords=\"$coords\" href=\"".
  1504.                       $this->csimtarget."\"";
  1505.         if( $this->csimalt != '' ) {
  1506.         $tmp = $this->csimalt;
  1507.         $this->csimarea .= " alt=\"$tmp\" title=\"$tmp\"";
  1508.         }
  1509.         $this->csimarea .= ">\n";
  1510.     }
  1511.  
  1512.     // Draw progress bar inside activity bar
  1513.     if( $this->progress->iProgress > 0 ) {
  1514.         
  1515.         $xtp = $aScale->TranslateDate($st);
  1516.         $xbp = $aScale->TranslateDate($en);
  1517.         $len = ($xbp-$xtp)*$this->progress->iProgress;
  1518.  
  1519.         $endpos = $xtp+$len;
  1520.         if( $endpos > $xt ) {
  1521.         $len -= ($xt-$xtp); 
  1522.  
  1523.         // Make sure that the progess bar doesn't extend over the end date
  1524.         if( $xtp+$len-1 > $xb )
  1525.             $len = $xb - $xtp + 1;
  1526.         
  1527.         if( $xtp < $xt ) 
  1528.             $xtp = $xt;
  1529.         
  1530.         $prog = $factory->Create($this->progress->iPattern,$this->progress->iColor);
  1531.         $prog->SetDensity($this->progress->iDensity);
  1532.             $barheight = ($yb-$yt+1);
  1533.         if( $this->iShadow ) 
  1534.             $barheight -= $this->iShadowWidth;
  1535.         $progressheight = floor($barheight*$this->progress->iHeight);
  1536.         $marg = ceil(($barheight-$progressheight)/2);
  1537.             $pos = new Rectangle($xtp,$yt + $marg, $len,$barheight-2*$marg);
  1538.         $prog->SetPos($pos);
  1539.         $prog->Stroke($aImg);
  1540.         }
  1541.     }
  1542.     
  1543.     // We don't plot the end mark if the bar has been capped
  1544.     if( $limst == $st ) {
  1545.         $y = $middle;
  1546.         // We treat the RIGHT and LEFT triangle mark a little bi
  1547.         // special so that these marks are placed right under the
  1548.         // bar.
  1549.         if( $this->leftMark->GetType() == MARK_LEFTTRIANGLE ) {
  1550.         $y = $yb ; 
  1551.         }
  1552.         $this->leftMark->Stroke($aImg,$xt,$y);
  1553.     }
  1554.     if( $limen == $en ) {
  1555.         $y = $middle;
  1556.         // We treat the RIGHT and LEFT triangle mark a little bi
  1557.         // special so that these marks are placed right under the
  1558.         // bar.
  1559.         if( $this->rightMark->GetType() == MARK_RIGHTTRIANGLE ) {
  1560.         $y = $yb ; 
  1561.         }
  1562.         $this->rightMark->Stroke($aImg,$xb,$y);
  1563.         
  1564.         $margin = $this->iCaptionMargin;
  1565.         if( $this->rightMark->show ) 
  1566.             $margin += $this->rightMark->GetWidth();
  1567.         $this->caption->Stroke($aImg,$xb+$margin,$middle);        
  1568.     }
  1569.     }
  1570. }
  1571.  
  1572. //===================================================
  1573. // CLASS MileStone
  1574. // Responsible for formatting individual milestones
  1575. //===================================================
  1576. class MileStone extends GanttPlotObject {
  1577.     var $mark;
  1578.     
  1579. //---------------
  1580. // CONSTRUCTOR    
  1581.     function MileStone($aVPos,$aLabel,$aDate,$aCaption="") {
  1582.     GanttPlotObject::GanttPlotObject();
  1583.     $this->caption->Set($aCaption);
  1584.     $this->caption->Align("left","center");
  1585.     $this->caption->SetFont(FF_FONT1,FS_BOLD);
  1586.     $this->title->Set($aLabel);
  1587.     $this->title->SetColor("darkred");
  1588.     $this->mark = new PlotMark();
  1589.     $this->mark->SetWidth(10);
  1590.     $this->mark->SetType(MARK_DIAMOND);
  1591.     $this->mark->SetColor("darkred");
  1592.     $this->mark->SetFillColor("darkred");
  1593.     $this->iVPos = $aVPos;
  1594.     $this->iStart = $aDate;
  1595.     }
  1596.     
  1597. //---------------
  1598. // PUBLIC METHODS    
  1599.     
  1600.     function GetAbsHeight($aImg) {
  1601.     return max($this->title->GetHeight($aImg),$this->mark->GetWidth());
  1602.     }
  1603.         
  1604.     function Stroke($aImg,$aScale) {
  1605.     // Put the mark in the middle at the middle of the day
  1606.     $d = $aScale->NormalizeDate($this->iStart)+SECPERDAY/2;
  1607.     $x = $aScale->TranslateDate($d);
  1608.     $y = $aScale->TranslateVertPos($this->iVPos)-($aScale->GetVertSpacing()/2);
  1609.     $this->title->Stroke($aImg,$aImg->left_margin+$this->iLabelLeftMargin,$y);
  1610.  
  1611.     // CSIM for title
  1612.     if( $this->title->csimtarget != '' ) {
  1613.         $title_xt = $aImg->left_margin+$this->iLabelLeftMargin;
  1614.         $title_xb = $title_xt + $this->title->GetWidth($aImg);
  1615.         $yt = round($y - $this->title->GetHeight($aImg)/2);
  1616.         $yb = round($y + $this->title->GetHeight($aImg)/2);
  1617.         $coords = "$title_xt,$yt,$title_xb,$yt,$title_xb,$yb,$title_xt,$yb";
  1618.         $this->csimarea .= "<area shape=\"poly\" coords=\"$coords\" href=\"".$this->title->csimtarget."\"";
  1619.         if( $this->title->csimalt != '' ) {
  1620.         $tmp = $this->title->csimalt;
  1621.         $this->csimarea .= " alt=\"$tmp\" title=\"$tmp\"";
  1622.         }
  1623.         $this->csimarea .= ">\n";
  1624.     }
  1625.  
  1626.  
  1627.     if( $d <  $aScale->iStartDate || $d > $aScale->iEndDate )
  1628.         return;
  1629.  
  1630.     // Remember the coordinates for any constrains linking to
  1631.     // this milestone
  1632.     $w = $this->mark->GetWidth()/2;
  1633.     $this->SetConstrainPos($x,round($y-$w),$x,round($y+$w));
  1634.     
  1635.     // Setup CSIM
  1636.     if( $this->csimtarget != '' ) {
  1637.         $this->mark->SetCSIMTarget( $this->csimtarget );
  1638.         $this->mark->SetCSIMAlt( $this->csimalt );
  1639.     }
  1640.         
  1641.     $this->mark->Stroke($aImg,$x,$y);        
  1642.     $this->caption->Stroke($aImg,$x+$this->mark->width/2+$this->iCaptionMargin,$y);
  1643.  
  1644.     $this->csimarea .= $this->mark->GetCSIMAreas();
  1645.     }
  1646. }
  1647.  
  1648.  
  1649. //===================================================
  1650. // CLASS GanttVLine
  1651. // Responsible for formatting individual milestones
  1652. //===================================================
  1653.  
  1654. class GanttVLine extends GanttPlotObject {
  1655.  
  1656.     var $iLine,$title_margin=3;
  1657.     var $iDayOffset=1;    // Defult to right edge of day
  1658.     
  1659. //---------------
  1660. // CONSTRUCTOR    
  1661.     function GanttVLine($aDate,$aTitle="",$aColor="black",$aWeight=3,$aStyle="dashed") {
  1662.     GanttPlotObject::GanttPlotObject();
  1663.     $this->iLine = new LineProperty();
  1664.     $this->iLine->SetColor($aColor);
  1665.     $this->iLine->SetWeight($aWeight);
  1666.     $this->iLine->SetStyle($aStyle);
  1667.     $this->iStart = $aDate;
  1668.     $this->title->Set($aTitle);
  1669.     }
  1670.  
  1671. //---------------
  1672. // PUBLIC METHODS    
  1673.  
  1674.     function SetDayOffset($aOff=0.5) {
  1675.     if( $aOff < 0.0 || $aOff > 1.0 )
  1676.         JpGraphError::Raise("Offset for vertical line must be in range [0,1]");
  1677.     $this->iDayOffset = $aOff;
  1678.     }
  1679.     
  1680.     function SetTitleMargin($aMarg) {
  1681.     $this->title_margin = $aMarg;
  1682.     }
  1683.     
  1684.     function Stroke($aImg,$aScale) {
  1685.     $d = $aScale->NormalizeDate($this->iStart)+$this->iDayOffset*SECPERDAY;
  1686.  
  1687.     if( $d <  $aScale->iStartDate || $d > $aScale->iEndDate )
  1688.         return;    
  1689.  
  1690.     $x = $aScale->TranslateDate($d);    
  1691.     $y1 = $aScale->iVertHeaderSize+$aImg->top_margin;
  1692.     $y2 = $aImg->height - $aImg->bottom_margin;    
  1693.     $this->iLine->Stroke($aImg,$x,$y1,$x,$y2);
  1694.     $this->title->Align("center","top");
  1695.     $this->title->Stroke($aImg,$x,$y2+$this->title_margin);
  1696.     }    
  1697. }
  1698.  
  1699. //===================================================
  1700. // CLASS LinkArrow
  1701. // Handles the drawing of a link line between 2 points
  1702. //===================================================
  1703. class LinkArrow {
  1704.     var $ix,$iy;
  1705.     var $isizespec = array(
  1706.     array(2,3),array(3,5),array(3,8),array(6,15),array(8,22));
  1707.     var $iDirection=ARROW_DOWN,$iType=ARROWT_SOLID,$iSize=ARROW_S2;
  1708.     var $iColor='black';
  1709.  
  1710.     function LinkArrow($x,$y,$aDirection,$aType=ARROWT_SOLID,$aSize=ARROW_S2) {
  1711.     $this->iDirection = $aDirection;
  1712.     $this->iType = $aType;
  1713.     $this->iSize = $aSize;
  1714.     $this->ix = $x;
  1715.     $this->iy = $y;
  1716.     }
  1717.     
  1718.     function SetColor($aColor) {
  1719.     $this->iColor = $aColor;
  1720.     }
  1721.  
  1722.     function SetSize($aSize) {
  1723.     $this->iSize = $aSize;
  1724.     }
  1725.  
  1726.     function SetType($aType) {
  1727.     $this->iType = $aType;
  1728.     }
  1729.  
  1730.     function Stroke($aImg) {
  1731.     list($dx,$dy) = $this->isizespec[$this->iSize];
  1732.     $x = $this->ix;
  1733.     $y = $this->iy;
  1734.     switch ( $this->iDirection ) {
  1735.         case ARROW_DOWN:
  1736.         $c = array($x,$y,$x-$dx,$y-$dy,$x+$dx,$y-$dy,$x,$y);
  1737.         break;
  1738.         case ARROW_UP:
  1739.         $c = array($x,$y,$x-$dx,$y+$dy,$x+$dx,$y+$dy,$x,$y);
  1740.         break;
  1741.         case ARROW_LEFT:
  1742.         $c = array($x,$y,$x+$dy,$y-$dx,$x+$dy,$y+$dx,$x,$y);
  1743.         break;
  1744.         case ARROW_RIGHT:
  1745.         $c = array($x,$y,$x-$dy,$y-$dx,$x-$dy,$y+$dx,$x,$y);
  1746.         break;
  1747.         default:
  1748.         JpGraphError::Raise('Unknown arrow direction for link.');
  1749.         die();
  1750.         break;
  1751.     }
  1752.     $aImg->SetColor($this->iColor);
  1753.     switch( $this->iType ) {
  1754.         case ARROWT_SOLID:
  1755.         $aImg->FilledPolygon($c);
  1756.         break;
  1757.         case ARROWT_OPEN:
  1758.         $aImg->Polygon($c);
  1759.         break;
  1760.         default:
  1761.         JpGraphError::Raise('Unknown arrow type for link.');
  1762.         die();
  1763.         break;        
  1764.     }
  1765.     }
  1766. }
  1767.  
  1768. //===================================================
  1769. // CLASS GanttLink
  1770. // Handles the drawing of a link line between 2 points
  1771. //===================================================
  1772.  
  1773. class GanttLink {
  1774.     var $iArrowType='';
  1775.     var $ix1,$ix2,$iy1,$iy2;
  1776.     var $iPathType=0, $iPathExtend=15;
  1777.     var $iColor='black',$iWeight=1;
  1778.     var $iArrowSize=ARROW_S2,$iArrowType=ARROWT_SOLID;
  1779.  
  1780.     function GanttLink($x1=0,$y1=0,$x2=0,$y2=0) {
  1781.     $this->ix1 = $x1;
  1782.     $this->ix2 = $x2;
  1783.     $this->iy1 = $y1;
  1784.     $this->iy2 = $y2;
  1785.     }
  1786.  
  1787.     function SetPos($x1,$y1,$x2,$y2) {
  1788.     $this->ix1 = $x1;
  1789.     $this->ix2 = $x2;
  1790.     $this->iy1 = $y1;
  1791.     $this->iy2 = $y2;
  1792.     }
  1793.  
  1794.     function SetPath($aPath) {
  1795.     $this->iPathType = $aPath;
  1796.     }
  1797.  
  1798.     function SetColor($aColor) {
  1799.     $this->iColor = $aColor;
  1800.     }
  1801.  
  1802.     function SetArrow($aSize,$aType=ARROWT_SOLID) {
  1803.     $this->iArrowSize = $aSize;
  1804.     $this->iArrowType = $aType;
  1805.     }
  1806.     
  1807.     function SetWeight($aWeight) {
  1808.     $this->iWeight = $aWeight;
  1809.     }
  1810.  
  1811.     function Stroke($aImg) {
  1812.     $x1 = $this->ix1 ;
  1813.     $x2 = $this->ix2 ;
  1814.     $y1 = $this->iy1 ;
  1815.     $y2 = $this->iy2 ;
  1816.     
  1817.     $midy = round(($y1+$y2)/2);
  1818.     
  1819.     switch ( $this->iPathType  ) {
  1820.         case PATH_BLEND_ON_MIDDLE:
  1821.             $c = array($x1,$y1,$x1,$midy,$x2,$midy,$x2,$y2);
  1822.             break;
  1823.         case PATH_RECTANGULAR:
  1824.             if( $x2 > $x1 ) {
  1825.                 $c = array($x1,$y1,$x2,$y1,$x2,$y2);
  1826.             } else {
  1827.                 // Always extend out horizontally a bit from the first point
  1828.                 $c = array($x1,$y1,$x1+$this->iPathExtend,$y1,
  1829.                 $x1+$this->iPathExtend,$midy,
  1830.                 $x2,$midy,$x2,$y2);
  1831.             }
  1832.             break;
  1833.         case PATH_DIAGONAL:
  1834.             $c = array($x1, $y1, $x2, $y2);
  1835.             break;
  1836.           default:
  1837.             JpGraphError::Raise('Internal error: Unknown path type specified for link.');
  1838.             exit(1);
  1839.             break;
  1840.     }
  1841.     $arrow = new LinkArrow($x2,$y2,($y2 > $y1?ARROW_DOWN:ARROW_UP));
  1842.  
  1843.     $aImg->SetColor($this->iColor);
  1844.     $aImg->SetLineWeight($this->iWeight);
  1845.     $aImg->Polygon($c);
  1846.     $aImg->SetLineWeight(1);
  1847.     $arrow->SetColor($this->iColor);
  1848.     $arrow->SetSize($this->iArrowSize);
  1849.     $arrow->SetType($this->iArrowType);
  1850.     $arrow->Stroke($aImg);
  1851.     }
  1852. }
  1853.  
  1854. // <EOF>
  1855. ?>
  1856.