Parent

Included Modules

Class Index [+]

Quicksearch

TaskJuggler::GanttChart

This class represents an abstract (output format independent) Gantt chart. It provides generator functions that can transform the abstract form into formats such as HTML or SVG. The appearance of the chart depend on 3 variable: the report period, the geometrical width and the scale. The report period is always provided by the user. In addition the width or the scale can be provided. The non-provided value will then be calculated. So after the object has been created, the user must call generateByWidth or generateByResolution.

Constants

SCROLLBARHEIGHT

The height in pixels of a horizontal scrollbar on an HTML page. This value should be large enough to work for all browsers.

Attributes

start[R]
end[R]
now[R]
weekStartsMonday[R]
header[R]
width[R]
scale[R]
scales[R]
table[R]
viewWidth[W]

Public Class Methods

new(now, weekStartsMonday, table = nil) click to toggle source

Create the GanttChart object, but don’t do much right now. We still need more information about the chart before we can actually generate it. now is the date that should be used as current date. weekStartsMonday is true if the weeks should start on Mondays instead of Sundays. table is a reference to the TableReport that the chart is part of.

     # File lib/reports/GanttChart.rb, line 45
 45:     def initialize(now, weekStartsMonday, table = nil)
 46:       # The start and end dates of the reported interval.
 47:       @start = nil
 48:       @end = nil
 49:       @now = now
 50:       @table = table
 51: 
 52:       # This defines the possible horizontal scales that the Gantt chart can
 53:       # have. The scales differ in their resolution and the amount of detail
 54:       # that is displayed. A scale is defined by its name. The _name_ must be
 55:       # unique and can be used to select the scale. The _stepSize_ defines the
 56:       # width of a scale step in pixels. The _stepsToFunc_ is a TjTime method
 57:       # that determines the number of steps between 2 dates. _minTimeOff_
 58:       # defines the minimum required length of an time-off interval that is
 59:       # displayed in this scale.
 60:       @@scales = [
 61:         { 'name' => 'hour', 'stepSize' => 20, 'stepsToFunc' => :hoursTo,
 62:           'minTimeOff' => 5 * 60 },
 63:         { 'name' => 'day', 'stepSize' => 20, 'stepsToFunc' => :daysTo,
 64:           'minTimeOff' => 6 * 60 * 60 },
 65:         { 'name' => 'week', 'stepSize' => 20, 'stepsToFunc' => :weeksTo,
 66:           'minTimeOff' => 24 * 60 * 60 },
 67:         { 'name' => 'month', 'stepSize' => 35, 'stepsToFunc' => :monthsTo,
 68:           'minTimeOff' => 5 * 24 * 60 * 60 },
 69:         { 'name' => 'quarter', 'stepSize' => 28, 'stepsToFunc' => :quartersTo,
 70:           'minTimeOff' => 1 },
 71:         { 'name' => 'year', 'stepSize' => 20, 'stepsToFunc' => :yearsTo,
 72:           'minTimeOff' => 1 }
 73:       ]
 74:       # This points to one of the scales above and marks the current scale.
 75:       @scale = nil
 76:       # The height of the chart (without the header)
 77:       @height = 0
 78:       # The width of the chart in pixels.
 79:       @width = 0
 80:       # The width of the view that the chart is presented in. If it's nil, the
 81:       # view will be adapted to the width of the chart.
 82:       @viewWidth = nil
 83:       # True of the week starts on a Monday.
 84:       @weekStartsMonday = weekStartsMonday
 85: 
 86:       # Reference to the GanttHeader object that models the chart header.
 87:       @header = nil
 88:       # The GanttLine objects that model the lines of the chart.
 89:       @lines = []
 90:       # The router for dependency lines.
 91:       @router = nil
 92:       # This dictionary stores primary task lines indexed by their task. To
 93:       # handle multiple scenarios, the dictionary stored the lines in an Array.
 94:       # This is used to generate dependency arrows.
 95:       @tasks = {}
 96:       # This is a list of the dependency lines. Each entry is an Array of [x, y]
 97:       # coordinate pairs.
 98:       @depArrows = []
 99:       # This is the list of arrow heads used for the dependency arrows. It
100:       # contains an Array of [ x, y ] coordinates that mark the tip of the
101:       # arrow.
102:       @arrowHeads = []
103:     end

Public Instance Methods

addTask(task, line) click to toggle source

Add a primary tasks line to the dictonary. task is a reference to the Task object and line is the corresponding primary ReportTableLine.

     # File lib/reports/GanttChart.rb, line 107
