class TaskJuggler::GanttLine

This class models the abstract (output independent) form of a line of a Gantt chart. Each line represents a property. Depending on the type of property and it's context (for nested properties) the content varies. Tasks (not nested) are represented as task bars or milestones. When nested into a resource they are represented as load stacks.

Attributes

height[R]
query[R]
y[R]

Public Class Methods

new(chart, query, y, height, lineIndex, tooltip) click to toggle source

Create a GanttLine object and generate the abstract representation.

# File lib/taskjuggler/reports/GanttLine.rb, line 34
def initialize(chart, query, y, height, lineIndex, tooltip)
  # A reference to the chart that the line belongs to.
  @chart = chart
  # Register the line with the chart.
  @chart.addLine(self)

  # The query is used to access the presented project data.
  @query = query
  # A CellSettingPatternList object to determine the tooltips for the
  # line's content.
  @tooltip = tooltip
  # The category determines the background color of the line.
  @category = nil
  # The y coordinate of the topmost pixel of this line.
  @y = y + chart.header.height + 1
  # The height of the line in screen pixels.
  @height = height
  # The index of the line in the chart. It starts with 0 and is
  # incremented for each line by one.
  @lineIndex = lineIndex
  # The x coordinates of the time-off zones. It's an Array of [ startX, endX
  # ] touples.
  @timeOffZones = []

  generate
end

Public Instance Methods

addBlockedZones(router) click to toggle source

Register the areas that dependency lines should not cross.

# File lib/taskjuggler/reports/GanttLine.rb, line 111
def addBlockedZones(router)
  @content.each do |c|
    c.addBlockedZones(router)
  end
end
getTask() click to toggle source

This function only works for primary task lines. It returns the generated intermediate object for that line.

# File lib/taskjuggler/reports/GanttLine.rb, line 102
def getTask
  if @content.length == 1
    @content[0]
  else
    nil
  end
end
to_html() click to toggle source

Convert the abstract representation of the GanttLine into HTML elements.

# File lib/taskjuggler/reports/GanttLine.rb, line 62
def to_html
  # The whole line is put in a 'div' section. All coordinates relative to
  # the top-left corner of this div. Elements that extend over the
  # boundaries of this div are cut off.
  div = XMLElement.new('div', 'class' => @category,
                       'style' => "margin:0px; padding:0px; " +
                       "position:absolute; " +
                       "left:0px; top:#{@y}px; " +
                       "width:#{@chart.width.to_i}px; " +
                       "height:#{@height}px; " +
                       "font-size:10px;")
  # Render time-off zones.
  @timeOffZones.each do |zone|
    div << rectToHTML(zone[0], 0, zone[1], @height, 'offduty')
  end

  # Render grid lines. The grid lines are determined by the large scale.
  @chart.header.gridLines.each do |line|
    div << rectToHTML(line, 0, 1, @height, 'tabvline')
  end

  # Now render the content as HTML elements.
  @content.each do |c|
    html = c.to_html
    if html && html[0]
      addHtmlTooltip(@tooltip, @query, html[0], div)
      div << html
    end
  end

  # Render the 'now' line
  if @chart.header.nowLineX
    div << rectToHTML(@chart.header.nowLineX, 0, 1, @height, 'nowline')
  end

  div
end

Private Instance Methods

addHtmlTooltip(tooltip, query, trigger, hook = nil) click to toggle source
# File lib/taskjuggler/reports/GanttLine.rb, line 350
def addHtmlTooltip(tooltip, query, trigger, hook = nil)
  return unless tooltip

  tooltip = tooltip.getPattern(query)
  return unless tooltip && !tooltip.empty?

  if tooltip.respond_to?('functionHandler')
    tooltip.setQuery(query)
  end
  if query
    query.attributeId = 'name'
    query.process
    title = query.to_s
  else
    title = ''
  end
  trigger['onclick'] = "TagToTip('ID#{trigger.object_id}', " +
                       "TITLE, '#{title.gsub(/'/, '&apos;')}')"
  trigger['style'] += 'cursor:help; '
  hook = trigger unless hook
  hook << (ltDiv = XMLElement.new('div', 'class' => 'tj_tooltip_box',
                                  'id' => "ID#{trigger.object_id}"))
  ltDiv << (tooltip.respond_to?('to_html') ? tooltip.to_html :
                                             XMLText.new(tooltip))
