source: trunk/client/modules/Elezioni/grafici/jpgraph_gantt.php

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