class TaskJuggler::TableReport

This is base class for all types of tabular reports. All tabular reports are converted to an abstract (output independent) intermediate form first, before the are turned into the requested output format.

Attributes

legend[R]

Public Class Methods

alignment(colId, attributeType) click to toggle source

Return the alignment of the column based on the colId or the attributeType.

# File lib/taskjuggler/reports/TableReport.rb, line 177
def TableReport::alignment(colId, attributeType)
  if @@propertiesById.has_key?(colId)
    return @@propertiesById[colId][2]
  elsif @@propertiesByType.has_key?(attributeType)
    return @@propertiesByType[attributeType][1]
  else
    :center
  end
end
calculated?(colId) click to toggle source

This function returns true if the values for the colId column need to be calculated.

# File lib/taskjuggler/reports/TableReport.rb, line 189
def TableReport::calculated?(colId)
  return @@propertiesById.has_key?(colId)
end
defaultColumnTitle(id) click to toggle source

Returns the default column title for the columns id.

# File lib/taskjuggler/reports/TableReport.rb, line 153
def TableReport::defaultColumnTitle(id)
  # Return an empty string for some special columns that don't have a fixed
  # title.
  specials = %w( chart hourly daily weekly monthly quarterly yearly)
  return '' if specials.include?(id)

  # Return the title for build-in hardwired columns.
  @@propertiesById.include?(id) ? @@propertiesById[id][0] : nil
end
indent(colId, propertyType) click to toggle source

Return if the column values should be indented based on the colId or the propertyType.

# File lib/taskjuggler/reports/TableReport.rb, line 165
def TableReport::indent(colId, propertyType)
  if @@propertiesById.has_key?(colId)
    return @@propertiesById[colId][1]
  elsif @@propertiesByType.has_key?(propertyType)
    return @@propertiesByType[propertyType][0]
  else
    false
  end
end
new(report) click to toggle source

Generate a new TableReport object.

Calls superclass method TaskJuggler::ReportBase.new
# File lib/taskjuggler/reports/TableReport.rb, line 88
def initialize(report)
  super
  @report.content = self

  # Reference to the intermediate representation.
  @table = nil
  # The table is generated row after row. We need to hold some computed
  # values that are specific to certain columns. For that we use a Hash of
  # ReportTableColumn objects.
  @columns = { }

  @legend = ReportTableLegend.new

end
scenarioSpecific?(colId) click to toggle source

This functions returns true if the values for the col_id column are scenario specific.

# File lib/taskjuggler/reports/TableReport.rb, line 195
def TableReport::scenarioSpecific?(colId)
  if @@propertiesById.has_key?(colId)
    return @@propertiesById[colId][3]
  end
  return false
end

Public Instance Methods

generateIntermediateFormat() click to toggle source
# File lib/taskjuggler/reports/TableReport.rb, line 103
def generateIntermediateFormat
  super
end
to_csv() click to toggle source

Convert the table into an Array of Arrays. It has one Array for each line. The nested Arrays have one String for each column.

# File lib/taskjuggler/reports/TableReport.rb, line 148
def to_csv
  @table.to_csv
end
to_html() click to toggle source

Turn the TableReport into an equivalent HTML element tree.

# File lib/taskjuggler/reports/TableReport.rb, line 108
def to_html
  html = []

  html << XMLComment.new("Dynamic Report ID: " +
                         "#{@report.project.reportContexts.last.
                            dynamicReportId}")
  html << rt_to_html('header')
  html << (tableFrame = generateHtmlTableFrame)

  # Now generate the actual table with the data.
  tableFrame << generateHtmlTableRow do
    td = XMLElement.new('td')
    td << @table.to_html
    td
  end

  # Embedd the caption as RichText into the table footer.
  if a('caption')
    tableFrame << generateHtmlTableRow do
      td = XMLElement.new('td')
      td << (div = XMLElement.new('div', 'class' => 'tj_table_caption'))
      a('caption').sectionNumbers = false
      div << a('caption').to_html
      td
    end
  end

  # The legend.
  tableFrame << generateHtmlTableRow do
    td = XMLElement.new('td')
    td << @legend.to_html
    td
  end

  html << rt_to_html('footer')
  html
end

Protected Instance Methods

adjustColumnPeriod(columnDef, tasks = [], scenarios = []) click to toggle source

