rspec_log_formatter 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +5 -13
  2. data/.gitignore +1 -1
  3. data/.ruby-version +1 -1
  4. data/README.md +53 -7
  5. data/bin/rspec_log_formatter +30 -0
  6. data/dummy/.gitignore +16 -0
  7. data/dummy/.rspec +1 -0
  8. data/dummy/Gemfile +47 -0
  9. data/dummy/README.rdoc +28 -0
  10. data/dummy/Rakefile +6 -0
  11. data/dummy/app/assets/images/.keep +0 -0
  12. data/dummy/app/assets/javascripts/application.js +16 -0
  13. data/dummy/app/assets/stylesheets/application.css +13 -0
  14. data/dummy/app/controllers/application_controller.rb +5 -0
  15. data/dummy/app/controllers/concerns/.keep +0 -0
  16. data/dummy/app/helpers/application_helper.rb +2 -0
  17. data/dummy/app/mailers/.keep +0 -0
  18. data/dummy/app/models/.keep +0 -0
  19. data/dummy/app/models/concerns/.keep +0 -0
  20. data/dummy/app/views/layouts/application.html.erb +14 -0
  21. data/dummy/bin/bundle +3 -0
  22. data/dummy/bin/rails +4 -0
  23. data/dummy/bin/rake +4 -0
  24. data/dummy/config/application.rb +23 -0
  25. data/dummy/config/boot.rb +4 -0
  26. data/dummy/config/database.yml +25 -0
  27. data/dummy/config/environment.rb +5 -0
  28. data/dummy/config/environments/development.rb +29 -0
  29. data/dummy/config/environments/production.rb +80 -0
  30. data/dummy/config/environments/test.rb +36 -0
  31. data/dummy/config/initializers/backtrace_silencers.rb +7 -0
  32. data/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  33. data/dummy/config/initializers/inflections.rb +16 -0
  34. data/dummy/config/initializers/mime_types.rb +5 -0
  35. data/dummy/config/initializers/secret_token.rb +12 -0
  36. data/dummy/config/initializers/session_store.rb +3 -0
  37. data/dummy/config/initializers/wrap_parameters.rb +14 -0
  38. data/dummy/config/locales/en.yml +23 -0
  39. data/dummy/config/routes.rb +56 -0
  40. data/dummy/config.ru +4 -0
  41. data/dummy/db/schema.rb +16 -0
  42. data/dummy/db/seeds.rb +7 -0
  43. data/dummy/lib/assets/.keep +0 -0
  44. data/dummy/lib/tasks/.keep +0 -0
  45. data/dummy/log/.keep +0 -0
  46. data/dummy/public/404.html +58 -0
  47. data/dummy/public/422.html +58 -0
  48. data/dummy/public/500.html +57 -0
  49. data/dummy/public/favicon.ico +0 -0
  50. data/dummy/public/robots.txt +5 -0
  51. data/dummy/spec/dummy_spec.rb +7 -0
  52. data/dummy/spec/spec_helper.rb +46 -0
  53. data/dummy/test/controllers/.keep +0 -0
  54. data/dummy/test/fixtures/.keep +0 -0
  55. data/dummy/test/helpers/.keep +0 -0
  56. data/dummy/test/integration/.keep +0 -0
  57. data/dummy/test/mailers/.keep +0 -0
  58. data/dummy/test/models/.keep +0 -0
  59. data/dummy/test/test_helper.rb +15 -0
  60. data/dummy/vendor/assets/javascripts/.keep +0 -0
  61. data/dummy/vendor/assets/stylesheets/.keep +0 -0
  62. data/lib/rspec_log_formatter/analysis/analyzer.rb +15 -50
  63. data/lib/rspec_log_formatter/analysis/pretty_printer.rb +10 -6
  64. data/lib/rspec_log_formatter/analysis/result.rb +8 -4
  65. data/lib/rspec_log_formatter/analysis/score.rb +42 -6
  66. data/lib/rspec_log_formatter/analyzer_formatter.rb +18 -6
  67. data/lib/rspec_log_formatter/formatter.rb +19 -12
  68. data/lib/rspec_log_formatter/history_manager.rb +46 -0
  69. data/lib/rspec_log_formatter/javascripts/chartkick.js +678 -0
  70. data/lib/rspec_log_formatter/performance_analyzer.rb +40 -0
  71. data/lib/rspec_log_formatter/templates/charts.html.erb +11 -0
  72. data/lib/rspec_log_formatter/version.rb +1 -1
  73. data/lib/rspec_log_formatter.rb +2 -1
  74. data/rspec_log_formatter.gemspec +1 -0
  75. data/spec/fixtures/test_slowing_down_over_time.history +7 -0
  76. data/spec/fixtures/varying_flakiness.history +9 -9
  77. data/spec/lib/rspec_log_analyzer/analysis/analyzer_spec.rb +25 -21
  78. data/spec/lib/rspec_log_analyzer/analysis/pretty_printer_spec.rb +10 -8
  79. data/spec/lib/rspec_log_analyzer/analyzer_formatter_spec.rb +3 -3
  80. data/spec/lib/rspec_log_analyzer/formatter_spec.rb +16 -18
  81. data/spec/lib/rspec_log_analyzer/history_manager_spec.rb +16 -0
  82. data/spec/lib/rspec_log_analyzer/performance_analyzer_spec.rb +16 -0
  83. data/specs.sh +4 -0
  84. 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
+ }());