<?php
/*=======================================================================
// File:    JPGRAPH_GANTT.PHP
// Description:    JpGraph Gantt plot extension
// Created:     2001-11-12
// Author:    Johan Persson (johanp@aditus.nu)
// Ver:        $Id: jpgraph_gantt.php 548 2006-02-18 07:29:57Z ljp $
//
// Copyright (c) Aditus Consulting. All rights reserved.
//========================================================================
*/

require_once('jpgraph_plotband.php'); 
require_once(
'jpgraph_iconplot.php'); 
require_once(
'jpgraph_plotmark.inc.php');

// Maximum size for Automatic Gantt chart
DEFINE('MAX_GANTTIMG_SIZE_W',4000);
DEFINE('MAX_GANTTIMG_SIZE_H',5000);

// Scale Header types
DEFINE("GANTT_HDAY",1);
DEFINE("GANTT_HWEEK",2);
DEFINE("GANTT_HMONTH",4);
DEFINE("GANTT_HYEAR",8);
DEFINE("GANTT_HHOUR",16);
DEFINE("GANTT_HMIN",32);

// Bar patterns
DEFINE("GANTT_RDIAG",BAND_RDIAG);    // Right diagonal lines
DEFINE("GANTT_LDIAG",BAND_LDIAG); // Left diagonal lines
DEFINE("GANTT_SOLID",BAND_SOLID); // Solid one color
DEFINE("GANTT_VLINE",BAND_VLINE); // Vertical lines
DEFINE("GANTT_HLINE",BAND_HLINE);  // Horizontal lines
DEFINE("GANTT_3DPLANE",BAND_3DPLANE);  // "3D" Plane
DEFINE("GANTT_HVCROSS",BAND_HVCROSS);  // Vertical/Hor crosses
DEFINE("GANTT_DIAGCROSS",BAND_DIAGCROSS); // Diagonal crosses

// Conversion constant
DEFINE("SECPERDAY",3600*24);

// Locales. ONLY KEPT FOR BACKWARDS COMPATIBILITY
// You should use the proper locale strings directly 
// from now on. 
DEFINE("LOCALE_EN","en_UK");
DEFINE("LOCALE_SV","sv_SE");

// Layout of bars
DEFINE("GANTT_EVEN",1);
DEFINE("GANTT_FROMTOP",2);

// Style for minute header
DEFINE("MINUTESTYLE_MM",0);        // 15
DEFINE("MINUTESTYLE_CUSTOM",2);        // Custom format


// Style for hour header
DEFINE("HOURSTYLE_HM24",0);        // 13:10
DEFINE("HOURSTYLE_HMAMPM",1);        // 1:10pm
DEFINE("HOURSTYLE_H24",2);        // 13
DEFINE("HOURSTYLE_HAMPM",3);        // 1pm
DEFINE("HOURSTYLE_CUSTOM",4);        // User defined

// Style for day header
DEFINE("DAYSTYLE_ONELETTER",0);        // "M"
DEFINE("DAYSTYLE_LONG",1);        // "Monday"
DEFINE("DAYSTYLE_LONGDAYDATE1",2);    // "Monday 23 Jun"
DEFINE("DAYSTYLE_LONGDAYDATE2",3);    // "Monday 23 Jun 2003"
DEFINE("DAYSTYLE_SHORT",4);        // "Mon"
DEFINE("DAYSTYLE_SHORTDAYDATE1",5);    // "Mon 23/6"
DEFINE("DAYSTYLE_SHORTDAYDATE2",6);    // "Mon 23 Jun"
DEFINE("DAYSTYLE_SHORTDAYDATE3",7);    // "Mon 23"
DEFINE("DAYSTYLE_SHORTDATE1",8);    // "23/6"
DEFINE("DAYSTYLE_SHORTDATE2",9);    // "23 Jun"
DEFINE("DAYSTYLE_SHORTDATE3",10);    // "Mon 23"
DEFINE("DAYSTYLE_SHORTDATE4",11);    // "23"
DEFINE("DAYSTYLE_CUSTOM",12);        // "M"

// Styles for week header
DEFINE("WEEKSTYLE_WNBR",0);
DEFINE("WEEKSTYLE_FIRSTDAY",1);
DEFINE("WEEKSTYLE_FIRSTDAY2",2);
DEFINE("WEEKSTYLE_FIRSTDAYWNBR",3);
DEFINE("WEEKSTYLE_FIRSTDAY2WNBR",4);