In case the user has not specified the report period, we try to fit all the tasks in and add an extra 5% time at both ends for some specific type of columns. scenarios is a list of scenario indexes. columnDef is a reference to the TableColumnDefinition object describing the current column.

# File lib/taskjuggler/reports/TableReport.rb, line 213
def adjustColumnPeriod(columnDef, tasks = [], scenarios = [])
  # If we have user specified dates for the report period or the column
  # period, we don't adjust the period. This flag is used to mark if we
  # have user-provided values.
  doNotAdjustStart = false
  doNotAdjustEnd = false

  # Determine the start date for the column.
  if columnDef.start
    # We have a user-specified, column specific start date.
    rStart = columnDef.start
    doNotAdjustStart = true
  else
    # Use the report start date.
    rStart = a('start')
    doNotAdjustStart = true if rStart != @project['start']
  end

  if columnDef.end
    rEnd = columnDef.end
    doNotAdjustEnd = true
  else
    rEnd = a('end')
    doNotAdjustEnd = true if rEnd != @project['end']
  end
  origStart = rStart
  origEnd = rEnd

  # Save the unadjusted dates to the columns Hash.
  @columns[columnDef] = TableReportColumn.new(rStart, rEnd)

  # If the task list is empty or the user has provided a custom start or
  # end date, we don't touch the report period.
  return if tasks.empty? || scenarios.empty? ||
            (doNotAdjustStart && doNotAdjustEnd)

  # Find the start date of the earliest tasks included in the report and
  # the end date of the last included tasks.
  rStart = rEnd = nil
  scenarios.each do |scenarioIdx|
    tasks.each do |task|
      date = task['start', scenarioIdx] || @project['start']
      rStart = date if rStart.nil? || date < rStart
      date = task['end', scenarioIdx] || @project['end']
      rEnd = date if rEnd.nil? || date > rEnd
    end
  end

  # We want to add at least 5% on both ends.
  margin = 0
  minWidth = rEnd - rStart + 1
  case columnDef.id
  when 'chart'
    # In case we have a 'chart' column, we enforce certain minimum width
    # The following table contains an entry for each scale. The entry
    # consists of the triple 'seconds per unit', 'minimum width units'
    # and 'margin units'. The minimum with does not include the margins
    # since they are always added.
    mwMap = {
      'hour' =>    [ 60 * 60,            18, 2 ],
      'day' =>     [ 60 * 60 * 24,       18, 2 ],
      'week' =>    [ 60 * 60 * 24 * 7,    6, 1 ],
      'month' =>   [ 60 * 60 * 24 * 31,  10, 1 ],
      'quarter' => [ 60 * 60 * 24 * 90,   6, 1 ],
      'year' =>    [ 60 * 60 * 24 * 365,  4, 1 ]
    }
    entry = mwMap[columnDef.scale]
    raise "Unknown scale #{columnDef.scale}" unless entry
    margin = entry[0] * entry[2]
    # If the with determined by start and end dates of the task is below
    # the minimum width, we increase the width to the value provided by
    # the table.
    minWidth = entry[0] * entry[1] if minWidth < entry[0] * entry[1]
  when 'hourly', 'daily', 'weekly', 'monthly', 'quarterly', 'yearly'
    # For the calendar columns we use a similar approach as we use for
    # the 'chart' column.
    mwMap = {
      'hourly' =>    [ 60 * 60,            18, 2 ],
      'daily' =>     [ 60 * 60 * 24,       18, 2 ],
      'weekly' =>    [ 60 * 60 * 24 * 7,    6, 1 ],
      'monthly' =>   [ 60 * 60 * 24 * 31,  10, 1 ],
      'quarterly' => [ 60 * 60 * 24 * 90,   6, 1 ],
      'yearly' =>    [ 60 * 60 * 24 * 365,  4, 1 ]
    }
    entry = mwMap[columnDef.id]
    raise "Unknown scale #{columnDef.id}" unless entry
    margin = entry[0] * entry[2]
    minWidth = entry[0] * entry[1] if minWidth < entry[0] * entry[1]
  else
    doNotAdjustStart = doNotAdjustEnd = true
  end

  unless doNotAdjustStart && doNotAdjustEnd
    if minWidth > (rEnd - rStart + 1)
      margin = (minWidth - (rEnd - rStart + 1)) / 2
    end

    rStart -= margin
    rEnd += margin

    # This could cause rStart to be larger than rEnd.
    rStart = origStart if doNotAdjustStart
    rEnd = origEnd if doNotAdjustEnd

    # Ensure that we have a valid interval. If not, go back to the
    # original interval dates.
    if rStart >= rEnd
      rStart = origStart
      rEnd = origEnd
    end

    # Save the adjusted dates to the columns Hash.
    @columns[columnDef] = TableReportColumn.new(rStart, rEnd)
  end
