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.
The height in pixels of a horizontal scrollbar on an HTML page. This value should be large enough to work for all browsers.
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/taskjuggler/reports/GanttChart.rb, line 46 46: def initialize(now, weekStartsMonday, table = nil) 47: # The start and end dates of the reported interval. 48: @start = nil 49: @end = nil 50: @now = now 51: @table = table 52: 53: # This defines the possible horizontal scales that the Gantt chart can 54: # have. The scales differ in their resolution and the amount of detail 55: # that is displayed. A scale is defined by its name. The _name_ must be 56: # unique and can be used to select the scale. The _stepSize_ defines the 57: # width of a scale step in pixels. The _stepsToFunc_ is a TjTime method 58: # that determines the number of steps between 2 dates. _minTimeOff_ 59: # defines the minimum required length of an time-off interval that is 60: # displayed in this scale. 61: @@scales = [ 62: { 'name' => 'hour', 'stepSize' => 20, 'stepsToFunc' => :hoursTo, 63: 'minTimeOff' => 5 * 60 }, 64: { 'name' => 'day', 'stepSize' => 20, 'stepsToFunc' => :daysTo, 65: 'minTimeOff' => 6 * 60 * 60 }, 66: { 'name' => 'week', 'stepSize' => 20, 'stepsToFunc' => :weeksTo, 67: 'minTimeOff' => 24 * 60 * 60 }, 68: { 'name' => 'month', 'stepSize' => 35, 'stepsToFunc' => :monthsTo, 69: 'minTimeOff' => 5 * 24 * 60 * 60 }, 70: { 'name' => 'quarter', 'stepSize' => 28, 'stepsToFunc' => :quartersTo, 71: 'minTimeOff' => 1 }, 72: { 'name' => 'year', 'stepSize' => 20, 'stepsToFunc' => :yearsTo, 73: 'minTimeOff' => 1 } 74: ] 75: # This points to one of the scales above and marks the current scale. 76: @scale = nil 77: # The height of the chart (without the header) 78: @height = 0 79: # The width of the chart in pixels. 80: @width = 0 81: # The width of the view that the chart is presented in. If it's nil, the 82: # view will be adapted to the width of the chart. 83: @viewWidth = nil 84: # True of the week starts on a Monday. 85: @weekStartsMonday = weekStartsMonday 86: 87: # Reference to the GanttHeader object that models the chart header. 88: @header = nil 89: # The GanttLine objects that model the lines of the chart. 90: @lines = [] 91: # The router for dependency lines. 92: @router = nil 93: # This dictionary stores primary task lines indexed by their task. To 94: # handle multiple scenarios, the dictionary stored the lines in an Array. 95: # This is used to generate dependency arrows. 96: @tasks = {} 97: # This is a list of the dependency lines. Each entry is an Array of [x, y] 98: # coordinate pairs. 99: @depArrows = [] 100: # This is the list of arrow heads used for the dependency arrows. It 101: # contains an Array of [ x, y ] coordinates that mark the tip of the 102: # arrow. 103: @arrowHeads = [] 104: end
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/taskjuggler/reports/GanttChart.rb, line 108 108: def addTask(task, line) 109: if @tasks.include?(task) 110: # Append the line to the existing lines. 111: @tasks[task] << line 112: else 113: # Add a new Array for this tasks and store the first line. 114: @tasks[task] = [ line ] 115: end 116: end
Utility function that convers a date to the corresponding X-position in the Gantt chart.
# File lib/taskjuggler/reports/GanttChart.rb, line 209 209: def dateToX(date) 210: ((@width / (@end - @start)) * (date - @start)).to_i 211: end
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/taskjuggler/reports/GanttChart.rb, line 130 130: def generateByScale(periodStart, periodEnd, scaleName) 131: @start = periodStart 132: @end = periodEnd 133: @scale = scaleByName(scaleName) 134: @stepSize = @scale['stepSize'] 135: steps = @start.send(@scale['stepsToFunc'], @end) 136: @width = @stepSize * steps 137: 138: @header = GanttHeader.new(self) 139: end
# File lib/taskjuggler/reports/GanttChart.rb, line 119 119: def generateByWidth(periodStart, periodEnd, width) 120: @start = periodStart 121: @end = periodEnd 122: @width = width 123: # TODO 124: end
Returns true if the chart includes a scrollbar.
# File lib/taskjuggler/reports/GanttChart.rb, line 223 223: def hasScrollbar? 224: @viewWidth && (@viewWidth < @width) 225: end
This is a noop function.
# File lib/taskjuggler/reports/GanttChart.rb, line 202 202: def to_csv(csv, startColumn) 203: # Can't put a Gantt chart into a CSV file. 204: 0 205: end
Convert the chart into an HTML representation.
# File lib/taskjuggler/reports/GanttChart.rb, line 142 142: def to_html 143: completeChart 144: 145: # The chart is rendered into a cell that extends over the full height of 146: # the table. No other cells for this column will be generated. In case 147: # there is a scrollbar, the table will have an extra line to hold the 148: # scrollbar. 149: td = XMLElement.new('td', 150: 'rowspan' => "#{2 + @lines.length + (hasScrollbar? ? 1 : 0)}", 151: 'style' => 'padding:0px; vertical-align:top;') 152: # Now we generate two 'div's nested into each other. The first div is the 153: # view. It may contain a scrollbar if the second div is wider than the 154: # first one. In case we need a scrollbar The outer div is 155: # SCROLLBARHEIGHT pixels heigher to hold the scrollbar. Unfortunately 156: # this must be a hardcoded value even though the height of the scrollbar 157: # varies from system to system. This value should be good enough for 158: # most systems. 159: td << (scrollDiv = XMLElement.new('div', 'class' => 'tabback', 160: 'style' => 'position:relative; ' + 161: "overflow:auto; " + 162: "width:#{hasScrollbar? ? @viewWidth : @width}px; " + 163: "height:#{@height + 164: (hasScrollbar? ? SCROLLBARHEIGHT : 0)}px;")) 165: scrollDiv << (div = XMLElement.new('div', 166: 'style' => "margin:0px; padding:0px; " + 167: "position:absolute; overflow:hidden; " + 168: "top:0px; left:0px; " + 169: "width:#{@width}px; " + 170: "height:#{@height}px; " + 171: "font-size:10px;")) 172: # Add the header. 173: div << @header.to_html 174: # These are the lines of the chart. 175: @lines.each do |line| 176: div << line.to_html 177: end 178: 179: # This is used for debugging and testing only. 180: #div << @router.to_html 181: 182: # Render the dependency lines. 183: @depArrows.each do |arrow| 184: xx = yy = nil 185: arrow.each do |x, y| 186: if xx 187: div << lineToHTML(xx, yy, x, y, 'depline') 188: end 189: xx = x 190: yy = y 191: end 192: end 193: # And the corresponsing arrow heads. 194: @arrowHeads.each do |x, y| 195: div << arrowHeadToHTML(x, y) 196: end 197: 198: td 199: end
Calculate the overall height of the chart and generate dependency arrows.
# File lib/taskjuggler/reports/GanttChart.rb, line 239 239: def completeChart 240: @lines.each do |line| 241: @height = line.y + line.height if line.y + line.height > @height 242: end 243: 244: # To layout the dependency lines, we use a GanttRouter. We only provide 245: # the start and end coordinates of each line and it will do the layout 246: # and routing. 247: @router = GanttRouter.new(@width, @height) 248: 249: # We don't want horizontal lines to cross the task bars. So we block 250: # these chart zones. Milestones should not be crossed in any direction. 251: @lines.each do |line| 252: line.addBlockedZones(@router) 253: end 254: 255: # Also protect the current date line from other vertical lines. 256: @router.addZone(@header.nowLineX - 1, 0, 3, @height - 1, false, true) 257: 258: # Generate the dependency arrows for all visible tasks. 259: @tasks.each do |task, lines| 260: generateDepLines(task, lines) 261: end 262: 263: # Make sure we have exactly one arrow head for each line end point even 264: # if the point is used by multiple lines. 265: @depArrows.each do |line| 266: endPoint = line.last 267: @arrowHeads << endPoint unless @arrowHeads.include?(endPoint) 268: end 269: end
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/taskjuggler/reports/GanttChart.rb, line 275 275: def generateDepLines(task, lines) 276: # Since we need the line and the index we use an index iterator. 277: lines.length.times do |lineIndex| 278: line = lines[lineIndex] 279: scenarioIdx = line.query.scenarioIdx 280: 281: # Generate the dependencies on the start of the task. 282: generateTaskDepLines('startsuccs', task, scenarioIdx, lineIndex, 283: *line.getTask.startDepLineStart) 284: 285: # Generate the dependencies on the end of the task. 286: generateTaskDepLines('endsuccs', task, scenarioIdx, lineIndex, 287: *line.getTask.endDepLineStart) 288: end 289: end
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/taskjuggler/reports/GanttChart.rb, line 296 296: def generateTaskDepLines(kind, task, scenarioIdx, lineIndex, startX, startY) 297: # This is an Array that holds 4 values for 298: # each entry: The x and y coordinates for start and end points. 299: touples = [] 300: task[kind, scenarioIdx].each do |t, onEnd| 301: # Skip inherited dependencies and tasks that are not included in the 302: # chart. 303: if (t.parent && 304: task.hasDependency?(scenarioIdx, kind, t.parent, onEnd)) || 305: !@tasks.include?(t) 306: next 307: end 308: endX, endY = @tasks[t][lineIndex].getTask.send( 309: onEnd ? :endDepLineEnd : :startDepLineEnd) 310: touples << [ startX, startY, endX, endY ] 311: end 312: @depArrows += @router.routeLines(touples) unless touples.empty?() 313: end
Find the scale with the name name and return a reference to the scale. If nothing is round an exception is raised.
# File lib/taskjuggler/reports/GanttChart.rb, line 231 231: def scaleByName(name) 232: @@scales.each do |scale| 233: return scale if scale['name'] == name 234: end 235: raise "Unknown scale #{name}" 236: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.