// Styles for month header
DEFINE("MONTHSTYLE_SHORTNAME",0);
DEFINE("MONTHSTYLE_LONGNAME",1);
DEFINE("MONTHSTYLE_LONGNAMEYEAR2",2);
DEFINE("MONTHSTYLE_SHORTNAMEYEAR2",3);
DEFINE("MONTHSTYLE_LONGNAMEYEAR4",4);
DEFINE("MONTHSTYLE_SHORTNAMEYEAR4",5);
DEFINE("MONTHSTYLE_FIRSTLETTER",6);


// Types of constrain links
DEFINE('CONSTRAIN_STARTSTART',0);
DEFINE('CONSTRAIN_STARTEND',1);
DEFINE('CONSTRAIN_ENDSTART',2);
DEFINE('CONSTRAIN_ENDEND',3);

// Arrow direction for constrain links
DEFINE('ARROW_DOWN',0);
DEFINE('ARROW_UP',1);
DEFINE('ARROW_LEFT',2);
DEFINE('ARROW_RIGHT',3);

// Arrow type for constrain type
DEFINE('ARROWT_SOLID',0);
DEFINE('ARROWT_OPEN',1);

// Arrow size for constrain lines
DEFINE('ARROW_S1',0);
DEFINE('ARROW_S2',1);
DEFINE('ARROW_S3',2);
DEFINE('ARROW_S4',3);
DEFINE('ARROW_S5',4);

// Activity types for use with utility method CreateSimple()
DEFINE('ACTYPE_NORMAL',0);
DEFINE('ACTYPE_GROUP',1);
DEFINE('ACTYPE_MILESTONE',2);

DEFINE('ACTINFO_3D',1);
DEFINE('ACTINFO_2D',0);


// Check if array_fill() exists
if (!function_exists('array_fill')) {
    function 
array_fill($iStart$iLen$vValue) {
    
$aResult = array();
    for (
$iCount $iStart$iCount $iLen $iStart$iCount++) {
        
$aResult[$iCount] = $vValue;
    }
    return 
$aResult;
    }
}

//===================================================
// CLASS GanttActivityInfo
// Description: 
//===================================================
class GanttActivityInfo {
    public 
$iShow=true;
    public 
$iLeftColMargin=4,$iRightColMargin=1,$iTopColMargin=1,$iBottomColMargin=3;
    public 
$vgrid null;
    private 
$iColor='black';
    private 
$iBackgroundColor='lightgray';
    private 
$iFFamily=FF_FONT1,$iFStyle=FS_NORMAL,$iFSize=10,$iFontColor='black';
    private 
$iTitles=array();
    private 
$iWidth=array(),$iHeight=-1;
    private 
$iTopHeaderMargin 4;
    private 
$iStyle=1;
    private 
$iHeaderAlign='center';

    function 
GanttActivityInfo() {
    
$this->vgrid = new LineProperty();
    }

    function 
Hide($aF=true) {
    
$this->iShow=!$aF;
    }

    function 
Show($aF=true) {
    
$this->iShow=$aF;
    }

    
// Specify font
    
function SetFont($aFFamily,$aFStyle=FS_NORMAL,$aFSize=10) {
    
$this->iFFamily $aFFamily;
    
$this->iFStyle     $aFStyle;
    
$this->iFSize     $aFSize;
    }

    function 
SetStyle($aStyle) {
    
$this->iStyle $aStyle;
    }

    function 
SetColumnMargin($aLeft,$aRight) {
    
$this->iLeftColMargin $aLeft;
    
$this->iRightColMargin $aRight;
    }

    function 
SetFontColor($aFontColor) {
    
$this->iFontColor $aFontColor;
    }

    function 
SetColor($aColor) {
    
$this->iColor $aColor;
    }

    function 
SetBackgroundColor($aColor) {
    
$this->iBackgroundColor $aColor;
    }

    function 
SetColTitles($aTitles,$aWidth=null) {
    
$this->iTitles $aTitles;
    
$this->iWidth $aWidth;
    }

    function 
SetMinColWidth($aWidths) {
    
$n min(count($this->iTitles),count($aWidths));
    for(
$i=0$i $n; ++$i ) {
        if( !empty(
$aWidths[$i]) ) {
        if( empty(
$this->iWidth[$i]) ) {
            
$this->iWidth[$i] = $aWidths[$i];
        }
        else {
            
$this->iWidth[$i] = max($this->iWidth[$i],$aWidths[$i]);
        }
        }
    }
    }

