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