end
generate() click to toggle source

Create the data objects that represent the abstract form of this perticular Gantt chart line.

# File lib/taskjuggler/reports/GanttLine.rb, line 121
def generate
  # This Array holds the GanttLineObjects.
  @content = []

  generateTimeOffZones

  if @query.property.is_a?(Task)
    generateTask
  else
    generateResource
  end
end
generateResource() click to toggle source

Generate abstract form of a resource line. The resource can be a primary line or appear in the scope of a task.

# File lib/taskjuggler/reports/GanttLine.rb, line 231
def generateResource
  # Set the alternating background color
  @category = "resourcecell#{(@lineIndex + 1) % 2 + 1}"

  # The cellStartDate Array contains the end of the final cell as last
  # element. We need to use a shift mechanism to start and end
  # dates/positions properly.
  x = nil
  startDate = endDate = nil

  project = @query.project
  property = @query.property
  scopeProperty = @query.scopeProperty

  # For unnested resource lines we show the assigned work and the
  # available work. For resources in a task scope we show the work
  # allocated to this task, the work allocated to other tasks and the free
  # work.
  if scopeProperty
    categories = [ 'assigned', 'busy', 'free' ]

    taskStart = scopeProperty['start', @query.scenarioIdx] ||
                project['start']
    taskEnd = scopeProperty['end', @query.scenarioIdx] ||
              project['end']

    if @chart.table
      @chart.table.legend.addGanttItem('Resource assigned to this task',
                                        'assigned')
      @chart.table.legend.addGanttItem('Resource assigned to task(s)',
                                       'busy')
      @chart.table.legend.addGanttItem('Resource available', 'free')
      @chart.table.legend.addGanttItem('Off-duty period', 'offduty')
    end
  else
    categories = [ 'busy', 'free' ]
    if @chart.table
      @chart.table.legend.addGanttItem('Resource assigned to task(s)',
                                       'busy')
      @chart.table.legend.addGanttItem('Resource available', 'free')
      @chart.table.legend.addGanttItem('Off-duty period', 'offduty')
    end
  end

  endDate = nil
  @chart.header.cellStartDates.each do |date|
    if endDate.nil?
      endDate = date
      next
    end

    startDate = endDate
    endDate = date

    if scopeProperty
      # If we have a scope limiting task, we only want to generate load
      # stacks that overlap with the task interval.
      next if endDate <= taskStart || taskEnd <= startDate
      if startDate < taskStart
        # Make sure the left edge of the first stack aligns with the
        # start of the scope task.
        startDate = taskStart
      end
      if endDate > taskEnd
        # Make sure the right edge of the last stack aligns with the end
        # of the scope task.
        endDate = taskEnd
      end

      startIdx = project.dateToIdx(startDate)
      endIdx = project.dateToIdx(endDate)

      taskWork = property.getEffectiveWork(@query.scenarioIdx,
                                           startIdx, endIdx,
                                           scopeProperty)
      overallWork = property.getEffectiveWork(@query.scenarioIdx,
                                              startIdx, endIdx)
      freeWork = property.getEffectiveFreeWork(@query.scenarioIdx,
                                               startIdx, endIdx)
      values = [ taskWork, overallWork - taskWork, freeWork ]
    else
      startIdx = project.dateToIdx(startDate)
      endIdx = project.dateToIdx(endDate)

      values = []
      values << property.getEffectiveWork(@query.scenarioIdx,
                                          startIdx, endIdx)
      values << property.getEffectiveFreeWork(@query.scenarioIdx,
                                              startIdx, endIdx)
    end

    x = @chart.dateToX(startDate)
    w = @chart.dateToX(endDate) - x + 1
    @content << GanttLoadStack.new(self, x + 1, w - 2, values, categories)
  end

end
generateTask() click to toggle source

Generate abstract form of a task line. The task can be a primary line or appear in the scope of a resource.

