railsblazer 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +1 -0
  3. data/.github/ISSUE_TEMPLATE.md +0 -0
  4. data/.gitignore +14 -0
  5. data/CHANGELOG.md +247 -0
  6. data/CONTRIBUTING.md +42 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +855 -0
  10. data/Rakefile +1 -0
  11. data/app/assets/fonts/blazer/glyphicons-halflings-regular.eot +0 -0
  12. data/app/assets/fonts/blazer/glyphicons-halflings-regular.svg +288 -0
  13. data/app/assets/fonts/blazer/glyphicons-halflings-regular.ttf +0 -0
  14. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff +0 -0
  15. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff2 +0 -0
  16. data/app/assets/javascripts/blazer/Chart.js +14145 -0
  17. data/app/assets/javascripts/blazer/Sortable.js +1144 -0
  18. data/app/assets/javascripts/blazer/ace.js +6 -0
  19. data/app/assets/javascripts/blazer/ace/ace.js +11 -0
  20. data/app/assets/javascripts/blazer/ace/ext-language_tools.js +5 -0
  21. data/app/assets/javascripts/blazer/ace/mode-sql.js +1 -0
  22. data/app/assets/javascripts/blazer/ace/snippets/sql.js +1 -0
  23. data/app/assets/javascripts/blazer/ace/snippets/text.js +1 -0
  24. data/app/assets/javascripts/blazer/ace/theme-twilight.js +1 -0
  25. data/app/assets/javascripts/blazer/application.js +79 -0
  26. data/app/assets/javascripts/blazer/bootstrap.js +2366 -0
  27. data/app/assets/javascripts/blazer/chartkick.js +1693 -0
  28. data/app/assets/javascripts/blazer/daterangepicker.js +1505 -0
  29. data/app/assets/javascripts/blazer/fuzzysearch.js +24 -0
  30. data/app/assets/javascripts/blazer/highlight.pack.js +1 -0
  31. data/app/assets/javascripts/blazer/jquery.js +10308 -0
  32. data/app/assets/javascripts/blazer/jquery.stickytableheaders.js +263 -0
  33. data/app/assets/javascripts/blazer/jquery_ujs.js +469 -0
  34. data/app/assets/javascripts/blazer/moment-timezone.js +1007 -0
  35. data/app/assets/javascripts/blazer/moment.js +3043 -0
  36. data/app/assets/javascripts/blazer/queries.js +110 -0
  37. data/app/assets/javascripts/blazer/routes.js +23 -0
  38. data/app/assets/javascripts/blazer/selectize.js +3667 -0
  39. data/app/assets/javascripts/blazer/stupidtable.js +114 -0
  40. data/app/assets/javascripts/blazer/vue.js +7515 -0
  41. data/app/assets/stylesheets/blazer/application.css +198 -0
  42. data/app/assets/stylesheets/blazer/bootstrap.css.erb +6202 -0
  43. data/app/assets/stylesheets/blazer/daterangepicker-bs3.css +375 -0
  44. data/app/assets/stylesheets/blazer/github.css +125 -0
  45. data/app/assets/stylesheets/blazer/selectize.default.css +387 -0
  46. data/app/controllers/blazer/base_controller.rb +113 -0
  47. data/app/controllers/blazer/checks_controller.rb +56 -0
  48. data/app/controllers/blazer/dashboards_controller.rb +105 -0
  49. data/app/controllers/blazer/queries_controller.rb +337 -0
  50. data/app/helpers/blazer/base_helper.rb +57 -0
  51. data/app/mailers/blazer/check_mailer.rb +27 -0
  52. data/app/mailers/blazer/slack_notifier.rb +76 -0
  53. data/app/models/blazer/audit.rb +6 -0
  54. data/app/models/blazer/check.rb +104 -0
  55. data/app/models/blazer/connection.rb +5 -0
  56. data/app/models/blazer/dashboard.rb +13 -0
  57. data/app/models/blazer/dashboard_query.rb +9 -0
  58. data/app/models/blazer/query.rb +40 -0
  59. data/app/models/blazer/record.rb +5 -0
  60. data/app/views/blazer/_nav.html.erb +16 -0
  61. data/app/views/blazer/_variables.html.erb +102 -0
  62. data/app/views/blazer/check_mailer/failing_checks.html.erb +6 -0
  63. data/app/views/blazer/check_mailer/state_change.html.erb +47 -0
  64. data/app/views/blazer/checks/_form.html.erb +79 -0
  65. data/app/views/blazer/checks/edit.html.erb +1 -0
  66. data/app/views/blazer/checks/index.html.erb +43 -0
  67. data/app/views/blazer/checks/new.html.erb +1 -0
  68. data/app/views/blazer/dashboards/_form.html.erb +76 -0
  69. data/app/views/blazer/dashboards/edit.html.erb +1 -0
  70. data/app/views/blazer/dashboards/new.html.erb +1 -0
  71. data/app/views/blazer/dashboards/show.html.erb +47 -0
  72. data/app/views/blazer/queries/_form.html.erb +240 -0
  73. data/app/views/blazer/queries/edit.html.erb +2 -0
  74. data/app/views/blazer/queries/home.html.erb +152 -0
  75. data/app/views/blazer/queries/new.html.erb +2 -0
  76. data/app/views/blazer/queries/run.html.erb +165 -0
  77. data/app/views/blazer/queries/schema.html.erb +20 -0
  78. data/app/views/blazer/queries/show.html.erb +73 -0
  79. data/app/views/layouts/blazer/application.html.erb +24 -0
  80. data/blazer-0.0.1.gem +0 -0
  81. data/blazer.gemspec +27 -0
  82. data/config/routes.rb +16 -0
  83. data/lib/blazer.rb +223 -0
  84. data/lib/blazer/adapters/athena_adapter.rb +128 -0
  85. data/lib/blazer/adapters/base_adapter.rb +53 -0
  86. data/lib/blazer/adapters/bigquery_adapter.rb +68 -0
  87. data/lib/blazer/adapters/cassandra_adapter.rb +59 -0
  88. data/lib/blazer/adapters/drill_adapter.rb +28 -0
  89. data/lib/blazer/adapters/druid_adapter.rb +67 -0
  90. data/lib/blazer/adapters/elasticsearch_adapter.rb +46 -0
  91. data/lib/blazer/adapters/mongodb_adapter.rb +39 -0
  92. data/lib/blazer/adapters/presto_adapter.rb +45 -0
  93. data/lib/blazer/adapters/snowflake_adapter.rb +73 -0
  94. data/lib/blazer/adapters/sql_adapter.rb +182 -0
  95. data/lib/blazer/data_source.rb +195 -0
  96. data/lib/blazer/detect_anomalies.R +19 -0
  97. data/lib/blazer/engine.rb +30 -0
  98. data/lib/blazer/result.rb +170 -0
  99. data/lib/blazer/run_statement.rb +40 -0
  100. data/lib/blazer/run_statement_job.rb +21 -0
  101. data/lib/blazer/version.rb +3 -0
  102. data/lib/generators/blazer/install_generator.rb +39 -0
  103. data/lib/generators/blazer/templates/config.yml.tt +62 -0
  104. data/lib/generators/blazer/templates/install.rb.tt +46 -0
  105. data/lib/tasks/blazer.rake +11 -0
  106. data/railsblazer-0.0.1.gem +0 -0
  107. metadata +234 -0
