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