    function 
GetWidth($aImg) {
    
$txt = new TextProperty();
    
$txt->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
    
$n count($this->iTitles) ;
    
$rm=$this->iRightColMargin;
    
$w 0;
    for(
$h=0$i=0$i $n; ++$i ) {
        
$w += $this->iLeftColMargin;
        
$txt->Set($this->iTitles[$i]);
        if( !empty(
$this->iWidth[$i]) ) {
        
$w1 max($txt->GetWidth($aImg)+$rm,$this->iWidth[$i]);
        }
        else {
        
$w1 $txt->GetWidth($aImg)+$rm;
        }
        
$this->iWidth[$i] = $w1;
        
$w += $w1;
        
$h max($h,$txt->GetHeight($aImg));
    }
    
$this->iHeight $h+$this->iTopHeaderMargin;
        
$txt='';
    return 
$w;
    }
    
    function 
GetColStart($aImg,&$aStart,$aAddLeftMargin=false) {
    
$n count($this->iTitles) ;
    
$adj $aAddLeftMargin $this->iLeftColMargin 0;
    
$aStart=array($aImg->left_margin+$adj);
    for( 
$i=1$i $n; ++$i ) {
        
$aStart[$i] = $aStart[$i-1]+$this->iLeftColMargin+$this->iWidth[$i-1];
    }
    }
    
    
// Adjust headers left, right or centered
    
function SetHeaderAlign($aAlign) {
    
$this->iHeaderAlign=$aAlign;
    }