end
generateAccountList(accountList, lineOffset, mode) click to toggle source

Generate a ReportTableLine for each of the accounts in accountList. If scopeLine is defined, the generated account lines will be within the scope this resource line.

# File lib/taskjuggler/reports/TableReport.rb, line 389
def generateAccountList(accountList, lineOffset, mode)
  # Get the current Query from the report context and create a copy. We
  # are going to modify it.
  accountList.query = query = @project.reportContexts.last.query.dup
  accountList.sort!

  # The primary line counter. Is not used for enclosed lines.
  no = lineOffset
  # The scope line counter. It's reset for each new scope.
  lineNo = lineOffset
  # Init the variable to get a larger scope
  line = nil
  accountList.each do |account|
    query.property = account

    no += 1
    Log.activity if lineNo % 10 == 0
    lineNo += 1
    a('scenarios').each do |scenarioIdx|
      query.scenarioIdx = scenarioIdx
      # Generate line for each account.
      line = ReportTableLine.new(@table, account, nil)

      line.no = no
      line.lineNo = lineNo
      line.subLineNo = @table.lines
      setIndent(line, a('accountroot'), accountList.treeMode?)

      # Generate a cell for each column in this line.
      a('columns').each do |columnDef|
        next unless generateTableCell(line, columnDef, query)
      end
    end
  end
  lineNo
end
generateHeaderCell(columnDef) click to toggle source

Generates cells for the table header. columnDef is the TableColumnDefinition object that describes the column. Based on the id of the column different actions need to be taken to generate the header text.

# File lib/taskjuggler/reports/TableReport.rb, line 332
def generateHeaderCell(columnDef)
  rStart = @columns[columnDef].start
  rEnd = @columns[columnDef].end

  case columnDef.id
  when 'chart'
    # For the 'chart' column we generate a GanttChart object. The sizes are
    # set so that the lines of the Gantt chart line up with the lines of the
    # table.
    gantt = GanttChart.new(a('now'),
                           a('weekStartsMonday'), columnDef, self)

    gantt.generateByScale(rStart, rEnd, columnDef.scale)
    # The header consists of 2 lines separated by a 1 pixel boundary.
    gantt.header.height = @table.headerLineHeight * 2 + 1
    # The maximum width of the chart. In case it needs more space, a
    # scrollbar is shown or the chart gets truncated depending on the output
    # format.
    gantt.viewWidth = columnDef.width ? columnDef.width : 450
    column = ReportTableColumn.new(@table, columnDef, '')
    column.cell1.special = gantt
    column.cell2.hidden = true
    column.scrollbar = gantt.hasScrollbar?
    @table.equiLines = true
  when 'hourly'
    genCalChartHeader(columnDef, rStart.midnight, rEnd, :sameTimeNextHour,
                      '%A %Y-%m-%d', '%H')
  when 'daily'
    genCalChartHeader(columnDef, rStart.midnight, rEnd, :sameTimeNextDay,
                      '%b %Y', '%d')
  when 'weekly'
    genCalChartHeader(columnDef,
                      rStart.beginOfWeek(a('weekStartsMonday')), rEnd,
                      :sameTimeNextWeek, '%b %Y', '%d')
  when 'monthly'
    genCalChartHeader(columnDef, rStart.beginOfMonth, rEnd,
                      :sameTimeNextMonth, '%Y', '%b')
  when 'quarterly'
    genCalChartHeader(columnDef, rStart.beginOfQuarter, rEnd,
                      :sameTimeNextQuarter, '%Y', 'Q%Q')
  when 'yearly'
    genCalChartHeader(columnDef, rStart.beginOfYear, rEnd, :sameTimeNextYear,
                      nil, '%Y')
  else
    # This is the most common case. It does not need any special treatment.
    # We just set the pre-defined or user-defined column title in the first
    # row of the header. The 2nd row is not visible.
    column = ReportTableColumn.new(@table, columnDef, columnDef.title)
    column.cell1.rows = 2
    column.cell2.hidden = true
    column.cell1.width = columnDef.width if columnDef.width
  end
