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
Public Class Methods
Return the alignment of the column based on the colId or the attributeType.
# File lib/taskjuggler/reports/TableReport.rb, line 179 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
This function returns true if the values for the colId column need to be calculated.
# File lib/taskjuggler/reports/TableReport.rb, line 191 def TableReport::calculated?(colId) return @@propertiesById.has_key?(colId) end
Returns the default column title for the columns id.
# File lib/taskjuggler/reports/TableReport.rb, line 155 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
Return if the column values should be indented based on the colId or the propertyType.
# File lib/taskjuggler/reports/TableReport.rb, line 167 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
Generate a new TableReport
object.
TaskJuggler::ReportBase::new
# File lib/taskjuggler/reports/TableReport.rb, line 90 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
This functions returns true if the values for the col_id column are scenario specific.
# File lib/taskjuggler/reports/TableReport.rb, line 197 def TableReport::scenarioSpecific?(colId) if @@propertiesById.has_key?(colId) return @@propertiesById[colId][3] end return false end
Public Instance Methods
TaskJuggler::ReportBase#generateIntermediateFormat
# File lib/taskjuggler/reports/TableReport.rb, line 105 def generateIntermediateFormat super end
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 150 def to_csv @table.to_csv end
Turn the TableReport
into an equivalent HTML element tree.
# File lib/taskjuggler/reports/TableReport.rb, line 110 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
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 215 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
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 391 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
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 334 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, a('markdate')) 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
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 489 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
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 433 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