Index: trunk/estimationtools/burndownchart.py
===================================================================
--- trunk/estimationtools/burndownchart.py	(revision 10108)
+++ trunk/estimationtools/burndownchart.py	(working copy)
@@ -10,7 +10,9 @@
 import copy
 
 DEFAULT_OPTIONS = {'width': '800', 'height': '200', 'color': 'ff9900', 'expected': '0',
-                   'bgcolor': 'ffffff00', 'wecolor':'ccccccaa', 'colorexpected': 'ffddaa', 'weekends':'true', 'gridlines' : '0'}
+                   'bgcolor': 'ffffff00', 'wecolor':'ccccccaa', 'colorexpected': 'ffddaa', 'weekends':'true', 'gridlines' : '0',
+                   'condfield' : '', 'cond' : '', 'label' : '',
+                   'graph2_field' : '', 'graph2_condfield' : '', 'graph2_cond' : '', 'graph2_label' : ''}
 
 class BurndownChart(EstimationToolsBase, WikiMacroBase):
     """Creates burn down chart for selected tickets.
@@ -39,6 +41,14 @@
      * `wecolor`: chart drawing area background color for weekends specified as 6-letter string of hexadecimal values in the format `RRGGBB`.
        Defaults to `ccccccaa`.
 
+     * `condfield`: field name used to check a special user condition for deciding if a ticket will be considered in the graph line. Defaults to empty.
+     * `cond`: condition used to check the value of a field given by 'condfield'. Defaults to empty.
+     * `label`: user defined name of graph line in legend. Defaults to no legend.
+     * `graph2_field`: field name to be shown as second graph line. Defaults to no second graph line.
+     * `graph2_condfield`: field name used to check a special user condition for deciding if a ticket will be considered in the second graph line. Defaults to empty.
+     * `graph2_cond`: condition used to check the value of a field given by 'graph2_condfield'. Defaults to empty.
+     * `graph2_label`: user defined name of second graph line in legend. Defaults to no legend.
+
     Examples:
     {{{
         [[BurndownChart(milestone=Sprint 1, startdate=2008-01-01)]]
@@ -63,8 +73,14 @@
         if (options['startdate'] >= options['enddate']):
             options['enddate'] = options['startdate'] + timedelta(days=1)
 
+        # consider user conditions
+        original_query_args = copy.deepcopy(query_args)
+        if options['condfield'] and options['cond']:
+            query_args[options['condfield']] = options['cond']
+
         # calculate data
-        timetable = self._calculate_timetable(options, query_args, req)
+        timetable = self._calculate_timetable(options, query_args, req, self.estimation_field, 0)
+        timetable_2 = {}
         
         # remove weekends
         if not options['weekends']:
@@ -72,8 +88,42 @@
                 if date.weekday() >= 5:
                     del timetable[date]
 
+        if (options['graph2_field']):
+            # consider user conditions
+            query_args = original_query_args
+            if options['graph2_condfield'] and options['graph2_cond']:
+                query_args[options['graph2_condfield']] = options['graph2_cond']
+
+            # the estimation field will not be accumulated for the days when the ticket has been closed
+            always_consider_value = 1
+            if options['graph2_field'] == self.estimation_field:
+                always_consider_value = 0
+
+            # calculate data
+            timetable_2 = self._calculate_timetable(options, query_args, req, options['graph2_field'], always_consider_value)
+            # remove weekends
+            if not options['weekends']:
+                for date in timetable_2.keys():
+                    if date.weekday() >= 5:
+                        del timetable_2[date]
+
+        # calculate maximum
+        expected_max = int(options.get('expected', 0))
+        maxhours = max(timetable.values() + [expected_max])
+        maxhours_2 = max(timetable_2.values() + [expected_max])
+        maxhours = max(maxhours, maxhours_2)
+        if maxhours <= Decimal(0):
+            maxhours = Decimal(100)
+
         # scale data
-        xdata, ydata, maxhours = self._scale_data(timetable, options)
+        xdata, ydata = self._scale_data(timetable, options, maxhours)
+        xdata_2 = {}
+        ydata_2 = {}
+        if (options['graph2_field']):
+            xdata_2, ydata_2 = self._scale_data(timetable_2, options, maxhours)
+        else:
+            xdata_2['0'] = '-1'
+            ydata_2['0'] = '-1'
 
         # build html for google chart api
         dates = sorted(timetable.keys())
@@ -120,10 +170,10 @@
         if title is None and options.get('milestone'):
             title = options['milestone'].split('|')[0]
 
-        chart_args = unicode_urlencode(
-                    {'chs': '%sx%s' % (options['width'], options['height']),
+        chart_lst = {'chs': '%sx%s' % (options['width'], options['height']),
                      'chf': 'c,s,%s|bg,s,00000000' % options['bgcolor'],
-                     'chd': 't:%s|%s%s' % (",".join(xdata), ",".join(ydata), expecteddata),
+                     'chd': 't:%s|%s%s|%s|%s' % (",".join(xdata), ",".join(ydata), expecteddata, ",".join(xdata_2), ",".join(ydata_2)),
+                     'chdlp': 'b',
                      'cht': 'lxy',
                      'chxt': 'x,x,y',
                      'chxl': bottomaxis,
@@ -131,7 +181,24 @@
                      'chm': "|".join(weekends),
                      'chg': gridlinesdata,
                      'chco': '%s,%s' % (options['color'], options['colorexpected']),
-                     'chtt': title})
+                     'chtt': title}
+
+        # probably add labels
+        if options['graph2_field']:
+            # in case of more than one graph, always add a legend to the chart for distinguishing the lines
+            # use field names by default, user can rename to own strings
+            label = self.estimation_field
+            graph2_label = options['graph2_field']
+            if options['label']:
+                label = options['label']
+            if options['graph2_label']:
+                graph2_label = options['graph2_label']
+            chart_lst['chdl'] = "%s|%s" % (label, graph2_label)
+        else:
+            if options['label']:
+                chart_lst['chdl'] = "%s" % options['label']
+
+        chart_args = unicode_urlencode(chart_lst)
         self.log.debug("BurndownChart data: %s" % repr(chart_args))
         if self.serverside_charts:
             return tag.image(src="%s?data=%s" % (req.href.estimationtools('chart'),
@@ -141,7 +208,7 @@
             return tag.image(src="http://chart.apis.google.com/chart?%s" % chart_args,
                              alt="Burndown Chart (client)")
 
-    def _calculate_timetable(self, options, query_args, req):
+    def _calculate_timetable(self, options, query_args, req, valuefield, always_consider_value):
         db = self.env.get_db_cnx()
 
         # create dictionary with entry for each day of the required time period
@@ -154,21 +221,21 @@
 
         # get current values for all tickets within milestone and sprints
 
-        query_args[self.estimation_field + "!"] = None
+        query_args[valuefield + "!"] = None
         tickets = execute_query(self.env, req, query_args)
 
         # add the open effort for each ticket for each day to the timetable
 
         for t in tickets:
 
-            # Record the current (latest) status and estimate, and ticket
+            # Record the current (latest) status and value, and ticket
             # creation date
 
             creation_date = t['time'].date()
             latest_status = t['status']
-            latest_estimate = self._cast_estimate(t[self.estimation_field])
-            if latest_estimate is None:
-                latest_estimate = Decimal(0)
+            latest_value = self._cast_value(t[valuefield])
+            if latest_value is None:
+                latest_value = Decimal(0)
 
             # Fetch change history for status and effort fields for this ticket
             history_cursor = db.cursor()
@@ -176,28 +243,28 @@
                 "DISTINCT c.field as field, c.time AS time, c.oldvalue as oldvalue, c.newvalue as newvalue "
                 "FROM ticket t, ticket_change c "
                 "WHERE t.id = %s and c.ticket = t.id and (c.field=%s or c.field='status')"
-                "ORDER BY c.time ASC", [t['id'], self.estimation_field])
+                "ORDER BY c.time ASC", [t['id'], valuefield])
 
             # Build up two dictionaries, mapping dates when effort/status
             # changed, to the latest effort/status on that day (in case of
             # several changes on the same day). Also record the oldest known
             # effort/status, i.e. that at the time of ticket creation
 
-            estimate_history = {}
+            value_history = {}
             status_history = {}
 
-            earliest_estimate = None
+            earliest_value = None
             earliest_status = None
 
             for row in history_cursor:
                 row_field, row_time, row_old, row_new = row
                 event_date = from_timestamp(row_time).date()
-                if row_field == self.estimation_field:
-                    new_value = self._cast_estimate(row_new)
+                if row_field == valuefield:
+                    new_value = self._cast_value(row_new)
                     if new_value is not None:
-                        estimate_history[event_date] = new_value
-                    if earliest_estimate is None:
-                        earliest_estimate = self._cast_estimate(row_old)
+                        value_history[event_date] = new_value
+                    if earliest_value is None:
+                        earliest_value = self._cast_value(row_old)
                 elif row_field == 'status':
                     status_history[event_date] = row_new
                     if earliest_status is None:
@@ -206,53 +273,49 @@
             # If we don't know already (i.e. the ticket effort/status was
             # not changed on the creation date), set the effort on the
             # creation date. It may be that we don't have an "earliest"
-            # estimate/status, because it was never changed. In this case,
+            # value/status, because it was never changed. In this case,
             # use the current (latest) value.
 
-            if not creation_date in estimate_history:
-                if earliest_estimate is not None:
-                    estimate_history[creation_date] = earliest_estimate
+            if not creation_date in value_history:
+                if earliest_value is not None:
+                    value_history[creation_date] = earliest_value
                 else:
-                    estimate_history[creation_date] = latest_estimate
+                    value_history[creation_date] = latest_value
             if not creation_date in status_history:
                 if earliest_status is not None:
                     status_history[creation_date] = earliest_status
                 else:
                     status_history[creation_date] = latest_status
 
-            # Finally estimates to the timetable. Treat any period where the
-            # ticket was closed as estimate 0. We need to loop from ticket
+            # Finally values to the timetable. Treat any period where the
+            # ticket was closed as value 0. We need to loop from ticket
             # creation date, not just from the timetable start date, since
             # it's possible that the ticket was changed between these two
             # dates.
 
             current_date = creation_date
-            current_estimate = None
+            current_value = None
             is_open = None
 
             while current_date <= options['enddate']:
                 if current_date in status_history:
                     is_open = (status_history[current_date] not in self.closed_states)
 
-                if current_date in estimate_history:
-                    current_estimate = estimate_history[current_date]
+                if current_date in value_history:
+                    current_value = value_history[current_date]
 
-                if current_date >= options['startdate'] and is_open:
-                    timetable[current_date] += current_estimate
+                if current_date >= options['startdate'] and (is_open or always_consider_value):
+                    timetable[current_date] += current_value
 
                 current_date += timedelta(days=1)
 
         return timetable
 
-    def _scale_data(self, timetable, options):
+    def _scale_data(self, timetable, options, maxhours):
         # create sorted list of dates
         dates = timetable.keys()
         dates.sort()
 
-        maxhours = max(timetable.values() + [int(options.get('expected', 0))])
-
-        if maxhours <= Decimal(0):
-            maxhours = Decimal(100)
         ydata = [str(self._round(timetable[d] * Decimal(100) / maxhours))
                  for d in dates]
         xdata = [str(self._round(x * Decimal(100) / (len(dates) - 1)))
@@ -263,17 +326,17 @@
         if remaining_days:
             ydata = ydata[: - remaining_days] + ['-1' for x in xrange(0, remaining_days)]
 
-        return xdata, ydata, maxhours
+        return xdata, ydata
 
     def _round(self, decimal_):
         return decimal_.quantize(Decimal("0.01"), ROUND_HALF_UP)
 
-    def _cast_estimate(self, estimate):
+    def _cast_value(self, value):
         # Treat 0, empty string or None as 0.0
-        if not estimate:
+        if not value:
             return Decimal(0)
         try:
-            return Decimal(estimate)
+            return Decimal(value)
         except (TypeError, ValueError, InvalidOperation):
             # Treat other incorrect values as None
             return None
Index: trunk/estimationtools/utils.py
===================================================================
--- trunk/estimationtools/utils.py	(revision 10108)
+++ trunk/estimationtools/utils.py	(working copy)
@@ -18,7 +18,8 @@
 
 AVAILABLE_OPTIONS = ['startdate', 'enddate', 'today', 'width', 'height', 
                      'color', 'bgcolor', 'wecolor', 'weekends', 'gridlines',
-                     'expected', 'colorexpected', 'title']
+                     'expected', 'colorexpected', 'title',
+                     'condfield', 'cond', 'label', 'graph2_field', 'graph2_condfield', 'graph2_cond', 'graph2_label']
 
 def get_estimation_field():
     return Option('estimation-tools', 'estimation_field', 'estimatedhours',