# File lib/taskjuggler/reports/GanttLine.rb, line 136
def generateTask
  # Set the background color
  @category = "taskcell#{(@lineIndex + 1) % 2 + 1}"

  project = @query.project
  property = @query.property
  scopeProperty = @query.scopeProperty
  taskStart = property['start', @query.scenarioIdx] || project['start']
  taskEnd = property['end', @query.scenarioIdx] || project['end']

  if scopeProperty
    # The task is nested into a resource. We show the work the resource is
    # doing for this task relative to the work the resource is doing for
    # all tasks.
    x = nil
    startDate = endDate = nil
    categories = [ 'busy', nil ]

    @chart.header.cellStartDates.each do |date|
      if x.nil?
        x = @chart.dateToX(endDate = date).to_i
      else
        xNew = @chart.dateToX(date).to_i
        w = xNew - x
        startDate = endDate
        endDate = date

        # If we have a scope limiting task, we only want to generate load
        # stacks that overlap with the task interval.
        next if endDate <= taskStart || taskEnd <= startDate
        if startDate < taskStart && endDate > taskStart
          # Make sure the left edge of the first stack aligns with the
          # start of the scope task.
          startDate = taskStart
          x = @chart.dateToX(startDate)
          w = xNew - x + 1
        elsif startDate < taskEnd && endDate > taskEnd
          # Make sure the right edge of the last stack aligns with the end
          # of the scope task.
          endDate = taskEnd
          w = @chart.dateToX(endDate) - x
        end
        startIdx = project.dateToIdx(startDate)
        endIdx = project.dateToIdx(endDate)

        overallWork = scopeProperty.getEffectiveWork(@query.scenarioIdx,
                                                     startIdx, endIdx) +
                      scopeProperty.getEffectiveFreeWork(@query.scenarioIdx,
                                                         startIdx, endIdx)
        workThisTask = property.getEffectiveWork(@query.scenarioIdx,
                                                  startIdx, endIdx,
                                                  scopeProperty)
        # If all values are 0 we make sure we show an empty frame.
        if overallWork == 0 && workThisTask == 0
          values = [ 0, 1 ]
        else
          values = [ workThisTask, overallWork - workThisTask ]
        end
        @content << GanttLoadStack.new(self, x + 1, w - 2, values,
                                       categories)

        x = xNew
      end
    end
    if @chart.table
      @chart.table.legend.addGanttItem('Resource assigned to task(s)',
                                       'busy')
    end
  else
    # The task is not nested into a resource. We show the classical Gantt
    # bars for the task.
    xStart = @chart.dateToX(taskStart)
    xEnd = @chart.dateToX(taskEnd)
    @chart.addTask(property, self)
    @content <<
      if property['milestone', @query.scenarioIdx]
        GanttMilestone.new(@height, xStart, @y)
      elsif property.container? &&
            ((rollupExpr = @query.project.reportContexts.
                          last.report.get('rollupTask')).nil? ||
             !rollupExpr.eval(@query))
        GanttContainer.new(@height, xStart, xEnd, @y)
      else
        GanttTaskBar.new(@query, @height, xStart, xEnd, @y)
      end

    # Make sure the legend includes the Gantt symbols.
    @chart.table.legend.showGanttItems = true if @chart.table
    @chart.table.legend.addGanttItem('Off-duty period', 'offduty')
  end

end
generateTimeOffZones() click to toggle source

Generate the data structures that mark the time-off periods of a task or resource int the chart. Depending on the resolution, the only periods with a duration above the threshold are shown.

# File lib/taskjuggler/reports/GanttLine.rb, line 332
def generateTimeOffZones
  iv = TimeInterval.new(@chart.start, @chart.end)
  # Don't show any zones if the threshold for this scale is 0 or smaller.
  return if (minTimeOff = @chart.scale['minTimeOff']) <= 0

  # Get the time-off intervals.
  @timeOffZones = @query.property.collectTimeOffIntervals(
                    @query.scenarioIdx, iv, minTimeOff)
  # Convert the start/end dates to X coordinates of the chart. When
  # finished, the zones in @timeOffZones are [ startX, endX ] touples.
  zones = []
  @timeOffZones.each do |zone|
    zones << [ s = @chart.dateToX(zone.start),
               @chart.dateToX(zone.end) -  s ]
  end
  @timeOffZones = zones
end