add estimated line and support empty component.
@@ -84,7 +84,7 @@ | ||
84 | 84 | this.max = parseInt(value,10); |
85 | 85 | } |
86 | 86 | |
87 | - this.render = function(canvas, title, height) | |
87 | + this.render = function(canvas, title, height, colPtr) | |
88 | 88 | { |
89 | 89 | var jg = new jsGraphics(canvas); |
90 | 90 |
@@ -111,7 +111,7 @@ | ||
111 | 111 | } |
112 | 112 | |
113 | 113 | // Draw the line graph |
114 | - var color = this.getColor(); | |
114 | + var color = colPtr; //this.getColor(); | |
115 | 115 | var oldx, oldy; |
116 | 116 | jg.setStroke(1); |
117 | 117 |
@@ -125,7 +125,7 @@ | ||
125 | 125 | jg.drawLine(oldx, h-oldy, sx, h-ht1); |
126 | 126 | } |
127 | 127 | |
128 | - jg.setColor("red"); | |
128 | + jg.setColor(colPtr); | |
129 | 129 | jg.fillEllipse(sx-2, h-ht1-2, 5, 5); |
130 | 130 | |
131 | 131 | jg.setColor("black"); |
@@ -18,7 +18,7 @@ | ||
18 | 18 | |
19 | 19 | from trac import __version__ as tracversion_runtime |
20 | 20 | |
21 | -from datetime import datetime | |
21 | +from datetime import datetime, date | |
22 | 22 | |
23 | 23 | from trac.core import * |
24 | 24 | from trac.config import BoolOption |
@@ -29,6 +29,7 @@ | ||
29 | 29 | from trac.util import escape, Markup, format_date |
30 | 30 | from trac.ticket import ITicketChangeListener |
31 | 31 | from trac.ticket import model |
32 | +from trac.util.datefmt import to_datetime | |
32 | 33 | |
33 | 34 | class BurndownComponent(Component): |
34 | 35 | implements(IEnvironmentSetupParticipant, INavigationContributor, |
@@ -169,7 +170,9 @@ | ||
169 | 170 | self.update_burndown_data() |
170 | 171 | |
171 | 172 | data['burndown_data'] = req.hdf['burndown_data'] = [] |
172 | - data['burndown_data'] = req.hdf['burndown_data'] = self.get_burndown_data(db, selected_milestone, components, selected_component) | |
173 | + burndown_data, estimated_data = self.get_burndown_data(db, selected_milestone, components, selected_component) | |
174 | + data['burndown_data'] = req.hdf['burndown_data'] = burndown_data | |
175 | + data['estimated_data'] = req.hdf['estimated_data'] = estimated_data | |
173 | 176 | |
174 | 177 | add_stylesheet(req, 'hw/css/burndown.css') |
175 | 178 |
@@ -188,17 +191,24 @@ | ||
188 | 191 | add_script(req, 'hw/js/wz_jsgraphics.js') |
189 | 192 | |
190 | 193 | return 'burndown.html', data, None |
191 | - | |
192 | - | |
194 | + | |
195 | + | |
193 | 196 | def get_burndown_data(self, db, selected_milestone, components, selected_component): |
194 | 197 | cursor = db.cursor() |
195 | - | |
198 | + cursor.execute("SELECT due FROM milestone where name='%s'" % selected_milestone['name']); | |
199 | + milestone_due = cursor.fetchone()[0]; | |
200 | + self.log.info("milestone_due:") | |
201 | + self.log.info(milestone_due) | |
202 | + t = to_datetime(milestone_due) | |
203 | + due_milestone = datetime(t.year,t.month,t.day) | |
204 | + | |
205 | + delta = None | |
196 | 206 | component_data = {} # this will be a dictionary of lists of tuples -- e.g. component_data = {'componentName':[(id, hours_remaining), (id, hours_remaining), (id, hours_remaining)]} |
197 | 207 | for comp in components: |
198 | 208 | if selected_component == 'All Components' or comp['name'] == selected_component: |
199 | 209 | self.log.debug("comp = %s", comp['name']) |
200 | - self.log.debug("selected_component = %s", selected_component) | |
201 | - sqlBurndown = "SELECT id, hours_remaining "\ | |
210 | + self.log.debug("selected_component = %s", sselected_component) | |
211 | + sqlBurndown = "SELECT id, hours_remaining ,date"\ | |
202 | 212 | "FROM burndown "\ |
203 | 213 | "WHERE milestone_name = %s AND component_name = %s "\ |
204 | 214 | "ORDER BY id" |
@@ -211,7 +221,7 @@ | ||
211 | 221 | if selected_component == 'All Components' or comp['name'] == selected_component: |
212 | 222 | self.log.debug("comp = %s", comp['name']) |
213 | 223 | self.log.debug("selected_component = %s", selected_component) |
214 | - sqlBurndown = "SELECT id, hours_remaining "\ | |
224 | + sqlBurndown = "SELECT id, hours_remaining, date "\ | |
215 | 225 | "FROM burndown "\ |
216 | 226 | "WHERE milestone_name = %s AND component_name = %s "\ |
217 | 227 | "ORDER BY id" |
@@ -228,6 +238,7 @@ | ||
228 | 238 | for time_unit in range (0, burndown_length): |
229 | 239 | sumHours = 0 |
230 | 240 | for comp in components: |
241 | + plotdata.append(comp['date']) | |
231 | 242 | self.log.debug('component: %s', [comp['name']]); |
232 | 243 | self.log.debug('time_unit: %s', [time_unit]); |
233 | 244 | if (component_data[comp['name']] and len(component_data[comp['name']]) > time_unit): |
@@ -245,9 +256,25 @@ | ||
245 | 256 | |
246 | 257 | else: |
247 | 258 | for time_unit in range (0, len(component_data[selected_component])): |
259 | + plotdata.append(component_data[selected_component]['date']) | |
248 | 260 | burndown_data.append((time_unit+1, component_data[selected_component][time_unit][1])) |
249 | - | |
250 | - return burndown_data | |
261 | + start_milestone = None | |
262 | + estimated_data = [] | |
263 | + if len(burndown_data)!=0: | |
264 | + if len(components)==0: | |
265 | + t = time.strptime(component_data['-'][0][2],'%Y/%m/%d') | |
266 | + start_milestone = datetime(t[0] ,t[1] ,t[2]) | |
267 | + else: | |
268 | + t = time.strptime(component_data[component_data.keys()[0]][0][2],'%Y/%m/%d') | |
269 | + start_milestone = datetime(t[0] ,t[1] ,t[2]) | |
270 | + initial_estimated = burndown_data[0][1] | |
271 | + | |
272 | + term = (due_milestone - start_milestone).days | |
273 | + | |
274 | + for time_unit in range (0,term): | |
275 | + estimated_data.append((time_unit+1 ,initial_estimated*(1-float(time_unit+1)/term) )) | |
276 | + | |
277 | + return (burndown_data, estimated_data) | |
251 | 278 | |
252 | 279 | def start_milestone(self, db, milestone): |
253 | 280 | startdate = dbhelper.get_startdate_for_milestone(db, milestone) |
@@ -86,13 +86,17 @@ | ||
86 | 86 | |
87 | 87 | <script> |
88 | 88 | var g = new line_graph(); |
89 | - | |
89 | + var e = new line_graph(); | |
90 | 90 | <py:for each="row in burndown_data"> |
91 | 91 | g.add('${row[0]}', ${row[1]}); |
92 | 92 | </py:for> |
93 | + <py:for each="row in estimated_data"> | |
94 | + e.add('${row[0]}', ${row[1]}); | |
95 | + </py:for> | |
93 | 96 | |
94 | 97 | //If called without a height parameter, defaults to 250 |
95 | - g.render("burndownGraph", "hours remaining vs. days of sprint", 300); | |
98 | + e.render("burndownGraph", "残り時間とスプリント日数", 300, "skyblue"); | |
99 | + g.render("burndownGraph", "", 300, "red"); | |
96 | 100 | |
97 | 101 | </script> |
98 | 102 | </py:otherwise> |