ver 0.5-SNAPSHOT
@@ -0,0 +1,9 @@ | ||
1 | +import unittest | |
2 | + | |
3 | +def suite(): | |
4 | + suite = unittest.TestSuite() | |
5 | + suite.addTest(dateutils_test.suite()) | |
6 | + return suite | |
7 | + | |
8 | +if __name__ == '__main__': | |
9 | + unittest.main(defaultTest="suite") | |
\ No newline at end of file |
@@ -0,0 +1,136 @@ | ||
1 | +# -*- coding: utf-8 -*- | |
2 | + | |
3 | +import sys | |
4 | +import os | |
5 | +import re | |
6 | +import unittest | |
7 | + | |
8 | +script_dir = os.path.dirname(os.path.abspath(__file__)) | |
9 | +base_dir = script_dir + os.sep + '../' | |
10 | +if not base_dir in sys.path: | |
11 | + sys.path.insert(0, base_dir) | |
12 | + | |
13 | +from renderer import ReportRenderer | |
14 | + | |
15 | +class ComponentManagerStub(object): | |
16 | + components = {} | |
17 | + | |
18 | + def component_activated(self, dummy): | |
19 | + pass | |
20 | + | |
21 | + def get_db_cnx(self): | |
22 | + return DBStub() | |
23 | + | |
24 | +class RequestStub(object): | |
25 | + def __init__(self): | |
26 | + self.href = HrefStub() | |
27 | + self.tz = None | |
28 | + | |
29 | +class HrefStub(object): | |
30 | + def worktime(self, id): | |
31 | + return 'worktime/%s' % id | |
32 | + | |
33 | +class ConfigStub(object): | |
34 | + def get(self, tag, key): | |
35 | + pass | |
36 | + | |
37 | +class DBStub(object): | |
38 | + def __init__(self, row=None): | |
39 | + self.row = row | |
40 | + | |
41 | + def cursor(self): | |
42 | + print "cursor" | |
43 | + return self | |
44 | + | |
45 | + def execute(self, sql , args): | |
46 | + print "excecute" | |
47 | + return self | |
48 | + | |
49 | + def fetchone(self): | |
50 | + print "fetchone" | |
51 | + return self.row | |
52 | + | |
53 | + def commit(self): | |
54 | + print "commit" | |
55 | + | |
56 | +class RendererTest(unittest.TestCase): | |
57 | + | |
58 | + def setUp(self): | |
59 | + self.env = ComponentManagerStub() | |
60 | + self.req = RequestStub() | |
61 | + self.db = DBStub() | |
62 | + | |
63 | + def test_getXAxisType(self): | |
64 | + renderer = ReportRenderer(self.env) | |
65 | + | |
66 | + type = renderer._getXAxisType(self.req, ['2011-04-31'], []) | |
67 | + self.assertEqual("string", type) | |
68 | + | |
69 | + type = renderer._getXAxisType(self.req, ['2011-04-30'], []) | |
70 | + self.assertEqual("date", type) | |
71 | + | |
72 | + type = renderer._getXAxisType(self.req, [100, 110], []) | |
73 | + self.assertEqual("number", type) | |
74 | + | |
75 | + type = renderer._getXAxisType(self.req, ["100", "110"], []) | |
76 | + self.assertEqual("number", type) | |
77 | + | |
78 | + def test_to_js_array(self): | |
79 | + renderer = ReportRenderer(self.env) | |
80 | + | |
81 | + result = renderer._to_js_array([1, 2, 3]) | |
82 | + self.assertEqual("[1,2,3]", result) | |
83 | + | |
84 | + result = renderer._to_js_array([1, 'a', 3]) | |
85 | + self.assertEqual("['1','a','3']", result) | |
86 | + | |
87 | + result = renderer._to_js_array([]) | |
88 | + self.assertEqual("[]", result) | |
89 | + | |
90 | + result = renderer._to_js_array(None) | |
91 | + self.assertEqual("null", result) | |
92 | + | |
93 | + def test_convert_col(self): | |
94 | + renderer = ReportRenderer(self.env) | |
95 | + | |
96 | + col = renderer._convert_col(self.req, 'test', '1') | |
97 | + self.assertEqual('1', col) | |
98 | + | |
99 | + col = renderer._convert_col(self.req, '時刻', '100000') | |
100 | + self.assertEqual('12:46:40', col) | |
101 | + | |
102 | + col = renderer._convert_col(self.req, '日付', '100000') | |
103 | + self.assertEqual('1970/01/02', col) | |
104 | + | |
105 | + col = renderer._convert_col(self.req, '日時', '100000') | |
106 | + self.assertEqual('1970/01/02 12:46:40', col) | |
107 | + | |
108 | + col = renderer._convert_col(self.req, 'time', '100000') | |
109 | + self.assertEqual('12:46:40', col) | |
110 | + | |
111 | + col = renderer._convert_col(self.req, 'date', '100000') | |
112 | + self.assertEqual('1970/01/02', col) | |
113 | + | |
114 | + col = renderer._convert_col(self.req, 'datetime', '100000') | |
115 | + self.assertEqual('1970/01/02 12:46:40', col) | |
116 | + | |
117 | + def ticket(id): | |
118 | + return '/ticket/%s' % id | |
119 | + self.req.href.ticket = ticket | |
120 | + | |
121 | + col = renderer._convert_col(self.req, 'ticket', '100') | |
122 | + self.assertEqual('<a class="ticket" href="/ticket/100" title="View Ticket">#100</a>', | |
123 | + str(col)) | |
124 | + | |
125 | + col = renderer._convert_col(self.req, 'id', '100') | |
126 | + self.assertEqual('<a class="ticket" href="/ticket/100" title="View Ticket">#100</a>', | |
127 | + str(col)) | |
128 | + | |
129 | + col = renderer._convert_col(self.req, 'チケット', '100') | |
130 | + self.assertEqual('<a class="ticket" href="/ticket/100" title="View Ticket">#100</a>', | |
131 | + str(col)) | |
132 | + | |
133 | + | |
134 | +if __name__ == '__main__': | |
135 | + unittest.main() | |
136 | + |
@@ -1,277 +1,249 @@ | ||
1 | -//reportgraph.js | |
1 | +(function(jQuery) { | |
2 | + jQuery.fn.reportGraph = function(options) { | |
3 | + var defaults = { | |
4 | + 'graph': 'lines', | |
5 | + 'stack' : false, | |
6 | + 'legendLoc' : 'ne', | |
7 | + 'legendXOffset' : 0, | |
8 | + 'legendYOffset' : 0, | |
9 | + 'xaxisType' : 'string', | |
10 | + 'xaxisMin' : null, | |
11 | + 'xaxisMax' : null, | |
12 | + 'yaxisMin' : null, | |
13 | + 'yaxisMax' : null, | |
14 | + 'xaxisFormatString' : null, | |
15 | + 'yaxisFormatString' : null | |
16 | + }; | |
2 | 17 | |
18 | + var setting = jQuery.extend(defaults, options); | |
19 | + var id = this.attr('id'); | |
20 | + | |
21 | + var graph = _getGraph(setting); | |
22 | + graph.draw(id); | |
23 | + | |
24 | + return this; | |
25 | + }; | |
3 | 26 | |
4 | -$( function() { | |
5 | - /* | |
6 | - $(".reportgraph").each( | |
7 | - function() { | |
8 | - renderGraph(this); | |
9 | - }); | |
10 | - */ | |
11 | -}); | |
12 | - | |
13 | -function renderGraph(id) { | |
27 | + // Factory | |
28 | + function _getGraph(setting) { | |
29 | + if (setting.graph == 'lines') { | |
30 | + return new LineGraph(setting); | |
31 | + } | |
32 | + if (setting.graph == 'bars') { | |
33 | + return new BarGraph(setting); | |
34 | + } | |
35 | + if (setting.graph == 'pie') { | |
36 | + return new PieGraph(setting); | |
37 | + } | |
38 | + } | |
14 | 39 | |
15 | - var idstr = jQuery(id).attr('id').split("_")[1]; | |
16 | - // console.log(idstr); | |
17 | - | |
18 | - // option handling | |
19 | - var title = $("#reportgraphopt_" + idstr + " .title").text(); | |
20 | - | |
21 | - var opt_per = $("#reportgraphopt_" + idstr + " .per").text() | |
22 | - .toLowerCase(); | |
23 | - var x_minTickSize = opt_per == "week" ? [ 7, "day" ] : [ 1, "day" ]; | |
24 | - | |
25 | - var graphtype = $("#reportgraphopt_" + idstr + " .graph").text() | |
26 | - .toLowerCase(); | |
27 | - var dateFormatPattern = $("#reportgraphopt_" + idstr + " .dateFormat") | |
28 | - .text(); | |
29 | - var dateFormat = new DateFormat(dateFormatPattern); | |
30 | - | |
31 | - var cols = $("#reportgraphopt_" + idstr + " .cols").text().split(','); | |
32 | - | |
33 | - var isStackSeries = false; | |
34 | - if($("#reportgraphopt_" + idstr + " .stack").text().toLowerCase() == 'true') { | |
35 | - isStackSeries = true; | |
36 | - } | |
37 | - | |
38 | - var legendLoc = $("#reportgraphopt_" + idstr + " .legendLoc").text(); | |
39 | - var legendXOffset = $("#reportgraphopt_" + idstr + " .legendXOffset").text(); | |
40 | - var legendYOffset = $("#reportgraphopt_" + idstr + " .legendYOffset").text(); | |
41 | - | |
42 | - var xaxisMin = $("#reportgraphopt_" + idstr + " .xaxisMin").text(); | |
43 | - var xaxisMax = $("#reportgraphopt_" + idstr + " .xaxisMax").text(); | |
44 | - var yaxisMin = $("#reportgraphopt_" + idstr + " .yaxisMin").text(); | |
45 | - var yaxisMax = $("#reportgraphopt_" + idstr + " .yaxisMax").text(); | |
46 | - | |
47 | - if(xaxisMin == 'null') {xaxisMin = null;} | |
48 | - if(xaxisMax == 'null') {xaxisMax = null;} | |
49 | - if(yaxisMin == 'null') {yaxisMin = null;} | |
50 | - if(yaxisMax == 'null') {yaxisMax = null;} | |
51 | - | |
52 | - var xaxisFormatString = $("#reportgraphopt_" + idstr + " .xaxisFormatString").text(); | |
53 | - var yaxisFormatString = $("#reportgraphopt_" + idstr + " .yaxisFormatString").text(); | |
54 | - | |
55 | - // collect label | |
56 | - var table_headers = []; | |
57 | - $("#reportgraphtable_" + idstr + " thead tr th").each( function(col_index) { | |
58 | - table_headers.push($(this).text()); | |
59 | - }); | |
60 | - | |
61 | - var labels = [] | |
62 | - if (cols[0] == '') { | |
63 | - $.each(table_headers, function(table_headers_index, table_header) { | |
64 | - if (table_headers_index == 0) { | |
65 | - return true; | |
66 | - } | |
67 | - labels.push(table_header); | |
68 | - }); | |
69 | - } else { | |
70 | - // for selected column | |
71 | - $.each(table_headers, function(table_headers_index, table_header) { | |
72 | - $.each(cols, function(col_index, col) { | |
73 | - if (col == table_header) { | |
74 | - labels.push(table_header); | |
75 | - } | |
76 | - }); | |
77 | - }); | |
78 | - } | |
79 | - | |
80 | - var xaxis_values = getXaxisValues(idstr, 0, dateFormat); | |
81 | - | |
82 | - var isDateAxis = false; | |
83 | - var isNumAxis = false; | |
84 | - switch (xaxis_values['type']) { | |
85 | - case 'date': | |
86 | - isDateAxis = true; | |
87 | - case 'number': | |
88 | - isNumAxis = true; | |
89 | - } | |
90 | - var datas = getTableValues(idstr, xaxis_values, table_headers, dateFormat); | |
91 | - | |
92 | - switch (graphtype) { | |
93 | - case 'bars': | |
94 | - graph_opt = { | |
95 | - type : $.jqplot.BarRenderer, | |
96 | - opts : { | |
97 | - } | |
98 | - }; | |
99 | - break; | |
100 | - case 'lines': | |
101 | - default: | |
102 | - graph_opt = { | |
103 | - type : $.jqplot.LineRenderer, | |
104 | - opts : { | |
105 | - } | |
106 | - }; | |
107 | - } | |
108 | - | |
109 | - var xaxis_opts = {} | |
110 | - if (isDateAxis) { | |
111 | - xaxis_opts = { | |
112 | - renderer: $.jqplot.DateAxisRenderer, | |
113 | - min: xaxisMin, | |
114 | - max: xaxisMax | |
115 | - }; | |
116 | - } else if (isNumAxis) { | |
117 | - xaxis_opts = { | |
118 | - min: toNum(xaxisMin, null), | |
119 | - max: toNum(xaxisMax, null) | |
120 | - }; | |
121 | - } else { | |
122 | - xaxis_opts = { | |
123 | - renderer: $.jqplot.CategoryAxisRenderer, | |
124 | - min: xaxisMin, | |
125 | - max: xaxisMax, | |
126 | - ticks: xaxis_values['values'] | |
127 | - }; | |
128 | - } | |
129 | - xaxis_opts.tickOptions = {formatString: xaxisFormatString}; | |
130 | - | |
131 | - var yaxis_opts = { | |
132 | - tickOptions: {formatString: yaxisFormatString}, | |
133 | - min: toNum(yaxisMin, null), | |
134 | - max: toNum(yaxisMax, null) | |
135 | - }; | |
136 | - | |
137 | - var series = []; | |
138 | - $.each(labels, function(index, label) { | |
139 | - series.push({ | |
140 | - label: label, | |
141 | - renderer: graph_opt['type'] | |
142 | - }) | |
143 | - }); | |
144 | - | |
145 | - isFill = false; | |
146 | - if (isStackSeries && graphtype == 'lines') { | |
147 | - isFill = true; | |
148 | - } | |
149 | - plot1 = $.jqplot('placeholder_' + idstr, datas, { | |
150 | - legend: { | |
151 | - show: true, | |
152 | - location: legendLoc, | |
153 | - xoffset: Number(legendXOffset, 10), | |
154 | - yoffset: Number(legendYOffset, 10) | |
155 | - }, | |
156 | - title: title, | |
157 | - stackSeries: isStackSeries, | |
158 | - series: series, | |
159 | - seriesDefaults: { | |
160 | - fill: isFill | |
161 | - }, | |
162 | - axes: { | |
163 | - xaxis: xaxis_opts, | |
164 | - yaxis: yaxis_opts | |
165 | - }, | |
166 | - highlighter: { | |
167 | - sizeAdjust: 10 | |
168 | - }, | |
169 | - cursor: {show: true} | |
170 | - }); | |
171 | -} | |
172 | - | |
173 | -function toNum(str, defaultValue) { | |
174 | - if (str == null) { | |
175 | - return defaultValue; | |
176 | - } | |
177 | - num = Number(str, 10); | |
178 | - if (!isNaN(num)) { | |
179 | - return num; | |
180 | - } else { | |
181 | - return defaultValue; | |
40 | + /** | |
41 | + * Class: Graph | |
42 | + */ | |
43 | + function Graph(setting) { | |
44 | + this.setting = setting; | |
45 | + | |
46 | + this.data = this.getData(); | |
47 | + this.title = setting.title; | |
48 | + this.stack = setting.stack; | |
49 | + this.seriesDefaults = this.getSeriesDefaults(setting); | |
50 | + this.series = this.getSeries(setting); | |
51 | + this.legendOption = { | |
52 | + show: true, | |
53 | + location: setting.legendLoc, | |
54 | + xoffset: setting.legendXOffset, | |
55 | + yoffset: setting.legendYOffset | |
56 | + }; | |
57 | + this.xaxisOption = { | |
58 | + renderer: this.getXAxisRenderer(setting), | |
59 | + min: this.getXAxisMin(setting), | |
60 | + max: this.getXAxisMax(setting), | |
61 | + ticks: this.getTicks(setting), | |
62 | + numberTicks: this.getNumberTicks(setting), | |
63 | + tickOptions: {formatString: setting.xaxisFormatString} | |
64 | + }; | |
65 | + this.yaxisOption = { | |
66 | + min: setting.yaxisMin, | |
67 | + max: setting.yaxisMax, | |
68 | + tickOptions: {formatString: setting.yaxisFormatString} | |
69 | + }; | |
182 | 70 | } |
183 | -} | |
71 | + | |
72 | + Graph.prototype.draw = function(id) { | |
73 | + jQuery.jqplot(id, this.data, { | |
74 | + legend: this.legendOption, | |
75 | + title: this.title, | |
76 | + stackSeries: this.stack, | |
77 | + seriesDefaults: this.seriesDefaults, | |
78 | + series: this.series, | |
79 | + axes: { | |
80 | + xaxis: this.xaxisOption, | |
81 | + yaxis: this.yaxisOption | |
82 | + }, | |
83 | + highlighter: { | |
84 | + sizeAdjust: 10 | |
85 | + }, | |
86 | + cursor: {show: true} | |
87 | + }); | |
88 | + }; | |
89 | + | |
90 | + Graph.prototype.getSeriesDefaults = function() { | |
91 | + var seriesDefaults = {}; | |
92 | + seriesDefaults = { | |
93 | + renderer: this.getRenderer(this.setting), | |
94 | + rendererOptions:{ | |
95 | + barWidth: 10 | |
96 | + }, | |
97 | + fill: this.setting.stack && this.setting.graph == 'lines' | |
98 | + } | |
99 | + return seriesDefaults; | |
100 | + }; | |
101 | + | |
102 | + Graph.prototype.getRenderer = function() { | |
103 | + return jQuery.jqplot.LineRenderer; | |
104 | + }; | |
105 | + | |
106 | + Graph.prototype.getData = function() { | |
107 | + if (this.setting.xaxisType == 'date' || this.setting.xaxisType == 'number') { | |
108 | + return _getData(this.setting); | |
109 | + } | |
110 | + return this.setting.data; | |
111 | + }; | |
112 | + | |
113 | + Graph.prototype.getTicks = function() { | |
114 | + if (this.setting.xaxisType == 'date' || this.setting.xaxisType == 'number') { | |
115 | + return [] | |
116 | + } | |
117 | + return this.setting.ticks; | |
118 | + }; | |
119 | + | |
120 | + Graph.prototype.getNumberTicks = function() { | |
121 | + var number = this.setting.ticks.length; | |
122 | + return number; | |
123 | + }; | |
124 | + | |
125 | + Graph.prototype.getXAxisRenderer = function() { | |
126 | + if (this.setting.xaxisType == 'number') { | |
127 | + return jQuery.jqplot.LinerAxisRenderer; | |
128 | + } | |
129 | + if (this.setting.xaxisType == 'date') { | |
130 | + return jQuery.jqplot.DateAxisRenderer; | |
131 | + } | |
132 | + // string | |
133 | + return jQuery.jqplot.CategoryAxisRenderer; | |
134 | + }; | |
135 | + | |
136 | + Graph.prototype.getXAxisMin = function() { | |
137 | + if (this.setting.xaxisMin == null) { | |
138 | + return this.setting.ticks[0]; | |
139 | + } | |
140 | + return this.setting.xaxisMin; | |
141 | + }; | |
142 | + | |
143 | + Graph.prototype.getXAxisMax = function() { | |
144 | + if (this.setting.xaxisMax == null) { | |
145 | + return this.setting.ticks[this.setting.ticks.length-1]; | |
146 | + } | |
147 | + return this.setting.xaxisMax; | |
148 | + }; | |
149 | + | |
150 | + Graph.prototype.getSeries = function() { | |
151 | + var series = []; | |
152 | + jQuery.each(this.setting.seriesLabel, function(index, value){ | |
153 | + // use default renderer. | |
154 | + series.push({label: value}) | |
155 | + }); | |
156 | + return series; | |
157 | + }; | |
158 | + | |
159 | + /** | |
160 | + * Class: BarGraph | |
161 | + */ | |
162 | + function BarGraph(setting) { | |
163 | + Graph.apply(this, arguments); | |
164 | + } | |
165 | + | |
166 | + // extend Graph | |
167 | + jQuery.extend(BarGraph.prototype, Graph.prototype); | |
168 | + | |
169 | + BarGraph.prototype.getRenderer = function() { | |
170 | + return jQuery.jqplot.BarRenderer; | |
171 | + }; | |
184 | 172 | |
185 | -function convertData(str, dateFormat) { | |
186 | - // try convert to date | |
187 | - date = dateFormat.parse(str); | |
188 | - if (date != null) { | |
189 | - var strDate = new DateFormat("yyyy-MM-dd").format(date); | |
190 | - return ['date', strDate]; | |
173 | + BarGraph.prototype.getSeriesDefaults = function() { | |
174 | + var seriesDefaults = Graph.prototype.getSeriesDefaults.apply(this); | |
175 | + seriesDefaults.rendererOptions = { | |
176 | + barWidth: this.setting.barWidth | |
177 | + }; | |
178 | + return seriesDefaults; | |
179 | + }; | |
180 | + | |
181 | + /** | |
182 | + * Class: LineGraph | |
183 | + */ | |
184 | + function LineGraph(setting) { | |
185 | + Graph.apply(this, arguments); | |
191 | 186 | } |
187 | + | |
188 | + // extend Graph | |
189 | + jQuery.extend(LineGraph.prototype, Graph.prototype); | |
192 | 190 | |
193 | - // try convert to int | |
194 | - num = Number(str, 10); | |
195 | - if (!isNaN(num)) { | |
196 | - return ['number', num]; | |
191 | + LineGraph.prototype.getRenderer = function() { | |
192 | + return jQuery.jqplot.LineRenderer; | |
193 | + }; | |
194 | + | |
195 | + /** | |
196 | + * Class: PieGraph | |
197 | + */ | |
198 | + function PieGraph(setting) { | |
199 | + Graph.apply(this, arguments); | |
197 | 200 | } |
201 | + | |
202 | + // extend Graph | |
203 | + jQuery.extend(PieGraph.prototype, Graph.prototype); | |
198 | 204 | |
199 | - // for string. create ticks. | |
200 | - return ['str', str]; | |
201 | -} | |
202 | - | |
203 | -function getTableValues(tableId, xaxis_values, table_headers, dateFormat) { | |
205 | + PieGraph.prototype.getRenderer = function() { | |
206 | + return jQuery.jqplot.PieRenderer; | |
207 | + }; | |
204 | 208 | |
205 | - var tmp = {}; | |
209 | + PieGraph.prototype.getData = function() { | |
210 | + return _getData(this.setting); | |
211 | + }; | |
206 | 212 | |
207 | - $.each(table_headers, function(index, table_header) { | |
208 | - tmp[table_header] = []; | |
209 | - } | |
210 | - ); | |
213 | + PieGraph.prototype.getTicks = function() { | |
214 | + return [] | |
215 | + }; | |
216 | + | |
217 | + PieGraph.prototype.getXAxisRenderer = function() { | |
218 | + return jQuery.jqplot.LinerAxisRenderer; | |
219 | + }; | |
220 | + | |
221 | + // Utility | |
222 | + function _getData(setting) { | |
223 | + var rtn = []; | |
224 | + jQuery.each(setting.data, function(seriesIndex, seriesValue) { | |
225 | + var newSeries = []; | |
226 | + | |
227 | + // caluculate sum for pie. | |
228 | + var sum = 0; | |
229 | + if (setting.graph == 'pie') { | |
230 | + jQuery.each(seriesValue, function(dataIndex, dataValue) { | |
231 | + sum += dataValue; | |
232 | + }); | |
233 | + } | |
211 | 234 | |
212 | - $("#reportgraphtable_" + tableId + " tbody tr").each( | |
213 | - function(row_index) { | |
214 | - $(this).children().each(function(col_index) { | |
215 | - | |
216 | - // for xaxis column | |
217 | - if(col_index == xaxis_values['colIndex']) { | |
218 | - return true; | |
235 | + // create series. | |
236 | + jQuery.each(seriesValue, function(dataIndex, dataValue) { | |
237 | + var newTick = setting.ticks[dataIndex]; | |
238 | + if (setting.graph == 'pie') { | |
239 | + newTick += ': ' + dataValue + ' (' + Math.round((dataValue/sum)*100*100)/100 + '%)'; | |
219 | 240 | } |
220 | - | |
221 | - table_header = table_headers[col_index]; | |
222 | - | |
223 | - value = $(this).text(); | |
224 | - if (value == '') { | |
225 | - return true; | |
226 | - } | |
227 | - value = Number(value, 10); | |
228 | - | |
229 | - if (xaxis_values['type'] == 'str') { | |
230 | - tmp[table_header].push(value); | |
231 | - } else { | |
232 | - tmp[table_header].push([xaxis_values['values'][row_index], value]); | |
233 | - } | |
241 | + newSeries.push([newTick, dataValue]) | |
234 | 242 | }); |
243 | + rtn.push(newSeries); | |
235 | 244 | }); |
236 | - | |
237 | - var datas = []; | |
238 | - $.each(table_headers, function(index, table_header) { | |
239 | - // for xaxis column | |
240 | - if(index == xaxis_values['colIndex']) { | |
241 | - return true; | |
242 | - } | |
243 | - datas.push(tmp[table_header]); | |
244 | - } | |
245 | - ); | |
245 | + return rtn; | |
246 | + } | |
246 | 247 | |
247 | - return datas; | |
248 | -} | |
248 | +})(jQuery); | |
249 | 249 | |
250 | -function getXaxisValues(tableId, index, dateFormat) { | |
251 | - var values = [] | |
252 | - var type = 'str'; | |
253 | - | |
254 | - $("#reportgraphtable_" + tableId + " tbody tr").each( | |
255 | - function(row_index) { | |
256 | - $(this).children().each( function(col_index) { | |
257 | - if (col_index == index) { | |
258 | - | |
259 | - str = $(this).text(); | |
260 | - xaxis_value = convertData(str, dateFormat); | |
261 | - | |
262 | - type = xaxis_value[0]; | |
263 | - values.push(xaxis_value[1]); | |
264 | - } | |
265 | - }); | |
266 | - } | |
267 | - ); | |
268 | - | |
269 | - var xaxis_values = { | |
270 | - colIndex: index, | |
271 | - type: type, | |
272 | - values: values | |
273 | - }; | |
274 | - | |
275 | - // console.log("xaxis_values=" + xaxis_values['values']) | |
276 | - return xaxis_values; | |
277 | -} |
@@ -86,10 +86,11 @@ | ||
86 | 86 | * yaxisMax: Y軸の最大値を指定します。指定しない場合は、グラフデータより自動的に計算されます。 |
87 | 87 | |
88 | 88 | 表からグラフの生成は、以下のルールに従って行われます。 |
89 | - * 一番左の列が、X軸の値になります。 | |
89 | + * グラフの種類がlines,barsの場合は、1列目がX軸の値になります。 | |
90 | 90 | * デフォルトではyyyy-MM-dd形式の場合は日付と見なして、時系列データとして扱います。 |
91 | 91 | * 2列目以降がグラフのデータとなります。 |
92 | 92 | * ヘッダ行の値がラベルになります。 |
93 | + * グラフの種類がpieの場合は、1列目が表示項目のラベル、2列目がその値として描画します。 | |
93 | 94 | |
94 | 95 | 例: |
95 | 96 | * report:1 を表示する。 |
@@ -130,7 +131,6 @@ | ||
130 | 131 | |
131 | 132 | def _add_script(self, req): |
132 | 133 | if not hasattr(req, '_reportinclude'): |
133 | - add_script(req, 'reportinclude/js/dateformat.js') | |
134 | 134 | #add_stylesheet(req, 'reportinclude/css/reportinclude.css') |
135 | 135 | |
136 | 136 | # add script and css for jqplot |
@@ -138,6 +138,7 @@ | ||
138 | 138 | add_script(req, 'reportinclude/js/jqplot/jquery.jqplot.min.js') |
139 | 139 | add_script(req, 'reportinclude/js/jqplot/jqplot.pointLabels.min.js') |
140 | 140 | add_script(req, 'reportinclude/js/jqplot/jqplot.barRenderer.min.js') |
141 | + add_script(req, 'reportinclude/js/jqplot/jqplot.pieRenderer.min.js') | |
141 | 142 | add_script(req, 'reportinclude/js/jqplot/jqplot.categoryAxisRenderer.min.js') |
142 | 143 | add_script(req, 'reportinclude/js/jqplot/jqplot.dateAxisRenderer.min.js') |
143 | 144 | add_script(req, 'reportinclude/js/jqplot/jqplot.cursor.min.js') |
@@ -3,8 +3,10 @@ | ||
3 | 3 | from genshi.builder import tag |
4 | 4 | from genshi.filters import Transformer |
5 | 5 | |
6 | +from trac.core import TracError | |
7 | +from trac.resource import ResourceNotFound | |
6 | 8 | from trac.ticket.report import ReportModule |
7 | -from trac.util.datefmt import format_datetime, format_date, format_time | |
9 | +from trac.util.datefmt import format_datetime, format_date, format_time, parse_date | |
8 | 10 | |
9 | 11 | class ReportRenderer(object): |
10 | 12 |
@@ -25,15 +27,15 @@ | ||
25 | 27 | if opts['title'] == '': |
26 | 28 | opts['title'] = title |
27 | 29 | |
28 | - table = self._render_table(req, id, vars, opts, title, sql, desc) | |
29 | - return self._render_graph(req, opts, table, title, desc) | |
30 | + table, columns, table_data = self._render_table(req, id, vars, opts, title, sql, desc) | |
31 | + table = self._render_graph(req, opts, table, columns, table_data, title, desc) | |
32 | + return table | |
30 | 33 | else: |
31 | 34 | raise ResourceNotFound( |
32 | 35 | _('Report [%(num)] does not exist.', num=id), |
33 | 36 | _('Invalid Report Number')) |
34 | - | |
37 | + | |
35 | 38 | def _render_table(self, req, id, vars, opts, title, sql, desc): |
36 | - | |
37 | 39 | db = self.env.get_db_cnx() |
38 | 40 | |
39 | 41 | sql, vars = self._sql_sub_vars(sql, vars, db) |
@@ -69,6 +71,10 @@ | ||
69 | 71 | tbody = tag.tbody() |
70 | 72 | table.append(tbody) |
71 | 73 | |
74 | + table_data = {} | |
75 | + for col in columns: | |
76 | + table_data[col] = [] | |
77 | + | |
72 | 78 | for idx, row in enumerate(cursor): |
73 | 79 | |
74 | 80 | odd_or_even = (idx % 2 == 0) and 'odd' or 'even' |
@@ -77,6 +83,7 @@ | ||
77 | 83 | |
78 | 84 | for col_idx, col in enumerate(row): |
79 | 85 | column = columns[col_idx] |
86 | + | |
80 | 87 | if column == '__color__' and css_class is '': |
81 | 88 | css_class = 'color%s-' % col |
82 | 89 | continue |
@@ -83,18 +90,10 @@ | ||
83 | 90 | if column.startswith('_'): |
84 | 91 | continue |
85 | 92 | |
86 | - if column == 'time': | |
87 | - col = col != None and format_time(int(col)) or '--' | |
88 | - if column in ('date', 'created', 'modified'): | |
89 | - col = col != None and format_date(int(col)) or '--' | |
90 | - if column == 'datetime': | |
91 | - col = col != None and format_datetime(int(col)) or '--' | |
92 | - | |
93 | - if column.lower() in ('ticket', 'id', u'チケット'): | |
94 | - col = tag.a('#' + str(col), title='View Ticket', class_='ticket', | |
95 | - href=req.href.ticket(str(col))) | |
93 | + converted_col = self._convert_col(req, column, col) | |
96 | 94 | |
97 | - tr = tr(tag.td(col)) | |
95 | + tr = tr(tag.td(converted_col)) | |
96 | + table_data[column].append(col) | |
98 | 97 | |
99 | 98 | css_class = css_class + odd_or_even |
100 | 99 | tr = tr(class_ = css_class) |
@@ -101,36 +100,67 @@ | ||
101 | 100 | tbody.append(tr) |
102 | 101 | |
103 | 102 | #add_stylesheet(req, 'wikitable/css/wikitable.css') |
104 | - return table | |
103 | + return table, columns, table_data | |
105 | 104 | |
106 | - def _render_graph(self, req, opts, table, title, desc): | |
107 | - | |
108 | - if opts['graph'] != 'lines' and opts['graph'] != 'bars': | |
105 | + def _render_graph(self, req, opts, table, columns, table_data, title, desc): | |
106 | + u"""TracReportsの結果をグラフで表示する。 | |
107 | + """ | |
108 | + if opts['graph'] not in ('lines', 'bars', 'pie'): | |
109 | 109 | return table |
110 | + | |
111 | + ticks = table_data[columns[0]] | |
112 | + | |
113 | + data = '[' | |
114 | + for column in columns[1:]: | |
115 | + #print table_data[column] | |
116 | + values = self._to_js_array(table_data[column]) | |
117 | + data = data + values + ',' | |
118 | + data = data[:-1] + ']' | |
119 | + | |
120 | + xaxisType = self._getXAxisType(req, ticks, opts) | |
110 | 121 | |
111 | - opttag = tag.div(id="reportgraphopt_%d" % (self.index), | |
112 | - style="display:none") | |
113 | - | |
114 | - for opt in opts: | |
115 | - opttag.append(tag.span(opts[opt], class_=opt)) | |
116 | - | |
117 | 122 | script = """ |
118 | 123 | jQuery(document).ready(function($) { |
119 | - renderGraph("#reportgraph_%d"); | |
124 | + $('#reportgraph_%d').reportGraph({ | |
125 | + graph: '%s', | |
126 | + seriesLabel: %s, | |
127 | + data: %s, | |
128 | + ticks: %s, | |
129 | + title: '%s', | |
130 | + stack: %s, | |
131 | + legendLoc: '%s', | |
132 | + legendXOffset: %s, | |
133 | + legendYOffset: %s, | |
134 | + xaxisType: '%s', | |
135 | + xaxisMin: %s, | |
136 | + xaxisMax: %s, | |
137 | + yaxisMin: %s, | |
138 | + yaxisMax: %s, | |
139 | + xaxisFormatString: '%s', | |
140 | + yaxisFormatString: '%s', | |
141 | + barWidth: '%s' | |
142 | + }); | |
120 | 143 | }); |
121 | - """ % (self.index) | |
144 | + """ % (self.index, opts['graph'], self._to_js_array(columns[1:]), | |
145 | + data, self._to_js_array(ticks), | |
146 | + opts['title'], opts['stack'].lower(), opts['legendLoc'], | |
147 | + opts['legendXOffset'], opts['legendYOffset'], | |
148 | + xaxisType, opts['xaxisMin'], opts['xaxisMax'], | |
149 | + opts['yaxisMin'], opts['yaxisMax'], | |
150 | + opts['xaxisFormatString'], opts['yaxisFormatString'], opts['barWidth']) | |
151 | + | |
152 | + print script | |
122 | 153 | |
123 | 154 | div = tag.div( |
124 | 155 | tag.div(' ', |
125 | - id="placeholder_%d" % (self.index), | |
156 | + id="reportgraph_%d" % (self.index), | |
126 | 157 | style="width:%spx;height:%spx;" % |
127 | 158 | (opts["width"],opts["height"])), |
128 | - opttag, | |
129 | 159 | tag.br(), |
130 | 160 | table, |
131 | 161 | tag.script(script, type="text/javascript"), |
132 | 162 | class_="reportgraph", |
133 | - id="reportgraph_%d" % (self.index) | |
163 | + id="reporinclude_%d" % (self.index) | |
134 | 164 | ) |
135 | 165 | return div |
136 | 166 |
@@ -146,7 +176,7 @@ | ||
146 | 176 | 'graph':'', |
147 | 177 | 'table':'inline', |
148 | 178 | 'dateFormat':'yyyy-MM-dd', |
149 | - 'stack':False, | |
179 | + 'stack':'false', | |
150 | 180 | 'legendLoc':'ne', |
151 | 181 | 'legendXOffset':'12', |
152 | 182 | 'legendYOffset':'12', |
@@ -155,7 +185,8 @@ | ||
155 | 185 | 'yaxisMin':'null', |
156 | 186 | 'yaxisMax':'null', |
157 | 187 | 'xaxisFormatString':'', |
158 | - 'yaxisFormatString':'' | |
188 | + 'yaxisFormatString':'', | |
189 | + 'barWidth':'50' | |
159 | 190 | } |
160 | 191 | |
161 | 192 | vars = {} |
@@ -214,3 +245,52 @@ | ||
214 | 245 | rm = ReportModule(self.env) |
215 | 246 | sql, vars = rm.sql_sub_vars(sql, vars, db) |
216 | 247 | return sql, vars |
248 | + | |
249 | + def _getXAxisType(self, req, ticks, opts): | |
250 | + if ticks and len(ticks) > 0: | |
251 | + try: | |
252 | + int(ticks[0]) | |
253 | + return 'number' | |
254 | + except ValueError: | |
255 | + pass | |
256 | + | |
257 | + try: | |
258 | + parse_date(ticks[0], req.tz) | |
259 | + return 'date' | |
260 | + except TracError: | |
261 | + pass | |
262 | + | |
263 | + return 'string' | |
264 | + | |
265 | + def _to_js_array(self, array): | |
266 | + if array is None: | |
267 | + return 'null' | |
268 | + if len(array) == 0: | |
269 | + return '[]' | |
270 | + | |
271 | + is_number = True | |
272 | + for value in array: | |
273 | + try: | |
274 | + int(value) | |
275 | + except ValueError: | |
276 | + is_number = False | |
277 | + pass | |
278 | + | |
279 | + if is_number: | |
280 | + return "[" + ",".join(map(str, array)) + "]" | |
281 | + else: | |
282 | + return "['" + "','".join(map(str, array)) + "']" | |
283 | + | |
284 | + def _convert_col(self, req, column, col): | |
285 | + if column == 'time' or column.endswith(u'時刻'): | |
286 | + col = col != None and format_time(int(col)) or '--' | |
287 | + if column in ('date', 'created', 'modified') or column.endswith(u'日付'): | |
288 | + print col | |
289 | + col = col != None and format_date(int(col)) or '--' | |
290 | + if column == 'datetime' or column.endswith(u'日時'): | |
291 | + col = col != None and format_datetime(int(col)) or '--' | |
292 | + | |
293 | + if column.lower() in ('ticket', 'id', u'チケット'): | |
294 | + col = tag.a('#' + str(col), title='View Ticket', class_='ticket', | |
295 | + href=req.href.ticket(str(col))) | |
296 | + return col |
@@ -8,12 +8,12 @@ | ||
8 | 8 | author_email='wadahiro@gmail.com', |
9 | 9 | url = "http://sourceforge.jp/projects/shibuya-trac/wiki/plugins%2FReportIncludePlugin", |
10 | 10 | description='Trac plugin for include a report (with graph) in a wiki page', |
11 | - version='0.4.3', | |
11 | + version='0.5-SNAPSHOT', | |
12 | 12 | license='New BSD', |
13 | 13 | packages=find_packages(exclude=['*.tests*']), |
14 | 14 | package_data={'reportinclude': ['htdocs/css/*.css', 'htdocs/css/jqplot/*.css', 'htdocs/js/*.js', |
15 | 15 | 'htdocs/js/flot/*.js', 'htdocs/js/jqplot/*.js', 'htdocs/image/*.gif', |
16 | - 'templates/*.html']}, | |
16 | + 'templates/*.html', 'htdocs/*.html']}, | |
17 | 17 | entry_points={ |
18 | 18 | 'trac.plugins': [ |
19 | 19 | 'reportinclude = reportinclude' |