107:     def addTask(task, line)
108:       if @tasks.include?(task)
109:         # Append the line to the existing lines.
110:         @tasks[task] << line
111:       else
112:         # Add a new Array for this tasks and store the first line.
113:         @tasks[task] = [ line ]
114:       end
115:     end
dateToX(date) click to toggle source

Utility function that convers a date to the corresponding X-position in the Gantt chart.

     # File lib/reports/GanttChart.rb, line 208
208:     def dateToX(date)
209:       (@width / (@end - @start)) * (date - @start)
210:     end
generateByScale(periodStart, periodEnd, scaleName) click to toggle source

Generate the actual chart data based on the report interval specified by periodStart and periodEnd as well as the name of the requested scale to be used. This function (or generateByWidth) must be called before any GanttLine objects are created for this chart.

     # File lib/reports/GanttChart.rb, line 129
129:     def generateByScale(periodStart, periodEnd, scaleName)
130:       @start = periodStart
131:       @end = periodEnd
132:       @scale = scaleByName(scaleName)
133:       @stepSize = @scale['stepSize']
134:       steps = @start.send(@scale['stepsToFunc'], @end)
135:       @width = @stepSize * steps
136: 
137:       @header = GanttHeader.new(self)
138:     end
generateByWidth(periodStart, periodEnd, width) click to toggle source
     # File lib/reports/GanttChart.rb, line 118
118:     def generateByWidth(periodStart, periodEnd, width)
119:       @start = periodStart
120:       @end = periodEnd
121:       @width = width
122:       # TODO
123:     end
hasScrollbar?() click to toggle source

Returns true if the chart includes a scrollbar.

     # File lib/reports/GanttChart.rb, line 222
222:     def hasScrollbar?
223:       @viewWidth && (@viewWidth < @width)
224:     end
to_csv() click to toggle source

This is a noop function.

     # File lib/reports/GanttChart.rb, line 201
201:     def to_csv
202:       # Can't put a Gantt chart into a CSV file.
203:       ''
204:     end
to_html() click to toggle source

Convert the chart into an HTML representation.

     # File lib/reports/GanttChart.rb, line 141
