rspec_log_formatter 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -13
- data/.gitignore +1 -1
- data/.ruby-version +1 -1
- data/README.md +53 -7
- data/bin/rspec_log_formatter +30 -0
- data/dummy/.gitignore +16 -0
- data/dummy/.rspec +1 -0
- data/dummy/Gemfile +47 -0
- data/dummy/README.rdoc +28 -0
- data/dummy/Rakefile +6 -0
- data/dummy/app/assets/images/.keep +0 -0
- data/dummy/app/assets/javascripts/application.js +16 -0
- data/dummy/app/assets/stylesheets/application.css +13 -0
- data/dummy/app/controllers/application_controller.rb +5 -0
- data/dummy/app/controllers/concerns/.keep +0 -0
- data/dummy/app/helpers/application_helper.rb +2 -0
- data/dummy/app/mailers/.keep +0 -0
- data/dummy/app/models/.keep +0 -0
- data/dummy/app/models/concerns/.keep +0 -0
- data/dummy/app/views/layouts/application.html.erb +14 -0
- data/dummy/bin/bundle +3 -0
- data/dummy/bin/rails +4 -0
- data/dummy/bin/rake +4 -0
- data/dummy/config/application.rb +23 -0
- data/dummy/config/boot.rb +4 -0
- data/dummy/config/database.yml +25 -0
- data/dummy/config/environment.rb +5 -0
- data/dummy/config/environments/development.rb +29 -0
- data/dummy/config/environments/production.rb +80 -0
- data/dummy/config/environments/test.rb +36 -0
- data/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/dummy/config/initializers/inflections.rb +16 -0
- data/dummy/config/initializers/mime_types.rb +5 -0
- data/dummy/config/initializers/secret_token.rb +12 -0
- data/dummy/config/initializers/session_store.rb +3 -0
- data/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/dummy/config/locales/en.yml +23 -0
- data/dummy/config/routes.rb +56 -0
- data/dummy/config.ru +4 -0
- data/dummy/db/schema.rb +16 -0
- data/dummy/db/seeds.rb +7 -0
- data/dummy/lib/assets/.keep +0 -0
- data/dummy/lib/tasks/.keep +0 -0
- data/dummy/log/.keep +0 -0
- data/dummy/public/404.html +58 -0
- data/dummy/public/422.html +58 -0
- data/dummy/public/500.html +57 -0
- data/dummy/public/favicon.ico +0 -0
- data/dummy/public/robots.txt +5 -0
- data/dummy/spec/dummy_spec.rb +7 -0
- data/dummy/spec/spec_helper.rb +46 -0
- data/dummy/test/controllers/.keep +0 -0
- data/dummy/test/fixtures/.keep +0 -0
- data/dummy/test/helpers/.keep +0 -0
- data/dummy/test/integration/.keep +0 -0
- data/dummy/test/mailers/.keep +0 -0
- data/dummy/test/models/.keep +0 -0
- data/dummy/test/test_helper.rb +15 -0
- data/dummy/vendor/assets/javascripts/.keep +0 -0
- data/dummy/vendor/assets/stylesheets/.keep +0 -0
- data/lib/rspec_log_formatter/analysis/analyzer.rb +15 -50
- data/lib/rspec_log_formatter/analysis/pretty_printer.rb +10 -6
- data/lib/rspec_log_formatter/analysis/result.rb +8 -4
- data/lib/rspec_log_formatter/analysis/score.rb +42 -6
- data/lib/rspec_log_formatter/analyzer_formatter.rb +18 -6
- data/lib/rspec_log_formatter/formatter.rb +19 -12
- data/lib/rspec_log_formatter/history_manager.rb +46 -0
- data/lib/rspec_log_formatter/javascripts/chartkick.js +678 -0
- data/lib/rspec_log_formatter/performance_analyzer.rb +40 -0
- data/lib/rspec_log_formatter/templates/charts.html.erb +11 -0
- data/lib/rspec_log_formatter/version.rb +1 -1
- data/lib/rspec_log_formatter.rb +2 -1
- data/rspec_log_formatter.gemspec +1 -0
- data/spec/fixtures/test_slowing_down_over_time.history +7 -0
- data/spec/fixtures/varying_flakiness.history +9 -9
- data/spec/lib/rspec_log_analyzer/analysis/analyzer_spec.rb +25 -21
- data/spec/lib/rspec_log_analyzer/analysis/pretty_printer_spec.rb +10 -8
- data/spec/lib/rspec_log_analyzer/analyzer_formatter_spec.rb +3 -3
- data/spec/lib/rspec_log_analyzer/formatter_spec.rb +16 -18
- data/spec/lib/rspec_log_analyzer/history_manager_spec.rb +16 -0
- data/spec/lib/rspec_log_analyzer/performance_analyzer_spec.rb +16 -0
- data/specs.sh +4 -0
- metadata +95 -12
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'csv'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module RspecLogFormatter
|
5
|
+
class HistoryManager
|
6
|
+
def initialize(filepath)
|
7
|
+
@filepath = filepath
|
8
|
+
end
|
9
|
+
|
10
|
+
def builds
|
11
|
+
results.map{|r| r.build_number.to_i}.reduce(SortedSet.new, &:<<).to_a
|
12
|
+
end
|
13
|
+
|
14
|
+
def truncate(number_of_builds)
|
15
|
+
kept_builds = builds.to_a.last(number_of_builds)
|
16
|
+
sio = StringIO.new
|
17
|
+
|
18
|
+
lines.each do |line|
|
19
|
+
sio.puts line if kept_builds.include? (parse(line).build_number.to_i)
|
20
|
+
end
|
21
|
+
|
22
|
+
sio.rewind
|
23
|
+
|
24
|
+
File.open(@filepath, 'w') do |f|
|
25
|
+
f.write sio.read
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def results
|
30
|
+
lines.map do |line|
|
31
|
+
parse(line)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def parse(line)
|
38
|
+
RspecLogFormatter::Analysis::Result.new(*CSV.parse_line(line, col_sep: "\t").first(8))
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
def lines
|
43
|
+
File.open(@filepath, 'r').lazy
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,678 @@
|
|
1
|
+
/*
|
2
|
+
* Chartkick.js
|
3
|
+
* Create beautiful Javascript charts with minimal code
|
4
|
+
* https://github.com/ankane/chartkick.js
|
5
|
+
* v1.1.1
|
6
|
+
* MIT License
|
7
|
+
*/
|
8
|
+
|
9
|
+
/*jslint browser: true, indent: 2, plusplus: true, vars: true */
|
10
|
+
/*global google, Highcharts, $*/
|
11
|
+
|
12
|
+
(function () {
|
13
|
+
'use strict';
|
14
|
+
|
15
|
+
var Chartkick, ISO8601_PATTERN, DECIMAL_SEPARATOR, defaultOptions, hideLegend,
|
16
|
+
setMin, setMax, setStacked, jsOptions, loaded, waitForLoaded, setBarMin, setBarMax, createDataTable, resize;
|
17
|
+
|
18
|
+
// only functions that need defined specific to charting library
|
19
|
+
var renderLineChart, renderPieChart, renderColumnChart, renderBarChart, renderAreaChart;
|
20
|
+
|
21
|
+
// helpers
|
22
|
+
|
23
|
+
function isArray(variable) {
|
24
|
+
return Object.prototype.toString.call(variable) === "[object Array]";
|
25
|
+
}
|
26
|
+
|
27
|
+
function isFunction(variable) {
|
28
|
+
return variable instanceof Function;
|
29
|
+
}
|
30
|
+
|
31
|
+
function isPlainObject(variable) {
|
32
|
+
return !isFunction(variable) && variable instanceof Object;
|
33
|
+
}
|
34
|
+
|
35
|
+
// https://github.com/madrobby/zepto/blob/master/src/zepto.js
|
36
|
+
function extend(target, source) {
|
37
|
+
var key;
|
38
|
+
for (key in source) {
|
39
|
+
if (isPlainObject(source[key]) || isArray(source[key])) {
|
40
|
+
if (isPlainObject(source[key]) && !isPlainObject(target[key])) {
|
41
|
+
target[key] = {};
|
42
|
+
}
|
43
|
+
if (isArray(source[key]) && !isArray(target[key])) {
|
44
|
+
target[key] = [];
|
45
|
+
}
|
46
|
+
extend(target[key], source[key]);
|
47
|
+
} else if (source[key] !== undefined) {
|
48
|
+
target[key] = source[key];
|
49
|
+
}
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
function merge(obj1, obj2) {
|
54
|
+
var target = {};
|
55
|
+
extend(target, obj1);
|
56
|
+
extend(target, obj2);
|
57
|
+
return target;
|
58
|
+
}
|
59
|
+
|
60
|
+
// https://github.com/Do/iso8601.js
|
61
|
+
ISO8601_PATTERN = /(\d\d\d\d)(\-)?(\d\d)(\-)?(\d\d)(T)?(\d\d)(:)?(\d\d)?(:)?(\d\d)?([\.,]\d+)?($|Z|([\+\-])(\d\d)(:)?(\d\d)?)/i;
|
62
|
+
DECIMAL_SEPARATOR = String(1.5).charAt(1);
|
63
|
+
|
64
|
+
function parseISO8601(input) {
|
65
|
+
var day, hour, matches, milliseconds, minutes, month, offset, result, seconds, type, year;
|
66
|
+
type = Object.prototype.toString.call(input);
|
67
|
+
if (type === '[object Date]') {
|
68
|
+
return input;
|
69
|
+
}
|
70
|
+
if (type !== '[object String]') {
|
71
|
+
return;
|
72
|
+
}
|
73
|
+
if (matches = input.match(ISO8601_PATTERN)) {
|
74
|
+
year = parseInt(matches[1], 10);
|
75
|
+
month = parseInt(matches[3], 10) - 1;
|
76
|
+
day = parseInt(matches[5], 10);
|
77
|
+
hour = parseInt(matches[7], 10);
|
78
|
+
minutes = matches[9] ? parseInt(matches[9], 10) : 0;
|
79
|
+
seconds = matches[11] ? parseInt(matches[11], 10) : 0;
|
80
|
+
milliseconds = matches[12] ? parseFloat(DECIMAL_SEPARATOR + matches[12].slice(1)) * 1000 : 0;
|
81
|
+
result = Date.UTC(year, month, day, hour, minutes, seconds, milliseconds);
|
82
|
+
if (matches[13] && matches[14]) {
|
83
|
+
offset = matches[15] * 60;
|
84
|
+
if (matches[17]) {
|
85
|
+
offset += parseInt(matches[17], 10);
|
86
|
+
}
|
87
|
+
offset *= matches[14] === '-' ? -1 : 1;
|
88
|
+
result -= offset * 60 * 1000;
|
89
|
+
}
|
90
|
+
return new Date(result);
|
91
|
+
}
|
92
|
+
}
|
93
|
+
// end iso8601.js
|
94
|
+
|
95
|
+
function negativeValues(series) {
|
96
|
+
var i, j, data;
|
97
|
+
for (i = 0; i < series.length; i++) {
|
98
|
+
data = series[i].data;
|
99
|
+
for (j = 0; j < data.length; j++) {
|
100
|
+
if (data[j][1] < 0) {
|
101
|
+
return true;
|
102
|
+
}
|
103
|
+
}
|
104
|
+
}
|
105
|
+
return false;
|
106
|
+
}
|
107
|
+
|
108
|
+
function jsOptionsFunc(defaultOptions, hideLegend, setMin, setMax, setStacked) {
|
109
|
+
return function (series, opts, chartOptions) {
|
110
|
+
var options = merge({}, defaultOptions);
|
111
|
+
options = merge(options, chartOptions || {});
|
112
|
+
|
113
|
+
// hide legend
|
114
|
+
// this is *not* an external option!
|
115
|
+
if (opts.hideLegend) {
|
116
|
+
hideLegend(options);
|
117
|
+
}
|
118
|
+
|
119
|
+
// min
|
120
|
+
if ("min" in opts) {
|
121
|
+
setMin(options, opts.min);
|
122
|
+
} else if (!negativeValues(series)) {
|
123
|
+
setMin(options, 0);
|
124
|
+
}
|
125
|
+
|
126
|
+
// max
|
127
|
+
if ("max" in opts) {
|
128
|
+
setMax(options, opts.max);
|
129
|
+
}
|
130
|
+
|
131
|
+
if (opts.stacked) {
|
132
|
+
setStacked(options);
|
133
|
+
}
|
134
|
+
|
135
|
+
// merge library last
|
136
|
+
options = merge(options, opts.library || {});
|
137
|
+
|
138
|
+
return options;
|
139
|
+
};
|
140
|
+
}
|
141
|
+
|
142
|
+
function setText(element, text) {
|
143
|
+
if (document.body.innerText) {
|
144
|
+
element.innerText = text;
|
145
|
+
} else {
|
146
|
+
element.textContent = text;
|
147
|
+
}
|
148
|
+
}
|
149
|
+
|
150
|
+
function chartError(element, message) {
|
151
|
+
setText(element, "Error Loading Chart: " + message);
|
152
|
+
element.style.color = "#ff0000";
|
153
|
+
}
|
154
|
+
|
155
|
+
function getJSON(element, url, success) {
|
156
|
+
jQuery.ajax({
|
157
|
+
dataType: "json",
|
158
|
+
url: url,
|
159
|
+
success: success,
|
160
|
+
error: function (jqXHR, textStatus, errorThrown) {
|
161
|
+
var message = (typeof errorThrown === "string") ? errorThrown : errorThrown.message;
|
162
|
+
chartError(element, message);
|
163
|
+
}
|
164
|
+
});
|
165
|
+
}
|
166
|
+
|
167
|
+
function errorCatcher(element, data, opts, callback) {
|
168
|
+
try {
|
169
|
+
callback(element, data, opts);
|
170
|
+
} catch (err) {
|
171
|
+
chartError(element, err.message);
|
172
|
+
throw err;
|
173
|
+
}
|
174
|
+
}
|
175
|
+
|
176
|
+
function fetchDataSource(element, dataSource, opts, callback) {
|
177
|
+
if (typeof dataSource === "string") {
|
178
|
+
getJSON(element, dataSource, function (data, textStatus, jqXHR) {
|
179
|
+
errorCatcher(element, data, opts, callback);
|
180
|
+
});
|
181
|
+
} else {
|
182
|
+
errorCatcher(element, dataSource, opts, callback);
|
183
|
+
}
|
184
|
+
}
|
185
|
+
|
186
|
+
// type conversions
|
187
|
+
|
188
|
+
function toStr(n) {
|
189
|
+
return "" + n;
|
190
|
+
}
|
191
|
+
|
192
|
+
function toFloat(n) {
|
193
|
+
return parseFloat(n);
|
194
|
+
}
|
195
|
+
|
196
|
+
function toDate(n) {
|
197
|
+
if (typeof n !== "object") {
|
198
|
+
if (typeof n === "number") {
|
199
|
+
n = new Date(n * 1000); // ms
|
200
|
+
} else { // str
|
201
|
+
// try our best to get the str into iso8601
|
202
|
+
// TODO be smarter about this
|
203
|
+
var str = n.replace(/ /, "T").replace(" ", "").replace("UTC", "Z");
|
204
|
+
n = parseISO8601(str) || new Date(n);
|
205
|
+
}
|
206
|
+
}
|
207
|
+
return n;
|
208
|
+
}
|
209
|
+
|
210
|
+
function toArr(n) {
|
211
|
+
if (!isArray(n)) {
|
212
|
+
var arr = [], i;
|
213
|
+
for (i in n) {
|
214
|
+
if (n.hasOwnProperty(i)) {
|
215
|
+
arr.push([i, n[i]]);
|
216
|
+
}
|
217
|
+
}
|
218
|
+
n = arr;
|
219
|
+
}
|
220
|
+
return n;
|
221
|
+
}
|
222
|
+
|
223
|
+
function sortByTime(a, b) {
|
224
|
+
return a[0].getTime() - b[0].getTime();
|
225
|
+
}
|
226
|
+
|
227
|
+
if ("Highcharts" in window) {
|
228
|
+
|
229
|
+
defaultOptions = {
|
230
|
+
chart: {},
|
231
|
+
xAxis: {
|
232
|
+
labels: {
|
233
|
+
style: {
|
234
|
+
fontSize: "12px"
|
235
|
+
}
|
236
|
+
}
|
237
|
+
},
|
238
|
+
yAxis: {
|
239
|
+
title: {
|
240
|
+
text: null
|
241
|
+
},
|
242
|
+
labels: {
|
243
|
+
style: {
|
244
|
+
fontSize: "12px"
|
245
|
+
}
|
246
|
+
}
|
247
|
+
},
|
248
|
+
title: {
|
249
|
+
text: null
|
250
|
+
},
|
251
|
+
credits: {
|
252
|
+
enabled: false
|
253
|
+
},
|
254
|
+
legend: {
|
255
|
+
borderWidth: 0
|
256
|
+
},
|
257
|
+
tooltip: {
|
258
|
+
style: {
|
259
|
+
fontSize: "12px"
|
260
|
+
}
|
261
|
+
},
|
262
|
+
plotOptions: {
|
263
|
+
areaspline: {},
|
264
|
+
series: {
|
265
|
+
marker: {}
|
266
|
+
}
|
267
|
+
}
|
268
|
+
};
|
269
|
+
|
270
|
+
hideLegend = function (options) {
|
271
|
+
options.legend.enabled = false;
|
272
|
+
};
|
273
|
+
|
274
|
+
setMin = function (options, min) {
|
275
|
+
options.yAxis.min = min;
|
276
|
+
};
|
277
|
+
|
278
|
+
setMax = function (options, max) {
|
279
|
+
options.yAxis.max = max;
|
280
|
+
};
|
281
|
+
|
282
|
+
setStacked = function (options) {
|
283
|
+
options.plotOptions.series.stacking = "normal";
|
284
|
+
};
|
285
|
+
|
286
|
+
jsOptions = jsOptionsFunc(defaultOptions, hideLegend, setMin, setMax, setStacked);
|
287
|
+
|
288
|
+
renderLineChart = function (element, series, opts, chartType) {
|
289
|
+
chartType = chartType || "spline";
|
290
|
+
var chartOptions = {};
|
291
|
+
if (chartType === "areaspline") {
|
292
|
+
chartOptions = {
|
293
|
+
plotOptions: {
|
294
|
+
areaspline: {
|
295
|
+
stacking: "normal"
|
296
|
+
},
|
297
|
+
series: {
|
298
|
+
marker: {
|
299
|
+
enabled: false
|
300
|
+
}
|
301
|
+
}
|
302
|
+
}
|
303
|
+
};
|
304
|
+
}
|
305
|
+
var options = jsOptions(series, opts, chartOptions), data, i, j;
|
306
|
+
options.xAxis.type = "datetime";
|
307
|
+
options.chart.type = chartType;
|
308
|
+
options.chart.renderTo = element.id;
|
309
|
+
|
310
|
+
for (i = 0; i < series.length; i++) {
|
311
|
+
data = series[i].data;
|
312
|
+
for (j = 0; j < data.length; j++) {
|
313
|
+
data[j][0] = data[j][0].getTime();
|
314
|
+
}
|
315
|
+
series[i].marker = {symbol: "circle"};
|
316
|
+
}
|
317
|
+
options.series = series;
|
318
|
+
new Highcharts.Chart(options);
|
319
|
+
};
|
320
|
+
|
321
|
+
renderPieChart = function (element, series, opts) {
|
322
|
+
var options = merge(defaultOptions, opts.library || {});
|
323
|
+
options.chart.renderTo = element.id;
|
324
|
+
options.series = [{
|
325
|
+
type: "pie",
|
326
|
+
name: "Value",
|
327
|
+
data: series
|
328
|
+
}];
|
329
|
+
new Highcharts.Chart(options);
|
330
|
+
};
|
331
|
+
|
332
|
+
renderColumnChart = function (element, series, opts, chartType) {
|
333
|
+
chartType = chartType || "column";
|
334
|
+
var options = jsOptions(series, opts), i, j, s, d, rows = [];
|
335
|
+
options.chart.type = chartType;
|
336
|
+
options.chart.renderTo = element.id;
|
337
|
+
|
338
|
+
for (i = 0; i < series.length; i++) {
|
339
|
+
s = series[i];
|
340
|
+
|
341
|
+
for (j = 0; j < s.data.length; j++) {
|
342
|
+
d = s.data[j];
|
343
|
+
if (!rows[d[0]]) {
|
344
|
+
rows[d[0]] = new Array(series.length);
|
345
|
+
}
|
346
|
+
rows[d[0]][i] = d[1];
|
347
|
+
}
|
348
|
+
}
|
349
|
+
|
350
|
+
var categories = [];
|
351
|
+
for (i in rows) {
|
352
|
+
if (rows.hasOwnProperty(i)) {
|
353
|
+
categories.push(i);
|
354
|
+
}
|
355
|
+
}
|
356
|
+
options.xAxis.categories = categories;
|
357
|
+
|
358
|
+
var newSeries = [];
|
359
|
+
for (i = 0; i < series.length; i++) {
|
360
|
+
d = [];
|
361
|
+
for (j = 0; j < categories.length; j++) {
|
362
|
+
d.push(rows[categories[j]][i] || 0);
|
363
|
+
}
|
364
|
+
|
365
|
+
newSeries.push({
|
366
|
+
name: series[i].name,
|
367
|
+
data: d
|
368
|
+
});
|
369
|
+
}
|
370
|
+
options.series = newSeries;
|
371
|
+
|
372
|
+
new Highcharts.Chart(options);
|
373
|
+
};
|
374
|
+
|
375
|
+
renderBarChart = function (element, series, opts) {
|
376
|
+
renderColumnChart(element, series, opts, "bar");
|
377
|
+
};
|
378
|
+
|
379
|
+
renderAreaChart = function (element, series, opts) {
|
380
|
+
renderLineChart(element, series, opts, "areaspline");
|
381
|
+
};
|
382
|
+
} else if ("google" in window) { // Google charts
|
383
|
+
// load from google
|
384
|
+
loaded = false;
|
385
|
+
google.setOnLoadCallback(function () {
|
386
|
+
loaded = true;
|
387
|
+
});
|
388
|
+
var loadOptions = {"packages": ["corechart"]};
|
389
|
+
var config = window.Chartkick || {};
|
390
|
+
if (config.language) {
|
391
|
+
loadOptions.language = config.language;
|
392
|
+
}
|
393
|
+
google.load("visualization", "1.0", loadOptions);
|
394
|
+
|
395
|
+
waitForLoaded = function (callback) {
|
396
|
+
google.setOnLoadCallback(callback); // always do this to prevent race conditions (watch out for other issues due to this)
|
397
|
+
if (loaded) {
|
398
|
+
callback();
|
399
|
+
}
|
400
|
+
};
|
401
|
+
|
402
|
+
// Set chart options
|
403
|
+
defaultOptions = {
|
404
|
+
chartArea: {},
|
405
|
+
fontName: "'Lucida Grande', 'Lucida Sans Unicode', Verdana, Arial, Helvetica, sans-serif",
|
406
|
+
pointSize: 6,
|
407
|
+
legend: {
|
408
|
+
textStyle: {
|
409
|
+
fontSize: 12,
|
410
|
+
color: "#444"
|
411
|
+
},
|
412
|
+
alignment: "center",
|
413
|
+
position: "right"
|
414
|
+
},
|
415
|
+
curveType: "function",
|
416
|
+
hAxis: {
|
417
|
+
textStyle: {
|
418
|
+
color: "#666",
|
419
|
+
fontSize: 12
|
420
|
+
},
|
421
|
+
gridlines: {
|
422
|
+
color: "transparent"
|
423
|
+
},
|
424
|
+
baselineColor: "#ccc",
|
425
|
+
viewWindow: {}
|
426
|
+
},
|
427
|
+
vAxis: {
|
428
|
+
textStyle: {
|
429
|
+
color: "#666",
|
430
|
+
fontSize: 12
|
431
|
+
},
|
432
|
+
baselineColor: "#ccc",
|
433
|
+
viewWindow: {}
|
434
|
+
},
|
435
|
+
tooltip: {
|
436
|
+
textStyle: {
|
437
|
+
color: "#666",
|
438
|
+
fontSize: 12
|
439
|
+
}
|
440
|
+
}
|
441
|
+
};
|
442
|
+
|
443
|
+
hideLegend = function (options) {
|
444
|
+
options.legend.position = "none";
|
445
|
+
};
|
446
|
+
|
447
|
+
setMin = function (options, min) {
|
448
|
+
options.vAxis.viewWindow.min = min;
|
449
|
+
};
|
450
|
+
|
451
|
+
setMax = function (options, max) {
|
452
|
+
options.vAxis.viewWindow.max = max;
|
453
|
+
};
|
454
|
+
|
455
|
+
setBarMin = function (options, min) {
|
456
|
+
options.hAxis.viewWindow.min = min;
|
457
|
+
};
|
458
|
+
|
459
|
+
setBarMax = function (options, max) {
|
460
|
+
options.hAxis.viewWindow.max = max;
|
461
|
+
};
|
462
|
+
|
463
|
+
setStacked = function (options) {
|
464
|
+
options.isStacked = true;
|
465
|
+
};
|
466
|
+
|
467
|
+
jsOptions = jsOptionsFunc(defaultOptions, hideLegend, setMin, setMax, setStacked);
|
468
|
+
|
469
|
+
// cant use object as key
|
470
|
+
createDataTable = function (series, columnType) {
|
471
|
+
var data = new google.visualization.DataTable();
|
472
|
+
data.addColumn(columnType, "");
|
473
|
+
|
474
|
+
var i, j, s, d, key, rows = [];
|
475
|
+
for (i = 0; i < series.length; i++) {
|
476
|
+
s = series[i];
|
477
|
+
data.addColumn("number", s.name);
|
478
|
+
|
479
|
+
for (j = 0; j < s.data.length; j++) {
|
480
|
+
d = s.data[j];
|
481
|
+
key = (columnType === "datetime") ? d[0].getTime() : d[0];
|
482
|
+
if (!rows[key]) {
|
483
|
+
rows[key] = new Array(series.length);
|
484
|
+
}
|
485
|
+
rows[key][i] = toFloat(d[1]);
|
486
|
+
}
|
487
|
+
}
|
488
|
+
|
489
|
+
var rows2 = [];
|
490
|
+
for (i in rows) {
|
491
|
+
if (rows.hasOwnProperty(i)) {
|
492
|
+
rows2.push([(columnType === "datetime") ? new Date(toFloat(i)) : i].concat(rows[i]));
|
493
|
+
}
|
494
|
+
}
|
495
|
+
if (columnType === "datetime") {
|
496
|
+
rows2.sort(sortByTime);
|
497
|
+
}
|
498
|
+
data.addRows(rows2);
|
499
|
+
|
500
|
+
return data;
|
501
|
+
};
|
502
|
+
|
503
|
+
resize = function (callback) {
|
504
|
+
if (window.attachEvent) {
|
505
|
+
window.attachEvent("onresize", callback);
|
506
|
+
} else if (window.addEventListener) {
|
507
|
+
window.addEventListener("resize", callback, true);
|
508
|
+
}
|
509
|
+
callback();
|
510
|
+
};
|
511
|
+
|
512
|
+
renderLineChart = function (element, series, opts) {
|
513
|
+
waitForLoaded(function () {
|
514
|
+
var options = jsOptions(series, opts);
|
515
|
+
var data = createDataTable(series, "datetime");
|
516
|
+
var chart = new google.visualization.LineChart(element);
|
517
|
+
resize(function () {
|
518
|
+
chart.draw(data, options);
|
519
|
+
});
|
520
|
+
});
|
521
|
+
};
|
522
|
+
|
523
|
+
renderPieChart = function (element, series, opts) {
|
524
|
+
waitForLoaded(function () {
|
525
|
+
var chartOptions = {
|
526
|
+
chartArea: {
|
527
|
+
top: "10%",
|
528
|
+
height: "80%"
|
529
|
+
}
|
530
|
+
};
|
531
|
+
var options = merge(merge(defaultOptions, chartOptions), opts.library || {});
|
532
|
+
|
533
|
+
var data = new google.visualization.DataTable();
|
534
|
+
data.addColumn("string", "");
|
535
|
+
data.addColumn("number", "Value");
|
536
|
+
data.addRows(series);
|
537
|
+
|
538
|
+
var chart = new google.visualization.PieChart(element);
|
539
|
+
resize(function () {
|
540
|
+
chart.draw(data, options);
|
541
|
+
});
|
542
|
+
});
|
543
|
+
};
|
544
|
+
|
545
|
+
renderColumnChart = function (element, series, opts) {
|
546
|
+
waitForLoaded(function () {
|
547
|
+
var options = jsOptions(series, opts);
|
548
|
+
var data = createDataTable(series, "string");
|
549
|
+
var chart = new google.visualization.ColumnChart(element);
|
550
|
+
resize(function () {
|
551
|
+
chart.draw(data, options);
|
552
|
+
});
|
553
|
+
});
|
554
|
+
};
|
555
|
+
|
556
|
+
renderBarChart = function (element, series, opts) {
|
557
|
+
waitForLoaded(function () {
|
558
|
+
var chartOptions = {
|
559
|
+
hAxis: {
|
560
|
+
gridlines: {
|
561
|
+
color: "#ccc"
|
562
|
+
}
|
563
|
+
}
|
564
|
+
};
|
565
|
+
var options = jsOptionsFunc(defaultOptions, hideLegend, setBarMin, setBarMax, setStacked)(series, opts, chartOptions);
|
566
|
+
var data = createDataTable(series, "string");
|
567
|
+
var chart = new google.visualization.BarChart(element);
|
568
|
+
resize(function () {
|
569
|
+
chart.draw(data, options);
|
570
|
+
});
|
571
|
+
});
|
572
|
+
};
|
573
|
+
|
574
|
+
renderAreaChart = function (element, series, opts) {
|
575
|
+
waitForLoaded(function () {
|
576
|
+
var chartOptions = {
|
577
|
+
isStacked: true,
|
578
|
+
pointSize: 0,
|
579
|
+
areaOpacity: 0.5
|
580
|
+
};
|
581
|
+
var options = jsOptions(series, opts, chartOptions);
|
582
|
+
var data = createDataTable(series, "datetime");
|
583
|
+
var chart = new google.visualization.AreaChart(element);
|
584
|
+
resize(function () {
|
585
|
+
chart.draw(data, options);
|
586
|
+
});
|
587
|
+
});
|
588
|
+
};
|
589
|
+
} else { // no chart library installed
|
590
|
+
renderLineChart = renderPieChart = renderColumnChart = renderBarChart = renderAreaChart = function () {
|
591
|
+
throw new Error("Please install Google Charts or Highcharts");
|
592
|
+
};
|
593
|
+
}
|
594
|
+
|
595
|
+
// process data
|
596
|
+
|
597
|
+
function processSeries(series, opts, time) {
|
598
|
+
var i, j, data, r, key;
|
599
|
+
|
600
|
+
// see if one series or multiple
|
601
|
+
if (!isArray(series) || typeof series[0] !== "object" || isArray(series[0])) {
|
602
|
+
series = [{name: "Value", data: series}];
|
603
|
+
opts.hideLegend = true;
|
604
|
+
} else {
|
605
|
+
opts.hideLegend = false;
|
606
|
+
}
|
607
|
+
|
608
|
+
// right format
|
609
|
+
for (i = 0; i < series.length; i++) {
|
610
|
+
data = toArr(series[i].data);
|
611
|
+
r = [];
|
612
|
+
for (j = 0; j < data.length; j++) {
|
613
|
+
key = data[j][0];
|
614
|
+
key = time ? toDate(key) : toStr(key);
|
615
|
+
r.push([key, toFloat(data[j][1])]);
|
616
|
+
}
|
617
|
+
if (time) {
|
618
|
+
r.sort(sortByTime);
|
619
|
+
}
|
620
|
+
series[i].data = r;
|
621
|
+
}
|
622
|
+
|
623
|
+
return series;
|
624
|
+
}
|
625
|
+
|
626
|
+
function processLineData(element, data, opts) {
|
627
|
+
renderLineChart(element, processSeries(data, opts, true), opts);
|
628
|
+
}
|
629
|
+
|
630
|
+
function processColumnData(element, data, opts) {
|
631
|
+
renderColumnChart(element, processSeries(data, opts, false), opts);
|
632
|
+
}
|
633
|
+
|
634
|
+
function processPieData(element, data, opts) {
|
635
|
+
var perfectData = toArr(data), i;
|
636
|
+
for (i = 0; i < perfectData.length; i++) {
|
637
|
+
perfectData[i] = [toStr(perfectData[i][0]), toFloat(perfectData[i][1])];
|
638
|
+
}
|
639
|
+
renderPieChart(element, perfectData, opts);
|
640
|
+
}
|
641
|
+
|
642
|
+
function processBarData(element, data, opts) {
|
643
|
+
renderBarChart(element, processSeries(data, opts, false), opts);
|
644
|
+
}
|
645
|
+
|
646
|
+
function processAreaData(element, data, opts) {
|
647
|
+
renderAreaChart(element, processSeries(data, opts, true), opts);
|
648
|
+
}
|
649
|
+
|
650
|
+
function setElement(element, data, opts, callback) {
|
651
|
+
if (typeof element === "string") {
|
652
|
+
element = document.getElementById(element);
|
653
|
+
}
|
654
|
+
fetchDataSource(element, data, opts || {}, callback);
|
655
|
+
}
|
656
|
+
|
657
|
+
// define classes
|
658
|
+
|
659
|
+
Chartkick = {
|
660
|
+
LineChart: function (element, dataSource, opts) {
|
661
|
+
setElement(element, dataSource, opts, processLineData);
|
662
|
+
},
|
663
|
+
PieChart: function (element, dataSource, opts) {
|
664
|
+
setElement(element, dataSource, opts, processPieData);
|
665
|
+
},
|
666
|
+
ColumnChart: function (element, dataSource, opts) {
|
667
|
+
setElement(element, dataSource, opts, processColumnData);
|
668
|
+
},
|
669
|
+
BarChart: function (element, dataSource, opts) {
|
670
|
+
setElement(element, dataSource, opts, processBarData);
|
671
|
+
},
|
672
|
+
AreaChart: function (element, dataSource, opts) {
|
673
|
+
setElement(element, dataSource, opts, processAreaData);
|
674
|
+
}
|
675
|
+
};
|
676
|
+
|
677
|
+
window.Chartkick = Chartkick;
|
678
|
+
}());
|