Subversion Repositories Applications.papyrus

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2150 mathias 1
<?php
2
/*=======================================================================
3
// File:	JPGRAPH_GANTT.PHP
4
// Description:	JpGraph Gantt plot extension
5
// Created: 	2001-11-12
6
// Author:	Johan Persson (johanp@aditus.nu)
7
// Ver:		$Id: jpgraph_gantt.php,v 1.1 2004/06/15 10:13:19 jpm Exp $
8
//
9
// License:	This code is released under QPL
10
// Copyright (c) 2002 Johan Persson
11
//========================================================================
12
*/
13
 
14
// Scale Header types
15
DEFINE("GANTT_HDAY",1);
16
DEFINE("GANTT_HWEEK",2);
17
DEFINE("GANTT_HMONTH",4);
18
DEFINE("GANTT_HYEAR",8);
19
 
20
// Bar patterns
21
DEFINE("GANTT_RDIAG",BAND_RDIAG);	// Right diagonal lines
22
DEFINE("GANTT_LDIAG",BAND_LDIAG); // Left diagonal lines
23
DEFINE("GANTT_SOLID",BAND_SOLID); // Solid one color
24
DEFINE("GANTT_VLINE",BAND_VLINE); // Vertical lines
25
DEFINE("GANTT_HLINE",BAND_HLINE);  // Horizontal lines
26
DEFINE("GANTT_3DPLANE",BAND_3DPLANE);  // "3D" Plane
27
DEFINE("GANTT_HVCROSS",BAND_HVCROSS);  // Vertical/Hor crosses
28
DEFINE("GANTT_DIAGCROSS",BAND_DIAGCROSS); // Diagonal crosses
29
 
30
// Conversion constant
31
DEFINE("SECPERDAY",3600*24);
32
 
33
// Locales. ONLY KEPT FOR BACKWARDS COMPATIBILITY
34
// You should use the proper locale strings directly
35
// from now on.
36
DEFINE("LOCALE_EN","en_UK");
37
DEFINE("LOCALE_SV","sv_SE");
38
 
39
// Layout of bars
40
DEFINE("GANTT_EVEN",1);
41
DEFINE("GANTT_FROMTOP",2);
42
 
43
// Styles for week header
44
DEFINE("WEEKSTYLE_WNBR",0);
45
DEFINE("WEEKSTYLE_FIRSTDAY",1);
46
DEFINE("WEEKSTYLE_FIRSTDAY2",2);
47
DEFINE("WEEKSTYLE_FIRSTDAYWNBR",3);
48
DEFINE("WEEKSTYLE_FIRSTDAY2WNBR",4);
49
 
50
// Styles for month header
51
DEFINE("MONTHSTYLE_SHORTNAME",0);
52
DEFINE("MONTHSTYLE_LONGNAME",1);
53
DEFINE("MONTHSTYLE_LONGNAMEYEAR2",2);
54
DEFINE("MONTHSTYLE_SHORTNAMEYEAR2",3);
55
DEFINE("MONTHSTYLE_LONGNAMEYEAR4",4);
56
DEFINE("MONTHSTYLE_SHORTNAMEYEAR4",5);
57
 
58
 
59
// Types of constrain links
60
DEFINE('CONSTRAIN_STARTSTART',0);
61
DEFINE('CONSTRAIN_STARTEND',1);
62
DEFINE('CONSTRAIN_ENDSTART',2);
63
DEFINE('CONSTRAIN_ENDEND',3);
64
 
65
// Arrow direction for constrain links
66
DEFINE('ARROW_DOWN',0);
67
DEFINE('ARROW_UP',1);
68
DEFINE('ARROW_LEFT',2);
69
DEFINE('ARROW_RIGHT',3);
70
 
71
// Arrow type for constrain type
72
DEFINE('ARROWT_SOLID',0);
73
DEFINE('ARROWT_OPEN',1);
74
 
75
// Arrow size for constrain lines
76
DEFINE('ARROW_S1',0);
77
DEFINE('ARROW_S2',1);
78
DEFINE('ARROW_S3',2);
79
DEFINE('ARROW_S4',3);
80
DEFINE('ARROW_S5',4);
81
 
82
// Activity types for use with utility method CreateSimple()
83
DEFINE('ACTYPE_NORMAL',0);
84
DEFINE('ACTYPE_GROUP',1);
85
DEFINE('ACTYPE_MILESTONE',2);
86
 
87
 
88
//===================================================
89
// CLASS GanttGraph
90
// Description: Main class to handle gantt graphs
91
//===================================================
92
class 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
//===================================================
562
class 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
//===================================================
677
class 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
//===================================================
761
class 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
//===================================================
1408
class 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
//===================================================
1484
class 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
//===================================================
1514
class 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
//===================================================
1733
class 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
 
1811
class 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
//===================================================
1860
class 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
 
1930
class 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
?>