    function 
Stroke($aImg,$aXLeft,$aYTop,$aXRight,$aYBottom,$aUseTextHeight=false) {

    if( !
$this->iShow ) return;

    
$txt = new TextProperty();
    
$txt->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
    
$txt->SetColor($this->iFontColor);
    
$txt->SetAlign($this->iHeaderAlign,'top');
    
$n=count($this->iTitles);

    if( 
$n == 
        return;
    
    
$x $aXLeft;
    
$h $this->iHeight;
    
$yTop $aUseTextHeight $aYBottom-$h-$this->iTopColMargin-$this->iBottomColMargin $aYTop ;

    if( 
$h ) {
        
JpGraphError::RaiseL(6001);
//('Internal error. Height for ActivityTitles is < 0');
    
}

    
$aImg->SetLineWeight(1);
    
// Set background color
    
$aImg->SetColor($this->iBackgroundColor);
    
$aImg->FilledRectangle($aXLeft,$yTop,$aXRight,$aYBottom-1);

    if( 
$this->iStyle == ) {
        
// Make a 3D effect
        
$aImg->SetColor('white');
        
$aImg->Line($aXLeft,$yTop+1,
            
$aXRight,$yTop+1);
    }
    
    for(
$i=0$i $n; ++$i ) {
        if( 
$this->iStyle == ) {
        
// Make a 3D effect
        
$aImg->SetColor('white');
        
$aImg->Line($x+1,$yTop,$x+1,$aYBottom);
        }
        
$x += $this->iLeftColMargin;
        
$txt->Set($this->iTitles[$i]);
        
        
// Adjust the text anchor position according to the choosen alignment
        
$xp $x;
        if( 
$this->iHeaderAlign == 'center' ) {
        
$xp = (($x-$this->iLeftColMargin)+($x+$this->iWidth[$i]))/2;
        }
        elseif( 
$this->iHeaderAlign == 'right' ) {
        
$xp $x +$this->iWidth[$i]-$this->iRightColMargin;
        }
            
        
$txt->Stroke($aImg,$xp,$yTop+$this->iTopHeaderMargin);
        
$x += $this->iWidth[$i];
        if( 
$i $n-) {
        
$aImg->SetColor($this->iColor);
        
$aImg->Line($x,$yTop,$x,$aYBottom);
        }
    }

    
$aImg->SetColor($this->iColor);
    
$aImg->Line($aXLeft,$yTop$aXRight,$yTop);

    
// Stroke vertical column dividers
    
$cols=array();
    
$this->GetColStart($aImg,$cols);
    
$n=count($cols);
    for( 
$i=1$i $n; ++$i ) {
        
$this->vgrid->Stroke($aImg,$cols[$i],$aYBottom,$cols[$i],
                    
$aImg->height $aImg->bottom_margin);
    }
    }
}


//===================================================
// CLASS GanttGraph
// Description: Main class to handle gantt graphs
//===================================================
class GanttGraph extends Graph {
    public 
$scale;        // Public accessible
    
public $hgrid=null;
    private 
$iObj=array();                // Gantt objects
    
private $iLabelHMarginFactor=0.2;    // 10% margin on each side of the labels
    
private $iLabelVMarginFactor=0.4;    // 40% margin on top and bottom of label
    
private $iLayout=GANTT_FROMTOP;    // Could also be GANTT_EVEN
    
private $iSimpleFont FF_FONT1,$iSimpleFontSize=11;
    private 
$iSimpleStyle=GANTT_RDIAG,$iSimpleColor='yellow',$iSimpleBkgColor='red';
    private 
$iSimpleProgressBkgColor='gray',$iSimpleProgressColor='darkgreen';
    private 
$iSimpleProgressStyle=GANTT_SOLID;
//---------------
// CONSTRUCTOR    
    // Create a new gantt graph
    
function GanttGraph($aWidth=0,$aHeight=0,$aCachedName="",$aTimeOut=0,$aInline=true) {

    
// Backward compatibility
    
if( $aWidth == -$aWidth=0;
    if( 
$aHeight == -$aHeight=0;

    if( 
$aWidth<  || $aHeight ) {
        
JpgraphError::RaiseL(6002);
//("You can't specify negative sizes for Gantt graph dimensions. Use 0 to indicate that you want the library to automatically determine a dimension.");
    
}
    
Graph::Graph($aWidth,$aHeight,$aCachedName,$aTimeOut,$aInline);        
    
$this->scale = new GanttScale($this->img);

    
// Default margins
    
$this->img->SetMargin(15,17,25,15);

    
$this->hgrid = new HorizontalGridLine();
        
    
$this->scale->ShowHeaders(GANTT_HWEEK|GANTT_HDAY);
    
$this->SetBox();
    }
    
//---------------
// PUBLIC METHODS

    // 

    
function SetSimpleFont($aFont,$aSize) {
    
$this->iSimpleFont $aFont;
    
$this->iSimpleFontSize $aSize;
    }

    function 
SetSimpleStyle($aBand,$aColor,$aBkgColor) {
    
$this->iSimpleStyle $aBand;
    
$this->iSimpleColor $aColor;
    
$this->iSimpleBkgColor $aBkgColor;
    }

    
// A utility function to help create basic Gantt charts
    
function CreateSimple($data,$constrains=array(),$progress=array()) {
    
$num count($data);
    for( 
$i=0$i $num; ++$i) {
        switch( 
$data[$i][1] ) {
        case 
ACTYPE_GROUP:
            
// Create a slightly smaller height bar since the
            // "wings" at the end will make it look taller
            
$a = new GanttBar($data[$i][0],$data[$i][2],$data[$i][3],$data[$i][4],'',8);
            
$a->title->SetFont($this->iSimpleFont,FS_BOLD,$this->iSimpleFontSize);        
            
$a->rightMark->Show();
            
$a->rightMark->SetType(MARK_RIGHTTRIANGLE);
            
$a->rightMark->SetWidth(8);
            
$a->rightMark->SetColor('black');
            
$a->rightMark->SetFillColor('black');
        
            
$a->leftMark->Show();
            
$a->leftMark->SetType(MARK_LEFTTRIANGLE);
            
$a->leftMark->SetWidth(8);
            
$a->leftMark->SetColor('black');
            
$a->leftMark->SetFillColor('black');
        
            
$a->SetPattern(BAND_SOLID,'black');
            
$csimpos 6;
            break;
        
        case 
ACTYPE_NORMAL:
            
$a = new GanttBar($data[$i][0],$data[$i][2],$data[$i][3],$data[$i][4],'',10);
            
$a->title->SetFont($this->iSimpleFont,FS_NORMAL,$this->iSimpleFontSize);
            
$a->SetPattern($this->iSimpleStyle,$this->iSimpleColor);
            
$a->SetFillColor($this->iSimpleBkgColor);
            
// Check if this activity should have a constrain line
            
$n count($constrains);
            for( 
$j=0$j $n; ++$j ) {
            if( empty(
$constrains[$j]) || (count($constrains[$j]) != 3) ) {
                
JpGraphError::RaiseL(6003,$j);
//("Invalid format for Constrain parameter at index=$j in CreateSimple(). Parameter must start with index 0 and contain arrays of (Row,Constrain-To,Constrain-Type)");     
            
}
            if( 
$constrains[$j][0]==$data[$i][0] ) {
                
$a->SetConstrain($constrains[$j][1],$constrains[$j][2],'black',ARROW_S2,ARROWT_SOLID);    
            }
            }

            
// Check if this activity have a progress bar
            
$n count($progress);
            for( 
$j=0$j $n; ++$j ) {
            
            if( empty(
$progress[$j]) || (count($progress[$j]) != 2) ) {
                
JpGraphError::RaiseL(6004,$j);
//("Invalid format for Progress parameter at index=$j in CreateSimple(). Parameter must start with index 0 and contain arrays of (Row,Progress)");    
            
}
            if( 
$progress[$j][0]==$data[$i][0] ) {
                
$a->progress->Set($progress[$j][1]);
                
$a->progress->SetPattern($this->iSimpleProgressStyle,
                             
$this->iSimpleProgressColor);
                
$a->progress->SetFillColor($this->iSimpleProgressBkgColor);
                
//$a->progress->SetPattern($progress[$j][2],$progress[$j][3]);
                
break;
            }
            }
            
$csimpos 6;
            break;

        case 
ACTYPE_MILESTONE:
            
$a = new MileStone($data[$i][0],$data[$i][2],$data[$i][3]);
            
$a->title->SetFont($this->iSimpleFont,FS_NORMAL,$this->iSimpleFontSize);
            
$a->caption->SetFont($this->iSimpleFont,FS_NORMAL,$this->iSimpleFontSize);
            
$csimpos 5;
            break;
        default:
            die(
'Unknown activity type');
            break;
        }

        
// Setup caption
        
$a->caption->Set($data[$i][$csimpos-1]);

        
// Check if this activity should have a CSIM target ?
        
if( !empty($data[$i][$csimpos]) ) {
        
$a->SetCSIMTarget($data[$i][$csimpos]);
        
$a->SetCSIMAlt($data[$i][$csimpos+1]);
        }
        if( !empty(
$data[$i][$csimpos+2]) ) {
        
$a->title->SetCSIMTarget($data[$i][$csimpos+2]);
        
$a->title->SetCSIMAlt($data[$i][$csimpos+3]);
        }

        
$this->Add($a);
    }
    }

    
    
// Set what headers should be shown
    
function ShowHeaders($aFlg) {
    
$this->scale->ShowHeaders($aFlg);
    }
    
    
// Specify the fraction of the font height that should be added 
    // as vertical margin
    
function SetLabelVMarginFactor($aVal) {
    
$this->iLabelVMarginFactor $aVal;
    }

    
// Synonym to the method above
    
function SetVMarginFactor($aVal) {
    
$this->iLabelVMarginFactor $aVal;
    }
    
    
    
// Add a new Gantt object
    
function Add($aObject) {
    if( 
is_array($aObject) && count($aObject) > ) {
        
$cl $aObject[0];
        if( 
class_exists('IconPlot',false) && ($cl instanceof IconPlot) ) {
        
$this->AddIcon($aObject);
        }
        else {
        
$n count($aObject);
        for(
$i=0$i $n; ++$i)
            
$this->iObj[] = $aObject[$i];
        }
    }
    else {
        if( 
class_exists('IconPlot',false) && ($aObject instanceof IconPlot) ) {
        
$this->AddIcon($aObject);
        }
        else {        
        
$this->iObj[] = $aObject;
        }
    }
    }

    
// Override inherit method from Graph and give a warning message
    
function SetScale($aAxisType,$aYMin=1,$aYMax=1,$aXMin=1,$aXMax=1) {
    
JpGraphError::RaiseL(6005);
//("SetScale() is not meaningfull with Gantt charts.");
    
}

    
// Specify the date range for Gantt graphs (if this is not set it will be
    // automtically determined from the input data)
    
function SetDateRange($aStart,$aEnd) {
    
// Adjust the start and end so that the indicate the
    // begining and end of respective start and end days
    
if( strpos($aStart,':') === false )
        
$aStart date('Y-m-d 00:00',strtotime($aStart));
    if( 
strpos($aEnd,':') === false )
        
$aEnd date('Y-m-d 23:59',strtotime($aEnd));
    
$this->scale->SetRange($aStart,$aEnd);
    }
    
    
// Get the maximum width of the activity titles columns for the bars
    // The name is lightly misleading since we from now on can have
    // multiple columns in the label section. When this was first written
    // it only supported a single label, hence the name.
    
function GetMaxLabelWidth() {
    
$m=10;
    if( 
$this->iObj != null ) {
        
$marg $this->scale->actinfo->iLeftColMargin+$this->scale->actinfo->iRightColMargin;
         
$n count($this->iObj);
         for(
$i=0$i $n; ++$i) {
        if( !empty(
$this->iObj[$i]->title) ) {
            if( 
$this->iObj[$i]->title->HasTabs() ) {
            list(
$tot,$w) = $this->iObj[$i]->title->GetWidth($this->img,true);
            
$m=max($m,$tot);
            }
            else 
            
$m=max($m,$this->iObj[$i]->title->GetWidth($this->img