end
generateResourceList(resourceList, taskList, scopeLine) click to toggle source

Generate a ReportTableLine for each of the resources in resourceList. In case taskList is not nil, it also generates the nested task lines for each task that the resource is assigned to. If scopeLine is defined, the generated resource lines will be within the scope this task line.

# File lib/taskjuggler/reports/TableReport.rb, line 487
def generateResourceList(resourceList, taskList, scopeLine)
  # Get the current Query from the report context and create a copy. We
  # are going to modify it.
  resourceList.query = query = @project.reportContexts.last.query.dup
  query.scopeProperty = scopeLine ? scopeLine.property : nil
  resourceList.sort!

  # The primary line counter. Is not used for enclosed lines.
  no = 0
  # The scope line counter. It's reset for each new scope.
  lineNo = scopeLine ? scopeLine.lineNo : 0
  # Init the variable to get a larger scope
  line = nil
  resourceList.each do |resource|
    # Get the current Query from the report context and create a copy. We
    # are going to modify it.
    query.property = resource
    query.scopeProperty = scopeLine ? scopeLine.property : nil

    no += 1
    Log.activity if lineNo % 10 == 0
    lineNo += 1
    a('scenarios').each do |scenarioIdx|
      query.scenarioIdx = scenarioIdx
      # Generate line for each resource.
      line = ReportTableLine.new(@table, resource, scopeLine)

      line.no = no unless scopeLine
      line.lineNo = lineNo
      line.subLineNo = @table.lines
      setIndent(line, a('resourceroot'), resourceList.treeMode?)

      # Generate a cell for each column in this line.
      a('columns').each do |column|
        next unless generateTableCell(line, column, query)
      end
    end

    if taskList
      # If we have a taskList we generate nested lines for each of the
      # tasks that the resource is assigned to and pass the user-defined
      # filter.
      taskList.setSorting(a('sortTasks'))
      assignedTaskList = filterTaskList(taskList, resource,
                                        a('hideTask'), a('rollupTask'),
                                        a('openNodes'))
      assignedTaskList.sort!
      lineNo = generateTaskList(assignedTaskList, nil, line)
    end
  end
  lineNo
end
generateTaskList(taskList, resourceList, scopeLine) click to toggle source

Generate a ReportTableLine for each of the tasks in taskList. In case resourceList is not nil, it also generates the nested resource lines for each resource that is assigned to the particular task. If scopeLine is defined, the generated task lines will be within the scope this resource line.

# File lib/taskjuggler/reports/TableReport.rb, line 431
def generateTaskList(taskList, resourceList, scopeLine)
  # Get the current Query from the report context and create a copy. We
  # are going to modify it.
  taskList.query = query = @project.reportContexts.last.query.dup
  query.scopeProperty = scopeLine ? scopeLine.property : nil
  taskList.sort!

  # The primary line counter. Is not used for enclosed lines.
  no = 0
  # The scope line counter. It's reset for each new scope.
  lineNo = scopeLine ? scopeLine.lineNo : 0
  # Init the variable to get a larger scope
  line = nil
  taskList.each do |task|
    # Get the current Query from the report context and create a copy. We
    # are going to modify it.
    query.property = task
    query.scopeProperty = scopeLine ? scopeLine.property : nil

    no += 1
    Log.activity if lineNo % 10 == 0
    lineNo += 1
    a('scenarios').each do |scenarioIdx|
      query.scenarioIdx = scenarioIdx
      # Generate line for each task.
      line = ReportTableLine.new(@table, task, scopeLine)

      line.no = no unless scopeLine
      line.lineNo = lineNo
      line.subLineNo = @table.lines
      setIndent(line, a('taskroot'), taskList.treeMode?)

      # Generate a cell for each column in this line.
      a('columns').each do |columnDef|
        next unless generateTableCell(line, columnDef, query)
      end
    end

    if resourceList
      # If we have a resourceList we generate nested lines for each of the
      # resources that are assigned to this task and pass the user-defined
      # filter.
      resourceList.setSorting(a('sortResources'))
      assignedResourceList = filterResourceList(resourceList, task,
          a('hideResource'), a('rollupResource'), a('openNodes'))
      assignedResourceList.sort!
      lineNo = generateResourceList(assignedResourceList, nil, line)
    end
  end
  lineNo
end