1########################################################################## 2# 3# File: Project/Gantt.pm 4# 5# Author: Alexander Westholm 6# 7# Purpose: This object represents a Project within this Gantt chart 8# module. It can also recursively represent a sub-project. 9# It provides methods for drawing the chart. This is also 10# the location of the User Documentation. 11# 12# Client: CPAN 13# 14# CVS: $Id: Gantt.pm,v 1.15 2004/08/03 17:58:12 awestholm Exp $ 15# 16########################################################################## 17=head1 NAME 18 19Project::Gantt - Create Gantt charts to manage project scheduling 20 21=head1 SYNOPSIS 22 23 #!/usr/bin/perl -w 24 # a fun, imaginary wednesday 25 use strict; 26 use Project::Gantt; 27 use Project::Gantt::Skin; 28 29 my $skin= new Project::Gantt::Skin( 30 doTitle => 0); 31 32 my $day = new Project::Gantt( 33 file => 'hourly.png', 34 skin => $skin, 35 mode => 'hours', 36 description => 'A day in the life'); 37 38 my $al = $day->addResource( 39 name => 'Alex'); 40 41 $day->addTask( 42 description => 'Finish sleep', 43 resource => $al, 44 start => '2004-07-21 00:00:00', 45 end => '2004-07-21 08:30:00'); 46 47 $day->addTask( 48 description => 'Breakfast/Wakeup', 49 resource => $al, 50 start => '2004-07-21 08:30:00', 51 end => '2004-07-21 10:00:00'); 52 53 my $sub = $day->addSubProject( 54 description => 'Important Stuff'); 55 $sub->addTask( 56 description => 'Contemplate my navel', 57 resource => $al, 58 start => '2004-07-21 10:00:00', 59 end => '2004-07-21 11:00:00'); 60 61 $day->addTask( 62 description => 'Lunch', 63 resource => $al, 64 start => '2004-07-21 11:00:00', 65 end => '2004-07-21 12:30:00'); 66 $sub->addTask( 67 description => 'Wonder about life', 68 resource => $al, 69 start => '2004-07-21 11:00:00', 70 end => '2004-07-21 11:22:00'); 71 72 $day->addTask( 73 description => 'Code for a while', 74 resource => $al, 75 start => '2004-07-21 12:30:00', 76 end => '2004-07-21 17:00:00'); 77 78 $day->addTask( 79 description => 'Sail', 80 resource => $al, 81 start => '2004-07-21 17:00:00', 82 end => '2004-07-21 20:30:00'); 83 $day->display(); 84 85=head1 DESCRIPTION 86 87B<Project::Gantt> provides the ability to easily draw Gantt charts for managing the schedules of projects and many other things. Gantt charts provide a simple, easy to comprehend visual representation of a schedule. 88 89The code above creates a simple chart to display the hour-by-hour breakdown of a sample day. Notice the B<Project::Gantt::Skin> object in use. This allows the look and feel of a Gantt chart to be customized. Also note that tasks are divided into two main categories: those that fall directly under the project, and those which are members of the subproject B<"Important Stuff">. Note also that the chart itself will be written to a file in the current working directory called B<"hourly.png">. This filename attribute may be set to something such as B<"png:-"> to send output directly to B<STDOUT>. 90 91As can be seen from the example, the methods that will be called by a user of this module include: I<addResource>,I<addTask>, I<addSubProject>, and I<display>. The names of these methods suggest their purpose, but they will be further explained. 92 93=over 94 95=item new() 96 97I<new> takes the following parameters: the skin object in use (if not using the default), the filename to use when writing the chart (use B<"png:-"> to write to B<STDOUT>), an overall description for the chart, and the time mode for output. The filename and description are fairly self explanatory. The B<Project::Gantt::Skin> object will be covered later in this document. The time mode selects which unit of time to use when displaying the chart. This unit can be one of the following: B<hours>, B<days>, and B<months>. Note that when using the months mode, small overflows of pixels may be present (i.e., one pixel more than should be). Normally these are not noticeable. They are a result of the calculation used to determine how many pixels a timespan should fill when using month more. This is because of the discrepancies between days in various months. If swim lanes are not in use (see the section on B<Project::Gantt::Skin>), these errors are unnoticeable. 98 99=item addResource() 100 101I<addResource> really only requires a B<name> parameter at this point. The method will accept whatever you give it, but currently only the B<name> parameter has any impact on the resulting chart. 102 103=item addTask() 104 105I<addTask> attaches a B<Project::Gantt::Task> object to the B<Project::Gantt> instance that called it. The calling instance may be the root project, or any subproject. The task will be anchored directly underneath it. Parameters that must be passed to this method are as follows: a description of the task, the resource assigned to its undertaking, the starting date of the task and its end date. 106 107=item addSubProject() 108 109I<addSubProject> returns an instance of B<Project::Gantt> anchored underneath the instance that called it. Thanks to Peter Weatherdon, you can now create nested sub-projects using this method on an existing sub-project object. This reference may then be used to call I<addTask> and create a container relationship with B<Project::Gantt::Task> objects. Currently, the only necesarry parameter is a description of the sub-project. 110 111=item display() 112 113Oddly enough, I<display> writes the chart to a file. 114 115=head2 SKIN OBJECTS 116 117B<Project::Gantt::Skin> objects allow users to customize the color scheme of a chart. The skin object is passed to B<Project::Gantt> during construction. All aspects of the skin are set during its construction as well. The following facets of the chart may be modified: 118 119=over 120 121=item primaryText 122 123B<primaryText> controls the font fill color for all but the sub-project description. The default is black. 124 125=item secondaryText 126 127B<secondaryText> controls the font fill for sub-projects. It defaults to a grey color (#969696). 128 129=item primaryFill 130 131B<primaryFill> is the color that fills the information boxes for rows representing tasks. The default is a blue color (#c4dbed). 132 133=item secondaryFill 134 135B<secondaryFill> is the color used by sub-project rows for informational boxes, as well as the fill for the calendar header. The default is a grey color (#e5e5e5). 136 137=item infoStroke 138 139B<infoStroke> is the stroke color for the informational boxes. This defaults to black. 140 141=item containerStroke 142 143B<containerStroke> is the stroke color for sub-projects on the chart. This defaults to black. 144 145=item containerFill 146 147B<containerFill> is the fill color for sub-project items. This defaults to grey (as defined by B<Image::Magick>). 148 149=item itemFill 150 151B<itemFill> is the fill color for task items on the chart. This defaults to blue. Note that there is no stroke color for tasks (it is set to the fill). 152 153=item background 154 155B<background> is quite obviously the background color. This defaults to white. 156 157=item font 158 159B<font> is the name of the font file as it is passed to B<Image::Magick>. See the docs for that module for more information. The default value for this property is determined by searching @INC for the directory of your Project::Gantt installation, and is set to the copy of Bitstream Vera included in the distribution. 160 161=item doTitle 162 163B<doTitle> is a boolean that determines whether the title of the chart is drawn on it. 164 165=item doSwimLanes 166 167B<doSwimLanes> is a boolean that determines whether lines should be drawn seperating each time interval from the header to the end of the graph. This makes it easy to see the exact values. 168 169=back 170 171=head1 AUTHOR 172 173Alexander Christian Westholm, E<lt>awestholm AT verizon.netE<gt> 174 175=head1 CHANGES 176 177August, 2004: Original Version 178 179January 2005: Modifications made by Peter Weatherdon (peter.weatherdon AT us.cd-adapco.com), including various bug fixes, and nested sub-projects. 180 181=head1 SEE ALSO 182 183L<Image::Magick>, L<Class::Date> 184 185=head1 COPYRIGHT 186 187Copyright 2005, Alexander Christian Westholm. 188 189This library is free software; you can redistribute it and/or 190modify it under the same terms as Perl itself. 191 192=cut 193 194package Project::Gantt; 195use strict; 196use warnings; 197use Project::Gantt::Resource; 198use Project::Gantt::Task; 199use Project::Gantt::ImageWriter; 200use Project::Gantt::DateUtils qw[:compare]; 201use Project::Gantt::Skin; 202 203our $VERSION = '1.01'; 204 205#params: 206#name 207sub new { 208 my $class = shift; 209 my %args = @_; 210 if(not $args{description}){ 211 die "Must provide description of project!"; 212 } 213 if(not $args{file} and not $args{parent}){ 214 die "Must provide filename for output!"; 215 } 216 $args{mode} = 'days' if not $args{mode}; 217 $args{tasks} = []; 218 $args{subprojs} = []; 219 $args{resources}= {}; 220 $args{parent} = 0 if not defined $args{parent}; 221 $args{subNodes} = 0; 222 # startDate must be now because comparison against 223 # a value that's better will cause handleDates to 224 # still get called, resetting the startDate to 0, if 225 # startDate is allowed to stay 0 226 $args{startDate}= -1; 227 $args{endDate} = 0; 228 $args{skin} = $args{skin} || new Project::Gantt::Skin(); 229 my $me = bless \%args, $class; 230 return $me; 231} 232 233sub addResource { 234 my $me = shift; 235 my %opts= @_; 236 my $res = new Project::Gantt::Resource(%opts); 237 $me->{resources}->{$opts{name}} = $res; 238 return $res; 239} 240 241sub addTask { 242 my $me = shift; 243 my %opts= @_; 244 die "Must provide resource for task!" if not $opts{resource}; 245 die "Must provide start date for task!" if not $opts{start}; 246 die "Must provide end date for task!" if not $opts{end}; 247 if(not $me->{parent}){ 248 if(not $me->{resources}->{$opts{resource}->getName()}){ 249 die "Mis-assignment of task resources!!"; 250 } 251 }else{ 252 if(not $me->{parent}->{resources}->{ 253 $opts{resource}->getName()}){ 254 255 #die "Mis-assignment of task resources!"; 256 } 257 } 258 $opts{start} .= " 09:00:00" if $opts{start} !~ /\:/; 259 $opts{end} .= " 17:00:00" if $opts{end} !~ /\:/; 260 # handle addition to sub-project 261 my $tsk = new Project::Gantt::Task(%opts); 262 $tsk->setParent($me); 263 $tsk->addResource($opts{resource}); 264 $tsk->_handleDates(); 265 push @{$me->{tasks}}, $tsk; 266 $me->incrNodeCount(); 267} 268 269# allow resource to be assignmed for every sub-task 270sub addSubProject { 271 my $me = shift; 272 my %opts= @_; 273 $opts{parent} = $me; 274 my $prj = new Project::Gantt(%opts); 275 push @{$me->{subprojs}}, $prj; 276 $me->incrNodeCount(); 277 return $prj; 278} 279 280sub display { 281 my $me = shift; 282 if($me->{parent}){ 283 die "Must not call display on sub-project!"; 284 } 285 my $wtr = new Project::Gantt::ImageWriter( 286 root => $me, 287 skin => $me->{skin}, 288 mode => $me->{mode}); 289 $wtr->display($me->{file}); 290} 291 292sub _display { 293 my $me = shift; 294 my $start = $me->{startDate}; 295 my $end = $me->{endDate}; 296 if($me->{parent}){ 297 # print container bar 298 print "SUBPROJECT: $me->{description}\n"; 299 }else{ 300 # print header 301 print "MASTER PROJECT: $me->{description}\n"; 302 } 303 print "RUNS FROM: $start to $end\n"; 304 305 for my $tsk (@{$me->{tasks}}){ 306 print "TASK: ".$tsk->getDescription()."\n"; 307 print "TASK START: ".$tsk->getStartDate()."\n"; 308 print "TASK END: ".$tsk->getEndDate()."\n"; 309 } 310 311 for my $sub (@{$me->{subprojs}}){ 312 $sub->display(); 313 } 314} 315 316sub getResources { 0 } 317 318sub getTasks { 319 my $me = shift; 320 return @{$me->{tasks}}; 321} 322 323sub getSubProjs { 324 my $me = shift; 325 return @{$me->{subprojs}}; 326} 327 328sub _handleDates { 329 my $me = shift; 330 my $prnt= $me->{parent}; 331 return if not $prnt; 332 my $oStrt = $prnt->getStartDate() || -1; 333 my $oEnd = $prnt->getEndDate() || 0; 334 if(($oStrt > $me->{startDate}) or ($oStrt == -1)){ 335 $prnt->getStartDate($me->{startDate}); 336 } 337 338 # Peter Weatherdon Jan 25, 2005 339 # Added check for $oEnd == 0 340 if(($oEnd < $me->{endDate}) or ($oEnd == 0)){ 341 $prnt->getEndDate($me->{endDate}); 342 } 343 344 # Peter Weatherdon: Jan 19, 2005 345 # Recursively call handleDates to support nested sub-projects 346 $prnt->_handleDates(); 347} 348 349sub setParent { 350 my $me = shift; 351 $me->{parent} = shift; 352} 353 354sub getNodeCount { 355 my $me = shift; 356 return $me->{subNodes}; 357} 358 359sub incrNodeCount { 360 my $me = shift; 361 if(not $me->{parent}){ 362 $me->{subNodes}++; 363 }else{ 364 $me->{parent}->incrNodeCount(); 365 } 366} 367 368sub getStartDate { 369 my $me = shift; 370 my $val = shift; 371 $me->{startDate} = $val if defined $val; 372 return $me->{startDate}; 373} 374 375sub getEndDate { 376 my $me = shift; 377 my $val = shift; 378 $me->{endDate} = $val if defined $val; 379 return $me->{endDate}; 380} 381 382sub getDescription { 383 my $me = shift; 384 return $me->{description}; 385} 386 387sub timeSpan { 388 my $me = shift; 389 my $span= $me->{mode}; 390 my $copyEnd = $me->{endDate}->clone(); 391 my $copyStr = $me->{startDate}->clone(); 392 if($span eq 'days'){ 393 return daysBetween($copyStr, $copyEnd); 394 }elsif($span eq 'months'){ 395 return monthsBetween($copyStr, $copyEnd); 396 }elsif($span eq 'hours'){ 397 return hoursBetween($copyStr, $copyEnd); 398 }else{ 399 die 'Bad argument to timeSpan!'; 400 } 401} 402 4031; 404