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