@@ -0,0 +1,1693 @@
1
+ /*
2
+ * Chartkick.js
3
+ * Create beautiful charts with one line of JavaScript
4
+ * https://github.com/ankane/chartkick.js
5
+ * v2.2.1
6
+ * MIT License
7
+ */
8
+
9
+ /*jslint browser: true, indent: 2, plusplus: true, vars: true */
10
+
11
+ (function (window) {
12
+ 'use strict';
13
+
14
+ var config = window.Chartkick || {};
15
+ var Chartkick, ISO8601_PATTERN, DECIMAL_SEPARATOR, adapters = [];
16
+ var DATE_PATTERN = /^(\d\d\d\d)(\-)?(\d\d)(\-)?(\d\d)$/i;
17
+ var GoogleChartsAdapter, HighchartsAdapter, ChartjsAdapter;
18
+ var pendingRequests = [], runningRequests = 0, maxRequests = 4;
19
+
20
+ // helpers
21
+
22
+ function isArray(variable) {
23
+ return Object.prototype.toString.call(variable) === "[object Array]";
24
+ }
25
+
26
+ function isFunction(variable) {
27
+ return variable instanceof Function;
28
+ }
29
+
30
+ function isPlainObject(variable) {
31
+ return !isFunction(variable) && variable instanceof Object;
32
+ }
33
+
34
+ // https://github.com/madrobby/zepto/blob/master/src/zepto.js
35
+ function extend(target, source) {
36
+ var key;
37
+ for (key in source) {
38
+ if (isPlainObject(source[key]) || isArray(source[key])) {
39
+ if (isPlainObject(source[key]) && !isPlainObject(target[key])) {
40
+ target[key] = {};
41
+ }
42
+ if (isArray(source[key]) && !isArray(target[key])) {
43
+ target[key] = [];
44
+ }
45
+ extend(target[key], source[key]);
46
+ } else if (source[key] !== undefined) {
47
+ target[key] = source[key];
48
+ }
49
+ }
50
+ }
51
+
52
+ function merge(obj1, obj2) {
53
+ var target = {};
54
+ extend(target, obj1);
55
+ extend(target, obj2);
56
+ return target;
57
+ }
58
+
59
+ // https://github.com/Do/iso8601.js
60
+ ISO8601_PATTERN = /(\d\d\d\d)(\-)?(\d\d)(\-)?(\d\d)(T)?(\d\d)(:)?(\d\d)?(:)?(\d\d)?([\.,]\d+)?($|Z|([\+\-])(\d\d)(:)?(\d\d)?)/i;
61
+ DECIMAL_SEPARATOR = String(1.5).charAt(1);
62
+
63
+ function parseISO8601(input) {
64
+ var day, hour, matches, milliseconds, minutes, month, offset, result, seconds, type, year;
65
+ type = Object.prototype.toString.call(input);
66
+ if (type === "[object Date]") {
67
+ return input;
68
+ }
69
+ if (type !== "[object String]") {
70
+ return;
71
+ }
72
+ matches = input.match(ISO8601_PATTERN);
73
+ if (matches) {
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, setTitle, setMin, setMax, setStacked, setXtitle, setYtitle) {
109
+ return function (chart, opts, chartOptions) {
110
+ var series = chart.data;
111
+ var options = merge({}, defaultOptions);
112
+ options = merge(options, chartOptions || {});
113
+
114
+ if (chart.hideLegend || "legend" in opts) {
115
+ hideLegend(options, opts.legend, chart.hideLegend);
116
+ }
117
+
118
+ if (opts.title) {
119
+ setTitle(options, opts.title);
120
+ }
121
+
122
+ // min
123
+ if ("min" in opts) {
124
+ setMin(options, opts.min);
125
+ } else if (!negativeValues(series)) {
126
+ setMin(options, 0);
127
+ }
128
+
129
+ // max
130
+ if (opts.max) {
131
+ setMax(options, opts.max);
132
+ }
133
+
134
+ if ("stacked" in opts) {
135
+ setStacked(options, opts.stacked);
136
+ }
137
+
138
+ if (opts.colors) {
139
+ options.colors = opts.colors;
140
+ }
141
+
142
+ if (opts.xtitle) {
143
+ setXtitle(options, opts.xtitle);
144
+ }
145
+
146
+ if (opts.ytitle) {
147
+ setYtitle(options, opts.ytitle);
148
+ }
149
+
150
+ // merge library last
151
+ options = merge(options, opts.library || {});
152
+
153
+ return options;
154
+ };
155
+ }
156
+
157
+ function setText(element, text) {
158
+ if (document.body.innerText) {
159
+ element.innerText = text;
160
+ } else {
161
+ element.textContent = text;
162
+ }
163
+ }
164
+
165
+ function chartError(element, message) {
166
+ setText(element, "Error Loading Chart: " + message);
167
+ element.style.color = "#ff0000";
168
+ }
169
+
170
+ function pushRequest(element, url, success) {
171
+ pendingRequests.push([element, url, success]);
172
+ runNext();
173
+ }
174
+
175
+ function runNext() {
176
+ if (runningRequests < maxRequests) {
177
+ var request = pendingRequests.shift()
178
+ if (request) {
179
+ runningRequests++;
180
+ getJSON(request[0], request[1], request[2]);
181
+ runNext();
182
+ }
183
+ }
184
+ }
185
+
186
+ function requestComplete() {
187
+ runningRequests--;
188
+ runNext();
189
+ }
190
+
191
+ function getJSON(element, url, success) {
192
+ ajaxCall(url, success, function (jqXHR, textStatus, errorThrown) {
193
+ var message = (typeof errorThrown === "string") ? errorThrown : errorThrown.message;
194
+ chartError(element, message);
195
+ });
196
+ }
197
+
198
+ function ajaxCall(url, success, error) {
199
+ var $ = window.jQuery || window.Zepto || window.$;
200
+
201
+ if ($) {
202
+ $.ajax({
203
+ dataType: "json",
204
+ url: url,
205
+ success: success,
206
+ error: error,
207
+ complete: requestComplete
208
+ });
209
+ } else {
210
+ var xhr = new XMLHttpRequest();
211
+ xhr.open("GET", url, true);
212
+ xhr.setRequestHeader("Content-Type", "application/json");
213
+ xhr.onload = function () {
214
+ requestComplete();
215
+ if (xhr.status === 200) {
216
+ success(JSON.parse(xhr.responseText), xhr.statusText, xhr);
217
+ } else {
218
+ error(xhr, "error", xhr.statusText);
219
+ }
220
+ };
221
+ xhr.send();
222
+ }
223
+ }
224
+
225
+ function errorCatcher(chart, callback) {
226
+ try {
227
+ callback(chart);
228
+ } catch (err) {
229
+ chartError(chart.element, err.message);
230
+ throw err;
231
+ }
232
+ }
233
+
234
+ function fetchDataSource(chart, callback, dataSource) {
235
+ if (typeof dataSource === "string") {
236
+ pushRequest(chart.element, dataSource, function (data, textStatus, jqXHR) {
237
+ chart.rawData = data;
238
+ errorCatcher(chart, callback);
239
+ });
240
+ } else {
241
+ chart.rawData = dataSource;
242
+ errorCatcher(chart, callback);
243
+ }
244
+ }
245
+
246
+ function addDownloadButton(chart) {
247
+ var element = chart.element;
248
+ var link = document.createElement("a");
249
+ link.download = chart.options.download === true ? "chart.png" : chart.options.download; // http://caniuse.com/download
250
+ link.style.position = "absolute";
251
+ link.style.top = "20px";
252
+ link.style.right = "20px";
253
+ link.style.zIndex = 1000;
254
+ link.style.lineHeight = "20px";
255
+ link.target = "_blank"; // for safari
256
+ var image = document.createElement("img");
257
+ image.alt = "Download";
258
+ image.style.border = "none";
259
+ // icon from font-awesome
260
+ // http://fa2png.io/
261
+ image.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAABCFBMVEUAAADMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMywEsqxAAAAV3RSTlMAAQIDBggJCgsMDQ4PERQaHB0eISIjJCouLzE0OTo/QUJHSUpLTU5PUllhYmltcHh5foWLjI+SlaCio6atr7S1t7m6vsHHyM7R2tze5Obo7fHz9ff5+/1hlxK2AAAA30lEQVQYGUXBhVYCQQBA0TdYWAt2d3d3YWAHyur7/z9xgD16Lw0DW+XKx+1GgX+FRzM3HWQWrHl5N/oapW5RPe0PkBu+UYeICvozTWZVK23Ao04B79oJrOsJDOoxkZoQPWgX29pHpCZEk7rEvQYiNSFq1UMqvlCjJkRBS1R8hb00Vb/TajtBL7nTHE1X1vyMQF732dQhyF2o6SAwrzP06iUQzvwsArlnzcOdrgBhJyHa1QOgO9U1GsKuvjUTjavliZYQ8nNPapG6sap/3nrIdJ6bOWzmX/fy0XVpfzZP3S8OJT3g9EEiJwAAAABJRU5ErkJggg==";
262
+ link.appendChild(image);
263
+ element.style.position = "relative";
264
+
265
+ chart.downloadAttached = true;
266
+
267
+ // mouseenter
268
+ addEvent(element, "mouseover", function(e) {
269
+ var related = e.relatedTarget;
270
+ // check download option again to ensure it wasn't changed
271
+ if (!related || (related !== this && !childOf(this, related)) && chart.options.download) {
272
+ link.href = chart.toImage();
273
+ element.appendChild(link);
274
+ }
275
+ });
276
+
277
+ // mouseleave
278
+ addEvent(element, "mouseout", function(e) {
279
+ var related = e.relatedTarget;
280
+ if (!related || (related !== this && !childOf(this, related))) {
281
+ if (link.parentNode) {
282
+ link.parentNode.removeChild(link);
283
+ }
284
+ }
285
+ });
286
+ }
287
+
288
+ // http://stackoverflow.com/questions/10149963/adding-event-listener-cross-browser
289
+ function addEvent(elem, event, fn) {
290
+ if (elem.addEventListener) {
291
+ elem.addEventListener(event, fn, false);
292
+ } else {
293
+ elem.attachEvent("on" + event, function() {
294
+ // set the this pointer same as addEventListener when fn is called
295
+ return(fn.call(elem, window.event));
296
+ });
297
+ }
298
+ }
299
+
300
+ // https://gist.github.com/shawnbot/4166283
301
+ function childOf(p, c) {
302
+ if (p === c) return false;
303
+ while (c && c !== p) c = c.parentNode;
304
+ return c === p;
305
+ }
306
+
307
+ // type conversions
308
+
309
+ function toStr(n) {
310
+ return "" + n;
311
+ }
312
+
313
+ function toFloat(n) {
314
+ return parseFloat(n);
315
+ }
316
+
317
+ function toDate(n) {
318
+ var matches, year, month, day;
319
+ if (typeof n !== "object") {
320
+ if (typeof n === "number") {
321
+ n = new Date(n * 1000); // ms
322
+ } else if ((matches = n.match(DATE_PATTERN))) {
323
+ year = parseInt(matches[1], 10);
324
+ month = parseInt(matches[3], 10) - 1;
325
+ day = parseInt(matches[5], 10);
326
+ return new Date(year, month, day);
327
+ } else { // str
328
+ // try our best to get the str into iso8601
329
+ // TODO be smarter about this
330
+ var str = n.replace(/ /, "T").replace(" ", "").replace("UTC", "Z");
331
+ n = parseISO8601(str) || new Date(n);
332
+ }
333
+ }
334
+ return n;
335
+ }
336
+
337
+ function toArr(n) {
338
+ if (!isArray(n)) {
339
+ var arr = [], i;
340
+ for (i in n) {
341
+ if (n.hasOwnProperty(i)) {
342
+ arr.push([i, n[i]]);
343
+ }
344
+ }
345
+ n = arr;
346
+ }
347
+ return n;
348
+ }
349
+
350
+ function sortByTime(a, b) {
351
+ return a[0].getTime() - b[0].getTime();
352
+ }
353
+
354
+ function sortByNumber(a, b) {
355
+ return a - b;
356
+ }
357
+
358
+ function loadAdapters() {
359
+ if (!HighchartsAdapter && "Highcharts" in window) {
360
+ HighchartsAdapter = new function () {
361
+ var Highcharts = window.Highcharts;
362
+
363
+ this.name = "highcharts";
364
+
365
+ var defaultOptions = {
366
+ chart: {},
367
+ xAxis: {
368
+ title: {
369
+ text: null
370
+ },
371
+ labels: {
372
+ style: {
373
+ fontSize: "12px"
374
+ }
375
+ }
376
+ },
377
+ yAxis: {
378
+ title: {
379
+ text: null
380
+ },
381
+ labels: {
382
+ style: {
383
+ fontSize: "12px"
384
+ }
385
+ }
386
+ },
387
+ title: {
388
+ text: null
389
+ },
390
+ credits: {
391
+ enabled: false
392
+ },
393
+ legend: {
394
+ borderWidth: 0
395
+ },
396
+ tooltip: {
397
+ style: {
398
+ fontSize: "12px"
399
+ }
400
+ },
401
+ plotOptions: {
402
+ areaspline: {},
403
+ series: {
404
+ marker: {}
405
+ }
406
+ }
407
+ };
408
+
409
+ var hideLegend = function (options, legend, hideLegend) {
410
+ if (legend !== undefined) {
411
+ options.legend.enabled = !!legend;
412
+ if (legend && legend !== true) {
413
+ if (legend === "top" || legend === "bottom") {
414
+ options.legend.verticalAlign = legend;
415
+ } else {
416
+ options.legend.layout = "vertical";
417
+ options.legend.verticalAlign = "middle";
418
+ options.legend.align = legend;
419
+ }
420
+ }
421
+ } else if (hideLegend) {
422
+ options.legend.enabled = false;
423
+ }
424
+ };
425
+
426
+ var setTitle = function (options, title) {
427
+ options.title.text = title;
428
+ };
429
+
430
+ var setMin = function (options, min) {
431
+ options.yAxis.min = min;
432
+ };
433
+
434
+ var setMax = function (options, max) {
435
+ options.yAxis.max = max;
436
+ };
437
+
438
+ var setStacked = function (options, stacked) {
439
+ options.plotOptions.series.stacking = stacked ? "normal" : null;
440
+ };
441
+
442
+ var setXtitle = function (options, title) {
443
+ options.xAxis.title.text = title;
444
+ };
445
+
446
+ var setYtitle = function (options, title) {
447
+ options.yAxis.title.text = title;
448
+ };
449
+
450
+ var jsOptions = jsOptionsFunc(defaultOptions, hideLegend, setTitle, setMin, setMax, setStacked, setXtitle, setYtitle);
451
+
452
+ this.renderLineChart = function (chart, chartType) {
453
+ chartType = chartType || "spline";
454
+ var chartOptions = {};
455
+ if (chartType === "areaspline") {
456
+ chartOptions = {
457
+ plotOptions: {
458
+ areaspline: {
459
+ stacking: "normal"
460
+ },
461
+ area: {
462
+ stacking: "normal"
463
+ },
464
+ series: {
465
+ marker: {
466
+ enabled: false
467
+ }
468
+ }
469
+ }
470
+ };
471
+ }
472
+
473
+ if (chart.options.curve === false) {
474
+ if (chartType === "areaspline") {
475
+ chartType = "area";
476
+ } else if (chartType === "spline") {
477
+ chartType = "line";
478
+ }
479
+ }
480
+
481
+ var options = jsOptions(chart, chart.options, chartOptions), data, i, j;
482
+ options.xAxis.type = chart.discrete ? "category" : "datetime";
483
+ if (!options.chart.type) {
484
+ options.chart.type = chartType;
485
+ }
486
+ options.chart.renderTo = chart.element.id;
487
+
488
+ var series = chart.data;
489
+ for (i = 0; i < series.length; i++) {
490
+ data = series[i].data;
491
+ if (!chart.discrete) {
492
+ for (j = 0; j < data.length; j++) {
493
+ data[j][0] = data[j][0].getTime();
494
+ }
495
+ }
496
+ series[i].marker = {symbol: "circle"};
497
+ }
498
+ options.series = series;
499
+ chart.chart = new Highcharts.Chart(options);
500
+ };
501
+
502
+ this.renderScatterChart = function (chart) {
503
+ var chartOptions = {};
504
+ var options = jsOptions(chart, chart.options, chartOptions);
505
+ options.chart.type = "scatter";
506
+ options.chart.renderTo = chart.element.id;
507
+ options.series = chart.data;
508
+ chart.chart = new Highcharts.Chart(options);
509
+ };
510
+
511
+ this.renderPieChart = function (chart) {
512
+ var chartOptions = merge(defaultOptions, {});
513
+
514
+ if (chart.options.colors) {
515
+ chartOptions.colors = chart.options.colors;
516
+ }
517
+ if (chart.options.donut) {
518
+ chartOptions.plotOptions = {pie: {innerSize: "50%"}};
519
+ }
520
+
521
+ if ("legend" in chart.options) {
522
+ hideLegend(chartOptions, chart.options.legend);
523
+ }
524
+
525
+ if (chart.options.title) {
526
+ setTitle(chartOptions, chart.options.title);
527
+ }
528
+
529
+ var options = merge(chartOptions, chart.options.library || {});
530
+ options.chart.renderTo = chart.element.id;
531
+ options.series = [{
532
+ type: "pie",
533
+ name: chart.options.label || "Value",
534
+ data: chart.data
535
+ }];
536
+ chart.chart = new Highcharts.Chart(options);
537
+ };
538
+
539
+ this.renderColumnChart = function (chart, chartType) {
540
+ chartType = chartType || "column";
541
+ var series = chart.data;
542
+ var options = jsOptions(chart, chart.options), i, j, s, d, rows = [], categories = [];
543
+ options.chart.type = chartType;
544
+ options.chart.renderTo = chart.element.id;
545
+
546
+ for (i = 0; i < series.length; i++) {
547
+ s = series[i];
548
+
549
+ for (j = 0; j < s.data.length; j++) {
550
+ d = s.data[j];
551
+ if (!rows[d[0]]) {
552
+ rows[d[0]] = new Array(series.length);
553
+ categories.push(d[0]);
554
+ }
555
+ rows[d[0]][i] = d[1];
556
+ }
557
+ }
558
+
559
+ options.xAxis.categories = categories;
560
+
561
+ var newSeries = [];
562
+ for (i = 0; i < series.length; i++) {
563
+ d = [];
564
+ for (j = 0; j < categories.length; j++) {
565
+ d.push(rows[categories[j]][i] || 0);
566
+ }
567
+
568
+ newSeries.push({
569
+ name: series[i].name,
570
+ data: d
571
+ });
572
+ }
573
+ options.series = newSeries;
574
+
575
+ chart.chart = new Highcharts.Chart(options);
576
+ };
577
+
578
+ var self = this;
579
+
580
+ this.renderBarChart = function (chart) {
581
+ self.renderColumnChart(chart, "bar");
582
+ };
583
+
584
+ this.renderAreaChart = function (chart) {
585
+ self.renderLineChart(chart, "areaspline");
586
+ };
587
+ };
588
+ adapters.push(HighchartsAdapter);
589
+ }
590
+ if (!GoogleChartsAdapter && window.google && (window.google.setOnLoadCallback || window.google.charts)) {
591
+ GoogleChartsAdapter = new function () {
592
+ var google = window.google;
593
+
594
+ this.name = "google";
595
+
596
+ var loaded = {};
597
+ var callbacks = [];
598
+
599
+ var runCallbacks = function () {
600
+ var cb, call;
601
+ for (var i = 0; i < callbacks.length; i++) {
602
+ cb = callbacks[i];
603
+ call = google.visualization && ((cb.pack === "corechart" && google.visualization.LineChart) || (cb.pack === "timeline" && google.visualization.Timeline));
604
+ if (call) {
605
+ cb.callback();
606
+ callbacks.splice(i, 1);
607
+ i--;
608
+ }
609
+ }
610
+ };
611
+
612
+ var waitForLoaded = function (pack, callback) {
613
+ if (!callback) {
614
+ callback = pack;
615
+ pack = "corechart";
616
+ }
617
+
618
+ callbacks.push({pack: pack, callback: callback});
619
+
620
+ if (loaded[pack]) {
621
+ runCallbacks();
622
+ } else {
623
+ loaded[pack] = true;
624
+
625
+ // https://groups.google.com/forum/#!topic/google-visualization-api/fMKJcyA2yyI
626
+ var loadOptions = {
627
+ packages: [pack],
628
+ callback: runCallbacks
629
+ };
630
+ if (config.language) {
631
+ loadOptions.language = config.language;
632
+ }
633
+
634
+ if (window.google.setOnLoadCallback) {
635
+ google.load("visualization", "1", loadOptions);
636
+ } else {
637
+ google.charts.load("current", loadOptions);
638
+ }
639
+ }
640
+ };
641
+
642
+ // Set chart options
643
+ var defaultOptions = {
644
+ chartArea: {},
645
+ fontName: "'Lucida Grande', 'Lucida Sans Unicode', Verdana, Arial, Helvetica, sans-serif",
646
+ pointSize: 6,
647
+ legend: {
648
+ textStyle: {
649
+ fontSize: 12,
650
+ color: "#444"
651
+ },
652
+ alignment: "center",
653
+ position: "right"
654
+ },
655
+ curveType: "function",
656
+ hAxis: {
657
+ textStyle: {
658
+ color: "#666",
659
+ fontSize: 12
660
+ },
661
+ titleTextStyle: {},
662
+ gridlines: {
663
+ color: "transparent"
664
+ },
665
+ baselineColor: "#ccc",
666
+ viewWindow: {}
667
+ },
668
+ vAxis: {
669
+ textStyle: {
670
+ color: "#666",
671
+ fontSize: 12
672
+ },
673
+ titleTextStyle: {},
674
+ baselineColor: "#ccc",
675
+ viewWindow: {}
676
+ },
677
+ tooltip: {
678
+ textStyle: {
679
+ color: "#666",
680
+ fontSize: 12
681
+ }
682
+ }
683
+ };
684
+
685
+ var hideLegend = function (options, legend, hideLegend) {
686
+ if (legend !== undefined) {
687
+ var position;
688
+ if (!legend) {
689
+ position = "none";
690
+ } else if (legend === true) {
691
+ position = "right";
692
+ } else {
693
+ position = legend;
694
+ }
695
+ options.legend.position = position;
696
+ } else if (hideLegend) {
697
+ options.legend.position = "none";
698
+ }
699
+ };
700
+
701
+ var setTitle = function (options, title) {
702
+ options.title = title;
703
+ options.titleTextStyle = {color: "#333", fontSize: "20px"};
704
+ };
705
+
706
+ var setMin = function (options, min) {
707
+ options.vAxis.viewWindow.min = min;
708
+ };
709
+
710
+ var setMax = function (options, max) {
711
+ options.vAxis.viewWindow.max = max;
712
+ };
713
+
714
+ var setBarMin = function (options, min) {
715
+ options.hAxis.viewWindow.min = min;
716
+ };
717
+
718
+ var setBarMax = function (options, max) {
719
+ options.hAxis.viewWindow.max = max;
720
+ };
721
+
722
+ var setStacked = function (options, stacked) {
723
+ options.isStacked = !!stacked;
724
+ };
725
+
726
+ var setXtitle = function (options, title) {
727
+ options.hAxis.title = title;
728
+ options.hAxis.titleTextStyle.italic = false;
729
+ };
730
+
731
+ var setYtitle = function (options, title) {
732
+ options.vAxis.title = title;
733
+ options.vAxis.titleTextStyle.italic = false;
734
+ };
735
+
736
+ var jsOptions = jsOptionsFunc(defaultOptions, hideLegend, setTitle, setMin, setMax, setStacked, setXtitle, setYtitle);
737
+
738
+ // cant use object as key
739
+ var createDataTable = function (series, columnType) {
740
+ var i, j, s, d, key, rows = [], sortedLabels = [];
741
+ for (i = 0; i < series.length; i++) {
742
+ s = series[i];
743
+
744
+ for (j = 0; j < s.data.length; j++) {
745
+ d = s.data[j];
746
+ key = (columnType === "datetime") ? d[0].getTime() : d[0];
747
+ if (!rows[key]) {
748
+ rows[key] = new Array(series.length);
749
+ sortedLabels.push(key);
750
+ }
751
+ rows[key][i] = toFloat(d[1]);
752
+ }
753
+ }
754
+
755
+ var rows2 = [];
756
+ var day = true;
757
+ var value;
758
+ for (var j = 0; j < sortedLabels.length; j++) {
759
+ var i = sortedLabels[j];
760
+ if (columnType === "datetime") {
761
+ value = new Date(toFloat(i));
762
+ day = day && isDay(value);
763
+ } else if (columnType === "number") {
764
+ value = toFloat(i);
765
+ } else {
766
+ value = i;
767
+ }
768
+ rows2.push([value].concat(rows[i]));
769
+ }
770
+ if (columnType === "datetime") {
771
+ rows2.sort(sortByTime);
772
+ }
773
+
774
+ // create datatable
775
+ var data = new google.visualization.DataTable();
776
+ columnType = columnType === "datetime" && day ? "date" : columnType;
777
+ data.addColumn(columnType, "");
778
+ for (i = 0; i < series.length; i++) {
779
+ data.addColumn("number", series[i].name);
780
+ }
781
+ data.addRows(rows2);
782
+
783
+ return data;
784
+ };
785
+
786
+ var resize = function (callback) {
787
+ if (window.attachEvent) {
788
+ window.attachEvent("onresize", callback);
789
+ } else if (window.addEventListener) {
790
+ window.addEventListener("resize", callback, true);
791
+ }
792
+ callback();
793
+ };
794
+
795
+ this.renderLineChart = function (chart) {
796
+ waitForLoaded(function () {
797
+ var chartOptions = {};
798
+
799
+ if (chart.options.curve === false) {
800
+ chartOptions.curveType = "none";
801
+ }
802
+
803
+ var options = jsOptions(chart, chart.options, chartOptions);
804
+ var data = createDataTable(chart.data, chart.discrete ? "string" : "datetime");
805
+ chart.chart = new google.visualization.LineChart(chart.element);
806
+ resize(function () {
807
+ chart.chart.draw(data, options);
808
+ });
809
+ });
810
+ };
811
+
812
+ this.renderPieChart = function (chart) {
813
+ waitForLoaded(function () {
814
+ var chartOptions = {
815
+ chartArea: {
816
+ top: "10%",
817
+ height: "80%"
818
+ },
819
+ legend: {}
820
+ };
821
+ if (chart.options.colors) {
822
+ chartOptions.colors = chart.options.colors;
823
+ }
824
+ if (chart.options.donut) {
825
+ chartOptions.pieHole = 0.5;
826
+ }
827
+ if ("legend" in chart.options) {
828
+ hideLegend(chartOptions, chart.options.legend);
829
+ }
830
+ if (chart.options.title) {
831
+ setTitle(chartOptions, chart.options.title);
832
+ }
833
+ var options = merge(merge(defaultOptions, chartOptions), chart.options.library || {});
834
+
835
+ var data = new google.visualization.DataTable();
836
+ data.addColumn("string", "");
837
+ data.addColumn("number", "Value");
838
+ data.addRows(chart.data);
839
+
840
+ chart.chart = new google.visualization.PieChart(chart.element);
841
+ resize(function () {
842
+ chart.chart.draw(data, options);
843
+ });
844
+ });
845
+ };
846
+
847
+ this.renderColumnChart = function (chart) {
848
+ waitForLoaded(function () {
849
+ var options = jsOptions(chart, chart.options);
850
+ var data = createDataTable(chart.data, "string");
851
+ chart.chart = new google.visualization.ColumnChart(chart.element);
852
+ resize(function () {
853
+ chart.chart.draw(data, options);
854
+ });
855
+ });
856
+ };
857
+
858
+ this.renderBarChart = function (chart) {
859
+ waitForLoaded(function () {
860
+ var chartOptions = {
861
+ hAxis: {
862
+ gridlines: {
863
+ color: "#ccc"
864
+ }
865
+ }
866
+ };
867
+ var options = jsOptionsFunc(defaultOptions, hideLegend, setTitle, setBarMin, setBarMax, setStacked, setXtitle, setYtitle)(chart, chart.options, chartOptions);
868
+ var data = createDataTable(chart.data, "string");
869
+ chart.chart = new google.visualization.BarChart(chart.element);
870
+ resize(function () {
871
+ chart.chart.draw(data, options);
872
+ });
873
+ });
874
+ };
875
+
876
+ this.renderAreaChart = function (chart) {
877
+ waitForLoaded(function () {
878
+ var chartOptions = {
879
+ isStacked: true,
880
+ pointSize: 0,
881
+ areaOpacity: 0.5
882
+ };
883
+ var options = jsOptions(chart, chart.options, chartOptions);
884
+ var data = createDataTable(chart.data, chart.discrete ? "string" : "datetime");
885
+ chart.chart = new google.visualization.AreaChart(chart.element);
886
+ resize(function () {
887
+ chart.chart.draw(data, options);
888
+ });
889
+ });
890
+ };
891
+
892
+ this.renderGeoChart = function (chart) {
893
+ waitForLoaded(function () {
894
+ var chartOptions = {
895
+ legend: "none",
896
+ colorAxis: {
897
+ colors: chart.options.colors || ["#f6c7b6", "#ce502d"]
898
+ }
899
+ };
900
+ var options = merge(merge(defaultOptions, chartOptions), chart.options.library || {});
901
+
902
+ var data = new google.visualization.DataTable();
903
+ data.addColumn("string", "");
904
+ data.addColumn("number", chart.options.label || "Value");
905
+ data.addRows(chart.data);
906
+
907
+ chart.chart = new google.visualization.GeoChart(chart.element);
908
+ resize(function () {
909
+ chart.chart.draw(data, options);
910
+ });
911
+ });
912
+ };
913
+
914
+ this.renderScatterChart = function (chart) {
915
+ waitForLoaded(function () {
916
+ var chartOptions = {};
917
+ var options = jsOptions(chart, chart.options, chartOptions);
918
+ var data = createDataTable(chart.data, "number");
919
+
920
+ chart.chart = new google.visualization.ScatterChart(chart.element);
921
+ resize(function () {
922
+ chart.chart.draw(data, options);
923
+ });
924
+ });
925
+ };
926
+
927
+ this.renderTimeline = function (chart) {
928
+ waitForLoaded("timeline", function () {
929
+ var chartOptions = {
930
+ legend: "none"
931
+ };
932
+
933
+ if (chart.options.colors) {
934
+ chartOptions.colors = chart.options.colors;
935
+ }
936
+ var options = merge(merge(defaultOptions, chartOptions), chart.options.library || {});
937
+
938
+ var data = new google.visualization.DataTable();
939
+ data.addColumn({type: "string", id: "Name"});
940
+ data.addColumn({type: "date", id: "Start"});
941
+ data.addColumn({type: "date", id: "End"});
942
+ data.addRows(chart.data);
943
+
944
+ chart.element.style.lineHeight = "normal";
945
+ chart.chart = new google.visualization.Timeline(chart.element);
946
+
947
+ resize(function () {
948
+ chart.chart.draw(data, options);
949
+ });
950
+ });
951
+ };
952
+ };
953
+
954
+ adapters.push(GoogleChartsAdapter);
955
+ }
956
+ if (!ChartjsAdapter && "Chart" in window) {
957
+ ChartjsAdapter = new function () {
958
+ var Chart = window.Chart;
959
+
960
+ this.name = "chartjs";
961
+
962
+ var baseOptions = {
963
+ maintainAspectRatio: false,
964
+ animation: false,
965
+ tooltips: {
966
+ displayColors: false
967
+ },
968
+ legend: {},
969
+ title: {fontSize: 20, fontColor: "#333"}
970
+ };
971
+
972
+ var defaultOptions = {
973
+ scales: {
974
+ yAxes: [
975
+ {
976
+ ticks: {
977
+ maxTicksLimit: 4
978
+ },
979
+ scaleLabel: {
980
+ fontSize: 16,
981
+ // fontStyle: "bold",
982
+ fontColor: "#333"
983
+ }
984
+ }
985
+ ],
986
+ xAxes: [
987
+ {
988
+ gridLines: {
989
+ drawOnChartArea: false
990
+ },
991
+ scaleLabel: {
992
+ fontSize: 16,
993
+ // fontStyle: "bold",
994
+ fontColor: "#333"
995
+ },
996
+ time: {},
997
+ ticks: {}
998
+ }
999
+ ]
1000
+ }
1001
+ };
1002
+
1003
+ // http://there4.io/2012/05/02/google-chart-color-list/
1004
+ var defaultColors = [
1005
+ "#3366CC", "#DC3912", "#FF9900", "#109618", "#990099", "#3B3EAC", "#0099C6",
1006
+ "#DD4477", "#66AA00", "#B82E2E", "#316395", "#994499", "#22AA99", "#AAAA11",
1007
+ "#6633CC", "#E67300", "#8B0707", "#329262", "#5574A6", "#3B3EAC"
1008
+ ];
1009
+
1010
+ var hideLegend = function (options, legend, hideLegend) {
1011
+ if (legend !== undefined) {
1012
+ options.legend.display = !!legend;
1013
+ if (legend && legend !== true) {
1014
+ options.legend.position = legend;
1015
+ }
1016
+ } else if (hideLegend) {
1017
+ options.legend.display = false;
1018
+ }
1019
+ };
1020
+
1021
+ var setTitle = function (options, title) {
1022
+ options.title.display = true;
1023
+ options.title.text = title;
1024
+ };
1025
+
1026
+ var setMin = function (options, min) {
1027
+ if (min !== null) {
1028
+ options.scales.yAxes[0].ticks.min = toFloat(min);
1029
+ }
1030
+ };
1031
+
1032
+ var setMax = function (options, max) {
1033
+ options.scales.yAxes[0].ticks.max = toFloat(max);
1034
+ };
1035
+
1036
+ var setBarMin = function (options, min) {
1037
+ if (min !== null) {
1038
+ options.scales.xAxes[0].ticks.min = toFloat(min);
1039
+ }
1040
+ };
1041
+
1042
+ var setBarMax = function (options, max) {
1043
+ options.scales.xAxes[0].ticks.max = toFloat(max);
1044
+ };
1045
+
1046
+ var setStacked = function (options, stacked) {
1047
+ options.scales.xAxes[0].stacked = !!stacked;
1048
+ options.scales.yAxes[0].stacked = !!stacked;
1049
+ };
1050
+
1051
+ var setXtitle = function (options, title) {
1052
+ options.scales.xAxes[0].scaleLabel.display = true;
1053
+ options.scales.xAxes[0].scaleLabel.labelString = title;
1054
+ };
1055
+
1056
+ var setYtitle = function (options, title) {
1057
+ options.scales.yAxes[0].scaleLabel.display = true;
1058
+ options.scales.yAxes[0].scaleLabel.labelString = title;
1059
+ };
1060
+
1061
+ var drawChart = function(chart, type, data, options) {
1062
+ if (chart.chart) {
1063
+ chart.chart.destroy();
1064
+ } else {
1065
+ chart.element.innerHTML = "<canvas></canvas>";
1066
+ }
1067
+
1068
+ var ctx = chart.element.getElementsByTagName("CANVAS")[0];
1069
+ chart.chart = new Chart(ctx, {
1070
+ type: type,
1071
+ data: data,
1072
+ options: options
1073
+ });
1074
+ };
1075
+
1076
+ // http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
1077
+ var addOpacity = function(hex, opacity) {
1078
+ var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
1079
+ return result ? "rgba(" + parseInt(result[1], 16) + ", " + parseInt(result[2], 16) + ", " + parseInt(result[3], 16) + ", " + opacity + ")" : hex;
1080
+ };
1081
+
1082
+ var setLabelSize = function (chart, data, options) {
1083
+ var maxLabelSize = Math.ceil(chart.element.offsetWidth / 4.0 / data.labels.length);
1084
+ if (maxLabelSize > 25) {
1085
+ maxLabelSize = 25;
1086
+ }
1087
+ options.scales.xAxes[0].ticks.callback = function (value) {
1088
+ value = toStr(value);
1089
+ if (value.length > maxLabelSize) {
1090
+ return value.substring(0, maxLabelSize - 2) + "...";
1091
+ } else {
1092
+ return value;
1093
+ }
1094
+ };
1095
+ };
1096
+
1097
+ var jsOptions = jsOptionsFunc(merge(baseOptions, defaultOptions), hideLegend, setTitle, setMin, setMax, setStacked, setXtitle, setYtitle);
1098
+
1099
+ var createDataTable = function (chart, options, chartType) {
1100
+ var datasets = [];
1101
+ var labels = [];
1102
+
1103
+ var colors = chart.options.colors || defaultColors;
1104
+
1105
+ var day = true;
1106
+ var week = true;
1107
+ var dayOfWeek;
1108
+ var month = true;
1109
+ var year = true;
1110
+ var hour = true;
1111
+ var minute = true;
1112
+ var detectType = (chartType === "line" || chartType === "area") && !chart.discrete;
1113
+
1114
+ var series = chart.data;
1115
+
1116
+ var sortedLabels = [];
1117
+
1118
+ var i, j, s, d, key, rows = [];
1119
+ for (i = 0; i < series.length; i++) {
1120
+ s = series[i];
1121
+
1122
+ for (j = 0; j < s.data.length; j++) {
1123
+ d = s.data[j];
1124
+ key = detectType ? d[0].getTime() : d[0];
1125
+ if (!rows[key]) {
1126
+ rows[key] = new Array(series.length);
1127
+ }
1128
+ rows[key][i] = toFloat(d[1]);
1129
+ if (sortedLabels.indexOf(key) === -1) {
1130
+ sortedLabels.push(key);
1131
+ }
1132
+ }
1133
+ }
1134
+
1135
+ if (detectType) {
1136
+ sortedLabels.sort(sortByNumber);
1137
+ }
1138
+
1139
+ var rows2 = [];
1140
+ for (j = 0; j < series.length; j++) {
1141
+ rows2.push([]);
1142
+ }
1143
+
1144
+ var value;
1145
+ var k;
1146
+ for (k = 0; k < sortedLabels.length; k++) {
1147
+ i = sortedLabels[k];
1148
+ if (detectType) {
1149
+ value = new Date(toFloat(i));
1150
+ // TODO make this efficient
1151
+ day = day && isDay(value);
1152
+ if (!dayOfWeek) {
1153
+ dayOfWeek = value.getDay();
1154
+ }
1155
+ week = week && isWeek(value, dayOfWeek);
1156
+ month = month && isMonth(value);
1157
+ year = year && isYear(value);
1158
+ hour = hour && isHour(value);
1159
+ minute = minute && isMinute(value);
1160
+ } else {
1161
+ value = i;
1162
+ }
1163
+ labels.push(value);
1164
+ for (j = 0; j < series.length; j++) {
1165
+ // Chart.js doesn't like undefined
1166
+ rows2[j].push(rows[i][j] === undefined ? null : rows[i][j]);
1167
+ }
1168
+ }
1169
+
1170
+ for (i = 0; i < series.length; i++) {
1171
+ s = series[i];
1172
+
1173
+ var backgroundColor = chartType !== "line" ? addOpacity(colors[i], 0.5) : colors[i];
1174
+
1175
+ var dataset = {
1176
+ label: s.name,
1177
+ data: rows2[i],
1178
+ fill: chartType === "area",
1179
+ borderColor: colors[i],
1180
+ backgroundColor: backgroundColor,
1181
+ pointBackgroundColor: colors[i],
1182
+ borderWidth: 2
1183
+ };
1184
+
1185
+ if (chart.options.curve === false) {
1186
+ dataset.lineTension = 0;
1187
+ }
1188
+
1189
+ datasets.push(merge(dataset, s.library || {}));
1190
+ }
1191
+
1192
+ if (detectType && labels.length > 0) {
1193
+ var minTime = labels[0].getTime();
1194
+ var maxTime = labels[0].getTime();
1195
+ for (i = 1; i < labels.length; i++) {
1196
+ value = labels[i].getTime();
1197
+ if (value < minTime) {
1198
+ minTime = value;
1199
+ }
1200
+ if (value > maxTime) {
1201
+ maxTime = value;
1202
+ }
1203
+ }
1204
+
1205
+ var timeDiff = (maxTime - minTime) / (86400 * 1000.0);
1206
+
1207
+ if (!options.scales.xAxes[0].time.unit) {
1208
+ var step;
1209
+ if (year || timeDiff > 365 * 10) {
1210
+ options.scales.xAxes[0].time.unit = "year";
1211
+ step = 365;
1212
+ } else if (month || timeDiff > 30 * 10) {
1213
+ options.scales.xAxes[0].time.unit = "month";
1214
+ step = 30;
1215
+ } else if (day || timeDiff > 10) {
1216
+ options.scales.xAxes[0].time.unit = "day";
1217
+ step = 1;
1218
+ } else if (hour || timeDiff > 0.5) {
1219
+ options.scales.xAxes[0].time.displayFormats = {hour: "MMM D, h a"};
1220
+ options.scales.xAxes[0].time.unit = "hour";
1221
+ step = 1 / 24.0;
1222
+ } else if (minute) {
1223
+ options.scales.xAxes[0].time.displayFormats = {minute: "h:mm a"};
1224
+ options.scales.xAxes[0].time.unit = "minute";
1225
+ step = 1 / 24.0 / 60.0;
1226
+ }
1227
+
1228
+ if (step && timeDiff > 0) {
1229
+ var unitStepSize = Math.ceil(timeDiff / step / (chart.element.offsetWidth / 100.0));
1230
+ if (week && step === 1) {
1231
+ unitStepSize = Math.ceil(unitStepSize / 7.0) * 7;
1232
+ }
1233
+ options.scales.xAxes[0].time.unitStepSize = unitStepSize;
1234
+ }
1235
+ }
1236
+
1237
+ if (!options.scales.xAxes[0].time.tooltipFormat) {
1238
+ if (day) {
1239
+ options.scales.xAxes[0].time.tooltipFormat = "ll";
1240
+ } else if (hour) {
1241
+ options.scales.xAxes[0].time.tooltipFormat = "MMM D, h a";
1242
+ } else if (minute) {
1243
+ options.scales.xAxes[0].time.tooltipFormat = "h:mm a";
1244
+ }
1245
+ }
1246
+ }
1247
+
1248
+ var data = {
1249
+ labels: labels,
1250
+ datasets: datasets
1251
+ };
1252
+
1253
+ return data;
1254
+ };
1255
+
1256
+ this.renderLineChart = function (chart, chartType) {
1257
+ var chartOptions = {};
1258
+ if (chartType === "area") {
1259
+ // TODO fix area stacked
1260
+ // chartOptions.stacked = true;
1261
+ }
1262
+ // fix for https://github.com/chartjs/Chart.js/issues/2441
1263
+ if (!chart.options.max && allZeros(chart.data)) {
1264
+ chartOptions.max = 1;
1265
+ }
1266
+
1267
+ var options = jsOptions(chart, merge(chartOptions, chart.options));
1268
+
1269
+ var data = createDataTable(chart, options, chartType || "line");
1270
+
1271
+ options.scales.xAxes[0].type = chart.discrete ? "category" : "time";
1272
+
1273
+ drawChart(chart, "line", data, options);
1274
+ };
1275
+
1276
+ this.renderPieChart = function (chart) {
1277
+ var options = merge({}, baseOptions);
1278
+ if (chart.options.donut) {
1279
+ options.cutoutPercentage = 50;
1280
+ }
1281
+
1282
+ if ("legend" in chart.options) {
1283
+ hideLegend(options, chart.options.legend);
1284
+ }
1285
+
1286
+ if (chart.options.title) {
1287
+ setTitle(options, chart.options.title);
1288
+ }
1289
+
1290
+ options = merge(options, chart.options.library || {});
1291
+
1292
+ var labels = [];
1293
+ var values = [];
1294
+ for (var i = 0; i < chart.data.length; i++) {
1295
+ var point = chart.data[i];
1296
+ labels.push(point[0]);
1297
+ values.push(point[1]);
1298
+ }
1299
+
1300
+ var data = {
1301
+ labels: labels,
1302
+ datasets: [
1303
+ {
1304
+ data: values,
1305
+ backgroundColor: chart.options.colors || defaultColors
1306
+ }
1307
+ ]
1308
+ };
1309
+
1310
+ drawChart(chart, "pie", data, options);
1311
+ };
1312
+
1313
+ this.renderColumnChart = function (chart, chartType) {
1314
+ var options;
1315
+ if (chartType === "bar") {
1316
+ options = jsOptionsFunc(merge(baseOptions, defaultOptions), hideLegend, setTitle, setBarMin, setBarMax, setStacked, setXtitle, setYtitle)(chart, chart.options);
1317
+ } else {
1318
+ options = jsOptions(chart, chart.options);
1319
+ }
1320
+ var data = createDataTable(chart, options, "column");
1321
+ setLabelSize(chart, data, options);
1322
+ drawChart(chart, (chartType === "bar" ? "horizontalBar" : "bar"), data, options);
1323
+ };
1324
+
1325
+ var self = this;
1326
+
1327
+ this.renderAreaChart = function (chart) {
1328
+ self.renderLineChart(chart, "area");
1329
+ };
1330
+
1331
+ this.renderBarChart = function (chart) {
1332
+ self.renderColumnChart(chart, "bar");
1333
+ };
1334
+
1335
+ this.renderScatterChart = function (chart) {
1336
+ var options = jsOptions(chart, chart.options);
1337
+
1338
+ var colors = chart.options.colors || defaultColors;
1339
+
1340
+ var datasets = [];
1341
+ var series = chart.data;
1342
+ for (var i = 0; i < series.length; i++) {
1343
+ var s = series[i];
1344
+ var d = [];
1345
+ for (var j = 0; j < s.data.length; j++) {
1346
+ d.push({
1347
+ x: toFloat(s.data[j][0]),
1348
+ y: toFloat(s.data[j][1])
1349
+ });
1350
+ }
1351
+
1352
+ datasets.push({
1353
+ label: s.name,
1354
+ showLine: false,
1355
+ data: d,
1356
+ borderColor: colors[i],
1357
+ backgroundColor: colors[i],
1358
+ pointBackgroundColor: colors[i]
1359
+ })
1360
+ }
1361
+
1362
+ var data = {datasets: datasets};
1363
+
1364
+ options.scales.xAxes[0].type = "linear";
1365
+ options.scales.xAxes[0].position = "bottom";
1366
+
1367
+ drawChart(chart, "line", data, options);
1368
+ };
1369
+ };
1370
+
1371
+ adapters.unshift(ChartjsAdapter);
1372
+ }
1373
+ }
1374
+
1375
+ function renderChart(chartType, chart) {
1376
+ callAdapter(chartType, chart);
1377
+ if (chart.options.download && !chart.downloadAttached && chart.adapter === "chartjs") {
1378
+ addDownloadButton(chart);
1379
+ }
1380
+ }
1381
+
1382
+ // TODO remove chartType if cross-browser way
1383
+ // to get the name of the chart class
1384
+ function callAdapter(chartType, chart) {
1385
+ var i, adapter, fnName, adapterName;
1386
+ fnName = "render" + chartType;
1387
+ adapterName = chart.options.adapter;
1388
+
1389
+ loadAdapters();
1390
+
1391
+ for (i = 0; i < adapters.length; i++) {
1392
+ adapter = adapters[i];
1393
+ if ((!adapterName || adapterName === adapter.name) && isFunction(adapter[fnName])) {
1394
+ chart.adapter = adapter.name;
1395
+ return adapter[fnName](chart);
1396
+ }
1397
+ }
1398
+ throw new Error("No adapter found");
1399
+ }
1400
+
1401
+ // process data
1402
+
1403
+ var toFormattedKey = function (key, keyType) {
1404
+ if (keyType === "number") {
1405
+ key = toFloat(key);
1406
+ } else if (keyType === "datetime") {
1407
+ key = toDate(key);
1408
+ } else {
1409
+ key = toStr(key);
1410
+ }
1411
+ return key;
1412
+ };
1413
+
1414
+ var formatSeriesData = function (data, keyType) {
1415
+ var r = [], key, j;
1416
+ for (j = 0; j < data.length; j++) {
1417
+ key = toFormattedKey(data[j][0], keyType);
1418
+ r.push([key, toFloat(data[j][1])]);
1419
+ }
1420
+ if (keyType === "datetime") {
1421
+ r.sort(sortByTime);
1422
+ }
1423
+ return r;
1424
+ };
1425
+
1426
+ function isMinute(d) {
1427
+ return d.getMilliseconds() === 0 && d.getSeconds() === 0;
1428
+ }
1429
+
1430
+ function isHour(d) {
1431
+ return isMinute(d) && d.getMinutes() === 0;
1432
+ }
1433
+
1434
+ function isDay(d) {
1435
+ return isHour(d) && d.getHours() === 0;
1436
+ }
1437
+
1438
+ function isWeek(d, dayOfWeek) {
1439
+ return isDay(d) && d.getDay() === dayOfWeek;
1440
+ }
1441
+
1442
+ function isMonth(d) {
1443
+ return isDay(d) && d.getDate() === 1;
1444
+ }
1445
+
1446
+ function isYear(d) {
1447
+ return isMonth(d) && d.getMonth() === 0;
1448
+ }
1449
+
1450
+ function isDate(obj) {
1451
+ return !isNaN(toDate(obj)) && toStr(obj).length >= 6;
1452
+ }
1453
+
1454
+ function allZeros(data) {
1455
+ var i, j, d;
1456
+ for (i = 0; i < data.length; i++) {
1457
+ d = data[i].data;
1458
+ for (j = 0; j < d.length; j++) {
1459
+ if (d[j][1] != 0) {
1460
+ return false;
1461
+ }
1462
+ }
1463
+ }
1464
+ return true;
1465
+ }
1466
+
1467
+ function detectDiscrete(series) {
1468
+ var i, j, data;
1469
+ for (i = 0; i < series.length; i++) {
1470
+ data = toArr(series[i].data);
1471
+ for (j = 0; j < data.length; j++) {
1472
+ if (!isDate(data[j][0])) {
1473
+ return true;
1474
+ }
1475
+ }
1476
+ }
1477
+ return false;
1478
+ }
1479
+
1480
+ function processSeries(chart, keyType) {
1481
+ var i;
1482
+
1483
+ var opts = chart.options;
1484
+ var series = chart.rawData;
1485
+
1486
+ // see if one series or multiple
1487
+ if (!isArray(series) || typeof series[0] !== "object" || isArray(series[0])) {
1488
+ series = [{name: opts.label || "Value", data: series}];
1489
+ chart.hideLegend = true;
1490
+ } else {
1491
+ chart.hideLegend = false;
1492
+ }
1493
+ if ((opts.discrete === null || opts.discrete === undefined)) {
1494
+ chart.discrete = detectDiscrete(series);
1495
+ } else {
1496
+ chart.discrete = opts.discrete;
1497
+ }
1498
+ if (chart.discrete) {
1499
+ keyType = "string";
1500
+ }
1501
+
1502
+ // right format
1503
+ for (i = 0; i < series.length; i++) {
1504
+ series[i].data = formatSeriesData(toArr(series[i].data), keyType);
1505
+ }
1506
+
1507
+ return series;
1508
+ }
1509
+
1510
+ function processSimple(chart) {
1511
+ var perfectData = toArr(chart.rawData), i;
1512
+ for (i = 0; i < perfectData.length; i++) {
1513
+ perfectData[i] = [toStr(perfectData[i][0]), toFloat(perfectData[i][1])];
1514
+ }
1515
+ return perfectData;
1516
+ }
1517
+
1518
+ function processTime(chart)
1519
+ {
1520
+ var i, data = chart.rawData;
1521
+ for (i = 0; i < data.length; i++) {
1522
+ data[i][1] = toDate(data[i][1]);
1523
+ data[i][2] = toDate(data[i][2]);
1524
+ }
1525
+ return data;
1526
+ }
1527
+
1528
+ function processLineData(chart) {
1529
+ return processSeries(chart, "datetime");
1530
+ }
1531
+
1532
+ function processColumnData(chart) {
1533
+ return processSeries(chart, "string");
1534
+ }
1535
+
1536
+ function processBarData(chart) {
1537
+ return processSeries(chart, "string");
1538
+ }
1539
+
1540
+ function processAreaData(chart) {
1541
+ return processSeries(chart, "datetime");
1542
+ }
1543
+
1544
+ function processScatterData(chart) {
1545
+ return processSeries(chart, "number");
1546
+ }
1547
+
1548
+ function createChart(chartType, chart, element, dataSource, opts, processData) {
1549
+ var elementId;
1550
+ if (typeof element === "string") {
1551
+ elementId = element;
1552
+ element = document.getElementById(element);
1553
+ if (!element) {
1554
+ throw new Error("No element with id " + elementId);
1555
+ }
1556
+ }
1557
+
1558
+ chart.element = element;
1559
+ opts = merge(Chartkick.options, opts || {});
1560
+ chart.options = opts;
1561
+ chart.dataSource = dataSource;
1562
+
1563
+ if (!processData) {
1564
+ processData = function (chart) {
1565
+ return chart.rawData;
1566
+ }
1567
+ }
1568
+
1569
+ // getters
1570
+ chart.getElement = function () {
1571
+ return element;
1572
+ };
1573
+ chart.getDataSource = function () {
1574
+ return chart.dataSource;
1575
+ };
1576
+ chart.getData = function () {
1577
+ return chart.data;
1578
+ };
1579
+ chart.getOptions = function () {
1580
+ return chart.options;
1581
+ };
1582
+ chart.getChartObject = function () {
1583
+ return chart.chart;
1584
+ };
1585
+ chart.getAdapter = function () {
1586
+ return chart.adapter;
1587
+ };
1588
+
1589
+ var callback = function () {
1590
+ chart.data = processData(chart);
1591
+ renderChart(chartType, chart);
1592
+ };
1593
+
1594
+ // functions
1595
+ chart.updateData = function (dataSource, options) {
1596
+ chart.dataSource = dataSource;
1597
+ if (options) {
1598
+ chart.options = merge(Chartkick.options, options);
1599
+ }
1600
+ fetchDataSource(chart, callback, dataSource);
1601
+ };
1602
+ chart.setOptions = function (options) {
1603
+ chart.options = merge(Chartkick.options, options);
1604
+ chart.redraw();
1605
+ };
1606
+ chart.redraw = function() {
1607
+ fetchDataSource(chart, callback, chart.rawData);
1608
+ };
1609
+ chart.refreshData = function () {
1610
+ if (typeof dataSource === "string") {
1611
+ // prevent browser from caching
1612
+ var sep = dataSource.indexOf("?") === -1 ? "?" : "&";
1613
+ var url = dataSource + sep + "_=" + (new Date()).getTime();
1614
+ fetchDataSource(chart, callback, url);
1615
+ }
1616
+ };
1617
+ chart.stopRefresh = function () {
1618
+ if (chart.intervalId) {
1619
+ clearInterval(chart.intervalId);
1620
+ }
1621
+ };
1622
+ chart.toImage = function () {
1623
+ if (chart.adapter === "chartjs") {
1624
+ return chart.chart.toBase64Image();
1625
+ } else {
1626
+ return null;
1627
+ }
1628
+ }
1629
+
1630
+ Chartkick.charts[element.id] = chart;
1631
+
1632
+ fetchDataSource(chart, callback, dataSource);
1633
+
1634
+ if (opts.refresh) {
1635
+ chart.intervalId = setInterval( function () {
1636
+ chart.refreshData();
1637
+ }, opts.refresh * 1000);
1638
+ }
1639
+ }
1640
+
1641
+ // define classes
1642
+
1643
+ Chartkick = {
1644
+ LineChart: function (element, dataSource, options) {
1645
+ createChart("LineChart", this, element, dataSource, options, processLineData);
1646
+ },
1647
+ PieChart: function (element, dataSource, options) {
1648
+ createChart("PieChart", this, element, dataSource, options, processSimple);
1649
+ },
1650
+ ColumnChart: function (element, dataSource, options) {
1651
+ createChart("ColumnChart", this, element, dataSource, options, processColumnData);
1652
+ },
1653
+ BarChart: function (element, dataSource, options) {
1654
+ createChart("BarChart", this, element, dataSource, options, processBarData);
1655
+ },
1656
+ AreaChart: function (element, dataSource, options) {
1657
+ createChart("AreaChart", this, element, dataSource, options, processAreaData);
1658
+ },
1659
+ GeoChart: function (element, dataSource, options) {
1660
+ createChart("GeoChart", this, element, dataSource, options, processSimple);
1661
+ },
1662
+ ScatterChart: function (element, dataSource, options) {
1663
+ createChart("ScatterChart", this, element, dataSource, options, processScatterData);
1664
+ },
1665
+ Timeline: function (element, dataSource, options) {
1666
+ createChart("Timeline", this, element, dataSource, options, processTime);
1667
+ },
1668
+ charts: {},
1669
+ configure: function (options) {
1670
+ for (var key in options) {
1671
+ if (options.hasOwnProperty(key)) {
1672
+ config[key] = options[key];
1673
+ }
1674
+ }
1675
+ },
1676
+ eachChart: function (callback) {
1677
+ for (var chartId in Chartkick.charts) {
1678
+ if (Chartkick.charts.hasOwnProperty(chartId)) {
1679
+ callback(Chartkick.charts[chartId]);
1680
+ }
1681
+ }
1682
+ },
1683
+ options: {},
1684
+ adapters: adapters,
1685
+ createChart: createChart
1686
+ };
1687
+
1688
+ if (typeof module === "object" && typeof module.exports === "object") {
1689
+ module.exports = Chartkick;
1690
+ } else {
1691
+ window.Chartkick = Chartkick;
1692
+ }
1693
+ }(window));