141:     def to_html
142:       completeChart
143: 
144:       # The chart is rendered into a cell that extends over the full height of
145:       # the table. No other cells for this column will be generated. In case
146:       # there is a scrollbar, the table will have an extra line to hold the
147:       # scrollbar.
148:       td = XMLElement.new('td',
149:         'rowspan' => "#{2 + @lines.length + (hasScrollbar? ? 1 : 0)}",
150:         'style' => 'padding:0px; vertical-align:top;')
151:       # Now we generate two 'div's nested into each other. The first div is the
152:       # view. It may contain a scrollbar if the second div is wider than the
153:       # first one. In case we need a scrollbar The outer div is
154:       # SCROLLBARHEIGHT pixels heigher to hold the scrollbar. Unfortunately
155:       # this must be a hardcoded value even though the height of the scrollbar
156:       # varies from system to system. This value should be good enough for
157:       # most systems.
158:       td << (scrollDiv = XMLElement.new('div', 'class' => 'tabback',
159:         'style' => 'position:relative; ' +
160:                    "overflow:auto; " +
161:                    "width:#{hasScrollbar? ? @viewWidth : @width}px; " +
162:                    "height:#{@height +
163:                              (hasScrollbar? ? SCROLLBARHEIGHT : 0)}px;"))
164:       scrollDiv << (div = XMLElement.new('div',
165:         'style' => "margin:0px; padding:0px; " +
166:                    "position:absolute; overflow:hidden; " +
167:                    "top:0px; left:0px; " +
168:                    "width:#{@width}px; " +
169:                    "height:#{@height}px; " +
170:                    "font-size:10px;"))
171:       # Add the header.
172:       div << @header.to_html
173:       # These are the lines of the chart.
174:       @lines.each do |line|
175:         div << line.to_html
176:       end
177: 
178:       # This is used for debugging and testing only.
179:       #div << @router.to_html
180: 
181:       # Render the dependency lines.
182:       @depArrows.each do |arrow|
183:         xx = yy = nil
184:         arrow.each do |x, y|
185:           if xx
186:             div << lineToHTML(xx, yy, x, y, 'depline')
187:           end
188:           xx = x
189:           yy = y
190:         end
191:       end
192:       # And the corresponsing arrow heads.
193:       @arrowHeads.each do |x, y|
194:         div << arrowHeadToHTML(x, y)
195:       end
196: 
197:       td
198:     end

Private Instance Methods

collectAndSortArrows(kind, task, scenarioIdx, lineIndex, startX, startY) click to toggle source

Generate the dependencies on the start or end of the task depending on kind. Use ‘startsuccs’ for the start and ‘endsuccs’ for end. startX and startY are the graphic coordinates for the begin of the arrow line. task references the Task in question and scenarioIdx the scenario. lineIndex specifies the line number in the chart.

     # File lib/reports/GanttChart.rb, line 281
281:     def collectAndSortArrows(kind, task, scenarioIdx, lineIndex, startX, startY)
282:       # We need to sort the arrows. This is an Array that holds 6 values for
283:       # each entry: The x and y coordinates for start and end points, the
284:       # sinus value of the angle between a vertical and the line specified by
285:       # the points and the length of the line.
286:       touples = []
287:       task[kind, scenarioIdx].each do |t, onEnd|
288:         # Skip inherited dependencies and tasks that are not included in the
289:         # chart.
290:         if (t.parent &&
291:             task.hasDependency?(scenarioIdx, kind, t.parent, onEnd)) ||
292:            !@tasks.include?(t)
293:           next
294:         end
295:         endX, endY = @tasks[t][lineIndex].getTask.send(
296:           onEnd ? :endDepLineEnd : :startDepLineEnd)
297:         # To make sure that we minimize the crossings of arrows that
298:         # originate from the same position, we sort the arrows by the
299:         # smallest angle between the vertical line through the task end
300:         # and the line between the start and end of the arrow.
301:         oppLeg = endX - startX
302:         adjLeg = (startY - endY).abs
303:         hypothenuse = Math.sqrt(adjLeg ** 2 + oppLeg ** 2)
304:         # We can now calculate the sinus values of the angle between the
305:         # vertical and a line through the coordinates.
306:         touples << [ startX, startY, endX, endY,
307:                      (oppLeg / hypothenuse), hypothenuse ]
308:       end
309:       # We sort the arrows from small to a large angle. In case the angle is
310:       # identical, we use the length of the line as second criteria.
311:       touples.sort! { |t1, t2| t1[4] == t2[4] ? t1[5] <=> t2[5] :
312:                                                 t1[4] <=> t2[4] }
313:       touples.each do |t|
314:         routeArrow(*t[0, 4])
315:       end
316:     end
completeChart() click to toggle source

Calculate the overall height of the chart and generate dependency arrows.

     # File lib/reports/GanttChart.rb, line 238
238:     def completeChart
239:       @lines.each do |line|
240:         @height = line.y + line.height if line.y + line.height > @height
241:       end
242: 
243:       @router = GanttRouter.new(@width, @height)
244: 
245:       @lines.each do |line|
246:         line.addBlockedZones(@router)
247:       end
248: 
249:       @router.addZone(@header.nowLineX - 1, 0, 3, @height - 1, false, true)
250: 
251:       @tasks.each do |task, lines|
252:         generateDepArrows(task, lines)
253:       end
254:     end
generateDepArrows(task, lines) click to toggle source

Generate an output format independent description of the dependency lines for a specific task. lines is a list of GanttLines that the tasks are displayed on. Reports with multiple scenarios have multiple lines per task.

     # File lib/reports/GanttChart.rb, line 260
260:     def generateDepArrows(task, lines)
261:       # Since we need the line and the index we use an index iterator.
262:       lines.length.times do |lineIndex|
263:         line = lines[lineIndex]
264:         scenarioIdx = line.query.scenarioIdx
265: 
266:         # Generate the dependencies on the start of the task.
267:         collectAndSortArrows('startsuccs', task, scenarioIdx, lineIndex,
268:                              *line.getTask.startDepLineStart)
269: 
270:         # Generate the dependencies on the end of the task.
271:         collectAndSortArrows('endsuccs', task, scenarioIdx, lineIndex,
272:                              *line.getTask.endDepLineStart)
273:       end
274:     end
routeArrow(startX, startY, endX, endY) click to toggle source

Route the dependency lines from the start to the end point.

     # File lib/reports/GanttChart.rb, line 319
319:     def routeArrow(startX, startY, endX, endY)
320:       @depArrows << @router.route([startX, startY], [endX, endY])
321: 
322:       # It's enough to have only a single arrow drawn at the end point even if
323:       # it's the destination of multiple lines.
324:       @arrowHeads.each do |x, y|
325:         return if x == endX && y == endY
326:       end
327:       @arrowHeads << [ endX, endY ]
328:     end
scaleByName(name) click to toggle source

Find the scale with the name name and return a reference to the scale. If nothing is round an exception is raised.

     # File lib/reports/GanttChart.rb, line 230
230:     def scaleByName(name)
231:       @@scales.each do |scale|
232:         return scale if scale['name'] == name
233:       end
234:       raise "Unknown scale #{name}"
235:     end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.