sql-jarvis 1.8.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.
Files changed (98) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/CHANGELOG.md +228 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +775 -0
  7. data/Rakefile +1 -0
  8. data/app/assets/fonts/blazer/glyphicons-halflings-regular.eot +0 -0
  9. data/app/assets/fonts/blazer/glyphicons-halflings-regular.svg +288 -0
  10. data/app/assets/fonts/blazer/glyphicons-halflings-regular.ttf +0 -0
  11. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff +0 -0
  12. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff2 +0 -0
  13. data/app/assets/javascripts/blazer/Chart.js +14145 -0
  14. data/app/assets/javascripts/blazer/Sortable.js +1144 -0
  15. data/app/assets/javascripts/blazer/ace.js +6 -0
  16. data/app/assets/javascripts/blazer/ace/ace.js +11 -0
  17. data/app/assets/javascripts/blazer/ace/ext-language_tools.js +5 -0
  18. data/app/assets/javascripts/blazer/ace/mode-sql.js +1 -0
  19. data/app/assets/javascripts/blazer/ace/snippets/sql.js +1 -0
  20. data/app/assets/javascripts/blazer/ace/snippets/text.js +1 -0
  21. data/app/assets/javascripts/blazer/ace/theme-twilight.js +1 -0
  22. data/app/assets/javascripts/blazer/application.js +79 -0
  23. data/app/assets/javascripts/blazer/bootstrap.js +2366 -0
  24. data/app/assets/javascripts/blazer/chartkick.js +1693 -0
  25. data/app/assets/javascripts/blazer/daterangepicker.js +1505 -0
  26. data/app/assets/javascripts/blazer/fuzzysearch.js +24 -0
  27. data/app/assets/javascripts/blazer/highlight.pack.js +1 -0
  28. data/app/assets/javascripts/blazer/jquery.js +10308 -0
  29. data/app/assets/javascripts/blazer/jquery.stickytableheaders.js +263 -0
  30. data/app/assets/javascripts/blazer/jquery_ujs.js +469 -0
  31. data/app/assets/javascripts/blazer/moment-timezone.js +1007 -0
  32. data/app/assets/javascripts/blazer/moment.js +3043 -0
  33. data/app/assets/javascripts/blazer/queries.js +110 -0
  34. data/app/assets/javascripts/blazer/routes.js +23 -0
  35. data/app/assets/javascripts/blazer/selectize.js +3667 -0
  36. data/app/assets/javascripts/blazer/stupidtable.js +114 -0
  37. data/app/assets/javascripts/blazer/vue.js +7515 -0
  38. data/app/assets/stylesheets/blazer/application.css +198 -0
  39. data/app/assets/stylesheets/blazer/bootstrap.css.erb +6202 -0
  40. data/app/assets/stylesheets/blazer/daterangepicker-bs3.css +375 -0
  41. data/app/assets/stylesheets/blazer/github.css +125 -0
  42. data/app/assets/stylesheets/blazer/selectize.default.css +387 -0
  43. data/app/controllers/blazer/base_controller.rb +103 -0
  44. data/app/controllers/blazer/checks_controller.rb +56 -0
  45. data/app/controllers/blazer/dashboards_controller.rb +105 -0
  46. data/app/controllers/blazer/queries_controller.rb +325 -0
  47. data/app/helpers/blazer/base_helper.rb +57 -0
  48. data/app/mailers/blazer/check_mailer.rb +27 -0
  49. data/app/models/blazer/audit.rb +6 -0
  50. data/app/models/blazer/check.rb +95 -0
  51. data/app/models/blazer/connection.rb +5 -0
  52. data/app/models/blazer/dashboard.rb +13 -0
  53. data/app/models/blazer/dashboard_query.rb +9 -0
  54. data/app/models/blazer/query.rb +31 -0
  55. data/app/models/blazer/record.rb +5 -0
  56. data/app/views/blazer/_nav.html.erb +16 -0
  57. data/app/views/blazer/_variables.html.erb +102 -0
  58. data/app/views/blazer/check_mailer/failing_checks.html.erb +6 -0
  59. data/app/views/blazer/check_mailer/state_change.html.erb +47 -0
  60. data/app/views/blazer/checks/_form.html.erb +71 -0
  61. data/app/views/blazer/checks/edit.html.erb +1 -0
  62. data/app/views/blazer/checks/index.html.erb +40 -0
  63. data/app/views/blazer/checks/new.html.erb +1 -0
  64. data/app/views/blazer/dashboards/_form.html.erb +76 -0
  65. data/app/views/blazer/dashboards/edit.html.erb +1 -0
  66. data/app/views/blazer/dashboards/new.html.erb +1 -0
  67. data/app/views/blazer/dashboards/show.html.erb +47 -0
  68. data/app/views/blazer/queries/_form.html.erb +240 -0
  69. data/app/views/blazer/queries/edit.html.erb +2 -0
  70. data/app/views/blazer/queries/home.html.erb +152 -0
  71. data/app/views/blazer/queries/new.html.erb +2 -0
  72. data/app/views/blazer/queries/run.html.erb +163 -0
  73. data/app/views/blazer/queries/schema.html.erb +18 -0
  74. data/app/views/blazer/queries/show.html.erb +73 -0
  75. data/app/views/layouts/blazer/application.html.erb +24 -0
  76. data/blazer.gemspec +26 -0
  77. data/config/routes.rb +16 -0
  78. data/lib/blazer.rb +185 -0
  79. data/lib/blazer/adapters/athena_adapter.rb +128 -0
  80. data/lib/blazer/adapters/base_adapter.rb +53 -0
  81. data/lib/blazer/adapters/bigquery_adapter.rb +67 -0
  82. data/lib/blazer/adapters/drill_adapter.rb +28 -0
  83. data/lib/blazer/adapters/elasticsearch_adapter.rb +49 -0
  84. data/lib/blazer/adapters/mongodb_adapter.rb +39 -0
  85. data/lib/blazer/adapters/presto_adapter.rb +45 -0
  86. data/lib/blazer/adapters/sql_adapter.rb +182 -0
  87. data/lib/blazer/data_source.rb +193 -0
  88. data/lib/blazer/detect_anomalies.R +19 -0
  89. data/lib/blazer/engine.rb +47 -0
  90. data/lib/blazer/result.rb +170 -0
  91. data/lib/blazer/run_statement.rb +40 -0
  92. data/lib/blazer/run_statement_job.rb +21 -0
  93. data/lib/blazer/version.rb +3 -0
  94. data/lib/generators/blazer/install_generator.rb +39 -0
  95. data/lib/generators/blazer/templates/config.yml +62 -0
  96. data/lib/generators/blazer/templates/install.rb +45 -0
  97. data/lib/tasks/blazer.rake +10 -0
  98. metadata +211 -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));