source: trunk/client/modules/Elezioni/grafici-old/jpgraph_gantt.php@ 2

Last change on this file since 2 was 2, checked in by root, 15 years ago

importo il progetto

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