pghero 1.4.0 → 1.4.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of pghero might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -1
- data/app/assets/javascripts/pghero/Chart.bundle.js +14591 -0
- data/app/assets/javascripts/pghero/application.js +141 -0
- data/app/assets/javascripts/pghero/chartkick.js +1396 -0
- data/app/assets/javascripts/pghero/jquery.js +11008 -0
- data/app/assets/javascripts/pghero/jquery.nouislider.min.js +31 -0
- data/app/assets/stylesheets/pghero/application.css +409 -0
- data/app/assets/stylesheets/pghero/jquery.nouislider.css +165 -0
- data/app/views/layouts/pg_hero/application.html.erb +2 -384
- data/app/views/pg_hero/home/_query_stats_slider.html.erb +2 -162
- data/app/views/pg_hero/home/system.html.erb +4 -6
- data/lib/pghero/engine.rb +11 -0
- data/lib/pghero/version.rb +1 -1
- metadata +9 -2
@@ -0,0 +1,141 @@
|
|
1
|
+
//= require ./jquery
|
2
|
+
//= require ./jquery.nouislider.min
|
3
|
+
//= require ./Chart.bundle
|
4
|
+
//= require ./chartkick
|
5
|
+
|
6
|
+
function initSlider() {
|
7
|
+
function roundTime(time) {
|
8
|
+
var period = 1000 * 60 * 5;
|
9
|
+
return new Date(Math.ceil(time.getTime() / period) * period);
|
10
|
+
}
|
11
|
+
|
12
|
+
function pad(num) {
|
13
|
+
return (num < 10) ? "0" + num : num;
|
14
|
+
}
|
15
|
+
|
16
|
+
var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
17
|
+
|
18
|
+
var days = 1;
|
19
|
+
var now = new Date();
|
20
|
+
var sliderStartAt = roundTime(now) - days * 24 * 60 * 60 * 1000;
|
21
|
+
var sliderMax = 24 * 12 * days;
|
22
|
+
|
23
|
+
startAt = startAt || sliderStartAt;
|
24
|
+
var min = (startAt > 0) ? (startAt - sliderStartAt) / (1000 * 60 * 5) : 0;
|
25
|
+
|
26
|
+
var max = (endAt > 0) ? (endAt - sliderStartAt) / (1000 * 60 * 5) : sliderMax;
|
27
|
+
|
28
|
+
var $slider = $("#slider");
|
29
|
+
|
30
|
+
$slider.noUiSlider({
|
31
|
+
range: {
|
32
|
+
min: 0,
|
33
|
+
max: sliderMax
|
34
|
+
},
|
35
|
+
step: 1,
|
36
|
+
connect: true,
|
37
|
+
start: [min, max]
|
38
|
+
});
|
39
|
+
|
40
|
+
function updateText() {
|
41
|
+
var values = $slider.val();
|
42
|
+
setText("#range-start", values[0]);
|
43
|
+
setText("#range-end", values[1]);
|
44
|
+
}
|
45
|
+
|
46
|
+
function setText(selector, offset) {
|
47
|
+
var time = timeAt(offset);
|
48
|
+
|
49
|
+
var html = "";
|
50
|
+
if (time == now) {
|
51
|
+
if (selector == "#range-end") {
|
52
|
+
html = "Now";
|
53
|
+
}
|
54
|
+
} else {
|
55
|
+
html = months[time.getMonth()] + " " + time.getDate() + ", " + pad(time.getHours()) + ":" + pad(time.getMinutes());
|
56
|
+
}
|
57
|
+
$(selector).html(html);
|
58
|
+
}
|
59
|
+
|
60
|
+
function timeAt(offset) {
|
61
|
+
var time = new Date(sliderStartAt + (offset * 5) * 60 * 1000);
|
62
|
+
return (time > now) ? now : time;
|
63
|
+
}
|
64
|
+
|
65
|
+
function timeParam(time) {
|
66
|
+
return time.toISOString();
|
67
|
+
}
|
68
|
+
|
69
|
+
function queriesPath(params) {
|
70
|
+
var path = "queries";
|
71
|
+
if (params.start_at || params.end_at || params.sort || params.min_average_time || params.min_calls || params.debug) {
|
72
|
+
path += "?" + $.param(params);
|
73
|
+
}
|
74
|
+
return path;
|
75
|
+
}
|
76
|
+
|
77
|
+
function refreshStats(push) {
|
78
|
+
var values = $slider.val();
|
79
|
+
var startAt = push ? timeAt(values[0]) : new Date(window.startAt);
|
80
|
+
var endAt = timeAt(values[1]);
|
81
|
+
|
82
|
+
var params = {}
|
83
|
+
if (startAt.getTime() != sliderStartAt) {
|
84
|
+
params.start_at = timeParam(startAt);
|
85
|
+
}
|
86
|
+
if (endAt < now) {
|
87
|
+
params.end_at = timeParam(endAt);
|
88
|
+
}
|
89
|
+
if (sort) {
|
90
|
+
params.sort = sort;
|
91
|
+
}
|
92
|
+
if (minAverageTime) {
|
93
|
+
params.min_average_time = minAverageTime;
|
94
|
+
}
|
95
|
+
if (minCalls) {
|
96
|
+
params.min_calls = minCalls;
|
97
|
+
}
|
98
|
+
if (debug) {
|
99
|
+
params.debug = debug;
|
100
|
+
}
|
101
|
+
|
102
|
+
var path = queriesPath(params);
|
103
|
+
|
104
|
+
$(".queries-table th a").each( function () {
|
105
|
+
var p = $.extend({}, params, {sort: $(this).data("sort"), min_average_time: minAverageTime, min_calls: minCalls, debug: debug});
|
106
|
+
if (!p.sort) {
|
107
|
+
delete p.sort;
|
108
|
+
}
|
109
|
+
if (!p.min_average_time) {
|
110
|
+
delete p.min_average_time;
|
111
|
+
}
|
112
|
+
if (!p.min_calls) {
|
113
|
+
delete p.min_calls;
|
114
|
+
}
|
115
|
+
if (!p.debug) {
|
116
|
+
delete p.debug;
|
117
|
+
}
|
118
|
+
$(this).attr("href", queriesPath(p));
|
119
|
+
});
|
120
|
+
|
121
|
+
|
122
|
+
var callback = function (response, status, xhr) {
|
123
|
+
if (status === "error" ) {
|
124
|
+
$(".queries-info").css("color", "red").html(xhr.status + " " + xhr.statusText);
|
125
|
+
}
|
126
|
+
};
|
127
|
+
$("#queries").html('<tr><td colspan="3"><p class="queries-info text-muted">...</p></td></tr>').load(path, callback);
|
128
|
+
|
129
|
+
if (push && history.pushState) {
|
130
|
+
history.pushState(null, null, path);
|
131
|
+
}
|
132
|
+
}
|
133
|
+
|
134
|
+
$slider.on("slide", updateText).on("change", function () {
|
135
|
+
refreshStats(true);
|
136
|
+
});
|
137
|
+
updateText();
|
138
|
+
$( function () {
|
139
|
+
refreshStats(false);
|
140
|
+
});
|
141
|
+
}
|
@@ -0,0 +1,1396 @@
|
|
1
|
+
/*
|
2
|
+
* Chartkick.js
|
3
|
+
* Create beautiful JavaScript charts with minimal code
|
4
|
+
* https://github.com/ankane/chartkick.js
|
5
|
+
* v2.0.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
|
+
|
19
|
+
// helpers
|
20
|
+
|
21
|
+
function isArray(variable) {
|
22
|
+
return Object.prototype.toString.call(variable) === "[object Array]";
|
23
|
+
}
|
24
|
+
|
25
|
+
function isFunction(variable) {
|
26
|
+
return variable instanceof Function;
|
27
|
+
}
|
28
|
+
|
29
|
+
function isPlainObject(variable) {
|
30
|
+
return !isFunction(variable) && variable instanceof Object;
|
31
|
+
}
|
32
|
+
|
33
|
+
// https://github.com/madrobby/zepto/blob/master/src/zepto.js
|
34
|
+
function extend(target, source) {
|
35
|
+
var key;
|
36
|
+
for (key in source) {
|
37
|
+
if (isPlainObject(source[key]) || isArray(source[key])) {
|
38
|
+
if (isPlainObject(source[key]) && !isPlainObject(target[key])) {
|
39
|
+
target[key] = {};
|
40
|
+
}
|
41
|
+
if (isArray(source[key]) && !isArray(target[key])) {
|
42
|
+
target[key] = [];
|
43
|
+
}
|
44
|
+
extend(target[key], source[key]);
|
45
|
+
} else if (source[key] !== undefined) {
|
46
|
+
target[key] = source[key];
|
47
|
+
}
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
function merge(obj1, obj2) {
|
52
|
+
var target = {};
|
53
|
+
extend(target, obj1);
|
54
|
+
extend(target, obj2);
|
55
|
+
return target;
|
56
|
+
}
|
57
|
+
|
58
|
+
// https://github.com/Do/iso8601.js
|
59
|
+
ISO8601_PATTERN = /(\d\d\d\d)(\-)?(\d\d)(\-)?(\d\d)(T)?(\d\d)(:)?(\d\d)?(:)?(\d\d)?([\.,]\d+)?($|Z|([\+\-])(\d\d)(:)?(\d\d)?)/i;
|
60
|
+
DECIMAL_SEPARATOR = String(1.5).charAt(1);
|
61
|
+
|
62
|
+
function parseISO8601(input) {
|
63
|
+
var day, hour, matches, milliseconds, minutes, month, offset, result, seconds, type, year;
|
64
|
+
type = Object.prototype.toString.call(input);
|
65
|
+
if (type === '[object Date]') {
|
66
|
+
return input;
|
67
|
+
}
|
68
|
+
if (type !== '[object String]') {
|
69
|
+
return;
|
70
|
+
}
|
71
|
+
matches = input.match(ISO8601_PATTERN);
|
72
|
+
if (matches) {
|
73
|
+
year = parseInt(matches[1], 10);
|
74
|
+
month = parseInt(matches[3], 10) - 1;
|
75
|
+
day = parseInt(matches[5], 10);
|
76
|
+
hour = parseInt(matches[7], 10);
|
77
|
+
minutes = matches[9] ? parseInt(matches[9], 10) : 0;
|
78
|
+
seconds = matches[11] ? parseInt(matches[11], 10) : 0;
|
79
|
+
milliseconds = matches[12] ? parseFloat(DECIMAL_SEPARATOR + matches[12].slice(1)) * 1000 : 0;
|
80
|
+
result = Date.UTC(year, month, day, hour, minutes, seconds, milliseconds);
|
81
|
+
if (matches[13] && matches[14]) {
|
82
|
+
offset = matches[15] * 60;
|
83
|
+
if (matches[17]) {
|
84
|
+
offset += parseInt(matches[17], 10);
|
85
|
+
}
|
86
|
+
offset *= matches[14] === '-' ? -1 : 1;
|
87
|
+
result -= offset * 60 * 1000;
|
88
|
+
}
|
89
|
+
return new Date(result);
|
90
|
+
}
|
91
|
+
}
|
92
|
+
// end iso8601.js
|
93
|
+
|
94
|
+
function negativeValues(series) {
|
95
|
+
var i, j, data;
|
96
|
+
for (i = 0; i < series.length; i++) {
|
97
|
+
data = series[i].data;
|
98
|
+
for (j = 0; j < data.length; j++) {
|
99
|
+
if (data[j][1] < 0) {
|
100
|
+
return true;
|
101
|
+
}
|
102
|
+
}
|
103
|
+
}
|
104
|
+
return false;
|
105
|
+
}
|
106
|
+
|
107
|
+
function jsOptionsFunc(defaultOptions, hideLegend, setMin, setMax, setStacked, setXtitle, setYtitle) {
|
108
|
+
return function (series, opts, chartOptions) {
|
109
|
+
var options = merge({}, defaultOptions);
|
110
|
+
options = merge(options, chartOptions || {});
|
111
|
+
|
112
|
+
// hide legend
|
113
|
+
// this is *not* an external option!
|
114
|
+
if (opts.hideLegend) {
|
115
|
+
hideLegend(options);
|
116
|
+
}
|
117
|
+
|
118
|
+
// min
|
119
|
+
if ("min" in opts) {
|
120
|
+
setMin(options, opts.min);
|
121
|
+
} else if (!negativeValues(series)) {
|
122
|
+
setMin(options, 0);
|
123
|
+
}
|
124
|
+
|
125
|
+
// max
|
126
|
+
if (opts.max) {
|
127
|
+
setMax(options, opts.max);
|
128
|
+
}
|
129
|
+
|
130
|
+
if ("stacked" in opts) {
|
131
|
+
setStacked(options, opts.stacked);
|
132
|
+
}
|
133
|
+
|
134
|
+
if (opts.colors) {
|
135
|
+
options.colors = opts.colors;
|
136
|
+
}
|
137
|
+
|
138
|
+
if (opts.xtitle) {
|
139
|
+
setXtitle(options, opts.xtitle);
|
140
|
+
}
|
141
|
+
|
142
|
+
if (opts.ytitle) {
|
143
|
+
setYtitle(options, opts.ytitle);
|
144
|
+
}
|
145
|
+
|
146
|
+
// merge library last
|
147
|
+
options = merge(options, opts.library || {});
|
148
|
+
|
149
|
+
return options;
|
150
|
+
};
|
151
|
+
}
|
152
|
+
|
153
|
+
function setText(element, text) {
|
154
|
+
if (document.body.innerText) {
|
155
|
+
element.innerText = text;
|
156
|
+
} else {
|
157
|
+
element.textContent = text;
|
158
|
+
}
|
159
|
+
}
|
160
|
+
|
161
|
+
function chartError(element, message) {
|
162
|
+
setText(element, "Error Loading Chart: " + message);
|
163
|
+
element.style.color = "#ff0000";
|
164
|
+
}
|
165
|
+
|
166
|
+
function getJSON(element, url, success) {
|
167
|
+
var $ = window.jQuery || window.Zepto || window.$;
|
168
|
+
$.ajax({
|
169
|
+
dataType: "json",
|
170
|
+
url: url,
|
171
|
+
success: success,
|
172
|
+
error: function (jqXHR, textStatus, errorThrown) {
|
173
|
+
var message = (typeof errorThrown === "string") ? errorThrown : errorThrown.message;
|
174
|
+
chartError(element, message);
|
175
|
+
}
|
176
|
+
});
|
177
|
+
}
|
178
|
+
|
179
|
+
function errorCatcher(chart, callback) {
|
180
|
+
try {
|
181
|
+
callback(chart);
|
182
|
+
} catch (err) {
|
183
|
+
chartError(chart.element, err.message);
|
184
|
+
throw err;
|
185
|
+
}
|
186
|
+
}
|
187
|
+
|
188
|
+
function fetchDataSource(chart, callback) {
|
189
|
+
if (typeof chart.dataSource === "string") {
|
190
|
+
getJSON(chart.element, chart.dataSource, function (data, textStatus, jqXHR) {
|
191
|
+
chart.data = data;
|
192
|
+
errorCatcher(chart, callback);
|
193
|
+
});
|
194
|
+
} else {
|
195
|
+
chart.data = chart.dataSource;
|
196
|
+
errorCatcher(chart, callback);
|
197
|
+
}
|
198
|
+
}
|
199
|
+
|
200
|
+
// type conversions
|
201
|
+
|
202
|
+
function toStr(n) {
|
203
|
+
return "" + n;
|
204
|
+
}
|
205
|
+
|
206
|
+
function toFloat(n) {
|
207
|
+
return parseFloat(n);
|
208
|
+
}
|
209
|
+
|
210
|
+
function toDate(n) {
|
211
|
+
var matches, year, month, day;
|
212
|
+
if (typeof n !== "object") {
|
213
|
+
if (typeof n === "number") {
|
214
|
+
n = new Date(n * 1000); // ms
|
215
|
+
} else if ((matches = n.match(DATE_PATTERN))) {
|
216
|
+
year = parseInt(matches[1], 10);
|
217
|
+
month = parseInt(matches[3], 10) - 1;
|
218
|
+
day = parseInt(matches[5], 10);
|
219
|
+
return new Date(year, month, day);
|
220
|
+
} else { // str
|
221
|
+
// try our best to get the str into iso8601
|
222
|
+
// TODO be smarter about this
|
223
|
+
var str = n.replace(/ /, "T").replace(" ", "").replace("UTC", "Z");
|
224
|
+
n = parseISO8601(str) || new Date(n);
|
225
|
+
}
|
226
|
+
}
|
227
|
+
return n;
|
228
|
+
}
|
229
|
+
|
230
|
+
function toArr(n) {
|
231
|
+
if (!isArray(n)) {
|
232
|
+
var arr = [], i;
|
233
|
+
for (i in n) {
|
234
|
+
if (n.hasOwnProperty(i)) {
|
235
|
+
arr.push([i, n[i]]);
|
236
|
+
}
|
237
|
+
}
|
238
|
+
n = arr;
|
239
|
+
}
|
240
|
+
return n;
|
241
|
+
}
|
242
|
+
|
243
|
+
function sortByTime(a, b) {
|
244
|
+
return a[0].getTime() - b[0].getTime();
|
245
|
+
}
|
246
|
+
|
247
|
+
function sortByNumber(a, b) {
|
248
|
+
return a - b;
|
249
|
+
}
|
250
|
+
|
251
|
+
function loadAdapters() {
|
252
|
+
if (!HighchartsAdapter && "Highcharts" in window) {
|
253
|
+
HighchartsAdapter = new function () {
|
254
|
+
var Highcharts = window.Highcharts;
|
255
|
+
|
256
|
+
this.name = "highcharts";
|
257
|
+
|
258
|
+
var defaultOptions = {
|
259
|
+
chart: {},
|
260
|
+
xAxis: {
|
261
|
+
title: {
|
262
|
+
text: null
|
263
|
+
},
|
264
|
+
labels: {
|
265
|
+
style: {
|
266
|
+
fontSize: "12px"
|
267
|
+
}
|
268
|
+
}
|
269
|
+
},
|
270
|
+
yAxis: {
|
271
|
+
title: {
|
272
|
+
text: null
|
273
|
+
},
|
274
|
+
labels: {
|
275
|
+
style: {
|
276
|
+
fontSize: "12px"
|
277
|
+
}
|
278
|
+
}
|
279
|
+
},
|
280
|
+
title: {
|
281
|
+
text: null
|
282
|
+
},
|
283
|
+
credits: {
|
284
|
+
enabled: false
|
285
|
+
},
|
286
|
+
legend: {
|
287
|
+
borderWidth: 0
|
288
|
+
},
|
289
|
+
tooltip: {
|
290
|
+
style: {
|
291
|
+
fontSize: "12px"
|
292
|
+
}
|
293
|
+
},
|
294
|
+
plotOptions: {
|
295
|
+
areaspline: {},
|
296
|
+
series: {
|
297
|
+
marker: {}
|
298
|
+
}
|
299
|
+
}
|
300
|
+
};
|
301
|
+
|
302
|
+
var hideLegend = function (options) {
|
303
|
+
options.legend.enabled = false;
|
304
|
+
};
|
305
|
+
|
306
|
+
var setMin = function (options, min) {
|
307
|
+
options.yAxis.min = min;
|
308
|
+
};
|
309
|
+
|
310
|
+
var setMax = function (options, max) {
|
311
|
+
options.yAxis.max = max;
|
312
|
+
};
|
313
|
+
|
314
|
+
var setStacked = function (options, stacked) {
|
315
|
+
options.plotOptions.series.stacking = stacked ? "normal" : null;
|
316
|
+
};
|
317
|
+
|
318
|
+
var setXtitle = function (options, title) {
|
319
|
+
options.xAxis.title.text = title;
|
320
|
+
};
|
321
|
+
|
322
|
+
var setYtitle = function (options, title) {
|
323
|
+
options.yAxis.title.text = title;
|
324
|
+
};
|
325
|
+
|
326
|
+
var jsOptions = jsOptionsFunc(defaultOptions, hideLegend, setMin, setMax, setStacked, setXtitle, setYtitle);
|
327
|
+
|
328
|
+
this.renderLineChart = function (chart, chartType) {
|
329
|
+
chartType = chartType || "spline";
|
330
|
+
var chartOptions = {};
|
331
|
+
if (chartType === "areaspline") {
|
332
|
+
chartOptions = {
|
333
|
+
plotOptions: {
|
334
|
+
areaspline: {
|
335
|
+
stacking: "normal"
|
336
|
+
},
|
337
|
+
series: {
|
338
|
+
marker: {
|
339
|
+
enabled: false
|
340
|
+
}
|
341
|
+
}
|
342
|
+
}
|
343
|
+
};
|
344
|
+
}
|
345
|
+
var options = jsOptions(chart.data, chart.options, chartOptions), data, i, j;
|
346
|
+
options.xAxis.type = chart.options.discrete ? "category" : "datetime";
|
347
|
+
options.chart.type = chartType;
|
348
|
+
options.chart.renderTo = chart.element.id;
|
349
|
+
|
350
|
+
var series = chart.data;
|
351
|
+
for (i = 0; i < series.length; i++) {
|
352
|
+
data = series[i].data;
|
353
|
+
if (!chart.options.discrete) {
|
354
|
+
for (j = 0; j < data.length; j++) {
|
355
|
+
data[j][0] = data[j][0].getTime();
|
356
|
+
}
|
357
|
+
}
|
358
|
+
series[i].marker = {symbol: "circle"};
|
359
|
+
}
|
360
|
+
options.series = series;
|
361
|
+
new Highcharts.Chart(options);
|
362
|
+
};
|
363
|
+
|
364
|
+
this.renderScatterChart = function (chart) {
|
365
|
+
var chartOptions = {};
|
366
|
+
var options = jsOptions(chart.data, chart.options, chartOptions);
|
367
|
+
options.chart.type = 'scatter';
|
368
|
+
options.chart.renderTo = chart.element.id;
|
369
|
+
options.series = chart.data;
|
370
|
+
new Highcharts.Chart(options);
|
371
|
+
};
|
372
|
+
|
373
|
+
this.renderPieChart = function (chart) {
|
374
|
+
var chartOptions = {};
|
375
|
+
if (chart.options.colors) {
|
376
|
+
chartOptions.colors = chart.options.colors;
|
377
|
+
}
|
378
|
+
var options = merge(merge(defaultOptions, chartOptions), chart.options.library || {});
|
379
|
+
options.chart.renderTo = chart.element.id;
|
380
|
+
options.series = [{
|
381
|
+
type: "pie",
|
382
|
+
name: chart.options.label || "Value",
|
383
|
+
data: chart.data
|
384
|
+
}];
|
385
|
+
new Highcharts.Chart(options);
|
386
|
+
};
|
387
|
+
|
388
|
+
this.renderColumnChart = function (chart, chartType) {
|
389
|
+
chartType = chartType || "column";
|
390
|
+
var series = chart.data;
|
391
|
+
var options = jsOptions(series, chart.options), i, j, s, d, rows = [];
|
392
|
+
options.chart.type = chartType;
|
393
|
+
options.chart.renderTo = chart.element.id;
|
394
|
+
|
395
|
+
for (i = 0; i < series.length; i++) {
|
396
|
+
s = series[i];
|
397
|
+
|
398
|
+
for (j = 0; j < s.data.length; j++) {
|
399
|
+
d = s.data[j];
|
400
|
+
if (!rows[d[0]]) {
|
401
|
+
rows[d[0]] = new Array(series.length);
|
402
|
+
}
|
403
|
+
rows[d[0]][i] = d[1];
|
404
|
+
}
|
405
|
+
}
|
406
|
+
|
407
|
+
var categories = [];
|
408
|
+
for (i in rows) {
|
409
|
+
if (rows.hasOwnProperty(i)) {
|
410
|
+
categories.push(i);
|
411
|
+
}
|
412
|
+
}
|
413
|
+
options.xAxis.categories = categories;
|
414
|
+
|
415
|
+
var newSeries = [];
|
416
|
+
for (i = 0; i < series.length; i++) {
|
417
|
+
d = [];
|
418
|
+
for (j = 0; j < categories.length; j++) {
|
419
|
+
d.push(rows[categories[j]][i] || 0);
|
420
|
+
}
|
421
|
+
|
422
|
+
newSeries.push({
|
423
|
+
name: series[i].name,
|
424
|
+
data: d
|
425
|
+
});
|
426
|
+
}
|
427
|
+
options.series = newSeries;
|
428
|
+
|
429
|
+
new Highcharts.Chart(options);
|
430
|
+
};
|
431
|
+
|
432
|
+
var self = this;
|
433
|
+
|
434
|
+
this.renderBarChart = function (chart) {
|
435
|
+
self.renderColumnChart(chart, "bar");
|
436
|
+
};
|
437
|
+
|
438
|
+
this.renderAreaChart = function (chart) {
|
439
|
+
self.renderLineChart(chart, "areaspline");
|
440
|
+
};
|
441
|
+
};
|
442
|
+
adapters.push(HighchartsAdapter);
|
443
|
+
}
|
444
|
+
if (!GoogleChartsAdapter && window.google && window.google.setOnLoadCallback) {
|
445
|
+
GoogleChartsAdapter = new function () {
|
446
|
+
var google = window.google;
|
447
|
+
|
448
|
+
this.name = "google";
|
449
|
+
|
450
|
+
var loaded = {};
|
451
|
+
var callbacks = [];
|
452
|
+
|
453
|
+
var runCallbacks = function () {
|
454
|
+
var cb, call;
|
455
|
+
for (var i = 0; i < callbacks.length; i++) {
|
456
|
+
cb = callbacks[i];
|
457
|
+
call = google.visualization && ((cb.pack === "corechart" && google.visualization.LineChart) || (cb.pack === "timeline" && google.visualization.Timeline));
|
458
|
+
if (call) {
|
459
|
+
cb.callback();
|
460
|
+
callbacks.splice(i, 1);
|
461
|
+
i--;
|
462
|
+
}
|
463
|
+
}
|
464
|
+
};
|
465
|
+
|
466
|
+
var waitForLoaded = function (pack, callback) {
|
467
|
+
if (!callback) {
|
468
|
+
callback = pack;
|
469
|
+
pack = "corechart";
|
470
|
+
}
|
471
|
+
|
472
|
+
callbacks.push({pack: pack, callback: callback});
|
473
|
+
|
474
|
+
if (loaded[pack]) {
|
475
|
+
runCallbacks();
|
476
|
+
} else {
|
477
|
+
loaded[pack] = true;
|
478
|
+
|
479
|
+
// https://groups.google.com/forum/#!topic/google-visualization-api/fMKJcyA2yyI
|
480
|
+
var loadOptions = {
|
481
|
+
packages: [pack],
|
482
|
+
callback: runCallbacks
|
483
|
+
};
|
484
|
+
if (config.language) {
|
485
|
+
loadOptions.language = config.language;
|
486
|
+
}
|
487
|
+
google.load("visualization", "1", loadOptions);
|
488
|
+
}
|
489
|
+
};
|
490
|
+
|
491
|
+
// Set chart options
|
492
|
+
var defaultOptions = {
|
493
|
+
chartArea: {},
|
494
|
+
fontName: "'Lucida Grande', 'Lucida Sans Unicode', Verdana, Arial, Helvetica, sans-serif",
|
495
|
+
pointSize: 6,
|
496
|
+
legend: {
|
497
|
+
textStyle: {
|
498
|
+
fontSize: 12,
|
499
|
+
color: "#444"
|
500
|
+
},
|
501
|
+
alignment: "center",
|
502
|
+
position: "right"
|
503
|
+
},
|
504
|
+
curveType: "function",
|
505
|
+
hAxis: {
|
506
|
+
textStyle: {
|
507
|
+
color: "#666",
|
508
|
+
fontSize: 12
|
509
|
+
},
|
510
|
+
titleTextStyle: {},
|
511
|
+
gridlines: {
|
512
|
+
color: "transparent"
|
513
|
+
},
|
514
|
+
baselineColor: "#ccc",
|
515
|
+
viewWindow: {}
|
516
|
+
},
|
517
|
+
vAxis: {
|
518
|
+
textStyle: {
|
519
|
+
color: "#666",
|
520
|
+
fontSize: 12
|
521
|
+
},
|
522
|
+
titleTextStyle: {},
|
523
|
+
baselineColor: "#ccc",
|
524
|
+
viewWindow: {}
|
525
|
+
},
|
526
|
+
tooltip: {
|
527
|
+
textStyle: {
|
528
|
+
color: "#666",
|
529
|
+
fontSize: 12
|
530
|
+
}
|
531
|
+
}
|
532
|
+
};
|
533
|
+
|
534
|
+
var hideLegend = function (options) {
|
535
|
+
options.legend.position = "none";
|
536
|
+
};
|
537
|
+
|
538
|
+
var setMin = function (options, min) {
|
539
|
+
options.vAxis.viewWindow.min = min;
|
540
|
+
};
|
541
|
+
|
542
|
+
var setMax = function (options, max) {
|
543
|
+
options.vAxis.viewWindow.max = max;
|
544
|
+
};
|
545
|
+
|
546
|
+
var setBarMin = function (options, min) {
|
547
|
+
options.hAxis.viewWindow.min = min;
|
548
|
+
};
|
549
|
+
|
550
|
+
var setBarMax = function (options, max) {
|
551
|
+
options.hAxis.viewWindow.max = max;
|
552
|
+
};
|
553
|
+
|
554
|
+
var setStacked = function (options, stacked) {
|
555
|
+
options.isStacked = !!stacked;
|
556
|
+
};
|
557
|
+
|
558
|
+
var setXtitle = function (options, title) {
|
559
|
+
options.hAxis.title = title;
|
560
|
+
options.hAxis.titleTextStyle.italic = false;
|
561
|
+
};
|
562
|
+
|
563
|
+
var setYtitle = function (options, title) {
|
564
|
+
options.vAxis.title = title;
|
565
|
+
options.vAxis.titleTextStyle.italic = false;
|
566
|
+
};
|
567
|
+
|
568
|
+
var jsOptions = jsOptionsFunc(defaultOptions, hideLegend, setMin, setMax, setStacked, setXtitle, setYtitle);
|
569
|
+
|
570
|
+
// cant use object as key
|
571
|
+
var createDataTable = function (series, columnType) {
|
572
|
+
var i, j, s, d, key, rows = [];
|
573
|
+
for (i = 0; i < series.length; i++) {
|
574
|
+
s = series[i];
|
575
|
+
|
576
|
+
for (j = 0; j < s.data.length; j++) {
|
577
|
+
d = s.data[j];
|
578
|
+
key = (columnType === "datetime") ? d[0].getTime() : d[0];
|
579
|
+
if (!rows[key]) {
|
580
|
+
rows[key] = new Array(series.length);
|
581
|
+
}
|
582
|
+
rows[key][i] = toFloat(d[1]);
|
583
|
+
}
|
584
|
+
}
|
585
|
+
|
586
|
+
var rows2 = [];
|
587
|
+
var day = true;
|
588
|
+
var value;
|
589
|
+
for (i in rows) {
|
590
|
+
if (rows.hasOwnProperty(i)) {
|
591
|
+
if (columnType === "datetime") {
|
592
|
+
value = new Date(toFloat(i));
|
593
|
+
day = day && isDay(value);
|
594
|
+
} else if (columnType === "number") {
|
595
|
+
value = toFloat(i);
|
596
|
+
} else {
|
597
|
+
value = i;
|
598
|
+
}
|
599
|
+
rows2.push([value].concat(rows[i]));
|
600
|
+
}
|
601
|
+
}
|
602
|
+
if (columnType === "datetime") {
|
603
|
+
rows2.sort(sortByTime);
|
604
|
+
}
|
605
|
+
|
606
|
+
// create datatable
|
607
|
+
var data = new google.visualization.DataTable();
|
608
|
+
columnType = columnType === "datetime" && day ? "date" : columnType;
|
609
|
+
data.addColumn(columnType, "");
|
610
|
+
for (i = 0; i < series.length; i++) {
|
611
|
+
data.addColumn("number", series[i].name);
|
612
|
+
}
|
613
|
+
data.addRows(rows2);
|
614
|
+
|
615
|
+
return data;
|
616
|
+
};
|
617
|
+
|
618
|
+
var resize = function (callback) {
|
619
|
+
if (window.attachEvent) {
|
620
|
+
window.attachEvent("onresize", callback);
|
621
|
+
} else if (window.addEventListener) {
|
622
|
+
window.addEventListener("resize", callback, true);
|
623
|
+
}
|
624
|
+
callback();
|
625
|
+
};
|
626
|
+
|
627
|
+
this.renderLineChart = function (chart) {
|
628
|
+
waitForLoaded(function () {
|
629
|
+
var options = jsOptions(chart.data, chart.options);
|
630
|
+
var data = createDataTable(chart.data, chart.options.discrete ? "string" : "datetime");
|
631
|
+
chart.chart = new google.visualization.LineChart(chart.element);
|
632
|
+
resize(function () {
|
633
|
+
chart.chart.draw(data, options);
|
634
|
+
});
|
635
|
+
});
|
636
|
+
};
|
637
|
+
|
638
|
+
this.renderPieChart = function (chart) {
|
639
|
+
waitForLoaded(function () {
|
640
|
+
var chartOptions = {
|
641
|
+
chartArea: {
|
642
|
+
top: "10%",
|
643
|
+
height: "80%"
|
644
|
+
}
|
645
|
+
};
|
646
|
+
if (chart.options.colors) {
|
647
|
+
chartOptions.colors = chart.options.colors;
|
648
|
+
}
|
649
|
+
var options = merge(merge(defaultOptions, chartOptions), chart.options.library || {});
|
650
|
+
|
651
|
+
var data = new google.visualization.DataTable();
|
652
|
+
data.addColumn("string", "");
|
653
|
+
data.addColumn("number", "Value");
|
654
|
+
data.addRows(chart.data);
|
655
|
+
|
656
|
+
chart.chart = new google.visualization.PieChart(chart.element);
|
657
|
+
resize(function () {
|
658
|
+
chart.chart.draw(data, options);
|
659
|
+
});
|
660
|
+
});
|
661
|
+
};
|
662
|
+
|
663
|
+
this.renderColumnChart = function (chart) {
|
664
|
+
waitForLoaded(function () {
|
665
|
+
var options = jsOptions(chart.data, chart.options);
|
666
|
+
var data = createDataTable(chart.data, "string");
|
667
|
+
chart.chart = new google.visualization.ColumnChart(chart.element);
|
668
|
+
resize(function () {
|
669
|
+
chart.chart.draw(data, options);
|
670
|
+
});
|
671
|
+
});
|
672
|
+
};
|
673
|
+
|
674
|
+
this.renderBarChart = function (chart) {
|
675
|
+
waitForLoaded(function () {
|
676
|
+
var chartOptions = {
|
677
|
+
hAxis: {
|
678
|
+
gridlines: {
|
679
|
+
color: "#ccc"
|
680
|
+
}
|
681
|
+
}
|
682
|
+
};
|
683
|
+
var options = jsOptionsFunc(defaultOptions, hideLegend, setBarMin, setBarMax, setStacked, setXtitle, setYtitle)(chart.data, chart.options, chartOptions);
|
684
|
+
var data = createDataTable(chart.data, "string");
|
685
|
+
chart.chart = new google.visualization.BarChart(chart.element);
|
686
|
+
resize(function () {
|
687
|
+
chart.chart.draw(data, options);
|
688
|
+
});
|
689
|
+
});
|
690
|
+
};
|
691
|
+
|
692
|
+
this.renderAreaChart = function (chart) {
|
693
|
+
waitForLoaded(function () {
|
694
|
+
var chartOptions = {
|
695
|
+
isStacked: true,
|
696
|
+
pointSize: 0,
|
697
|
+
areaOpacity: 0.5
|
698
|
+
};
|
699
|
+
var options = jsOptions(chart.data, chart.options, chartOptions);
|
700
|
+
var data = createDataTable(chart.data, chart.options.discrete ? "string" : "datetime");
|
701
|
+
chart.chart = new google.visualization.AreaChart(chart.element);
|
702
|
+
resize(function () {
|
703
|
+
chart.chart.draw(data, options);
|
704
|
+
});
|
705
|
+
});
|
706
|
+
};
|
707
|
+
|
708
|
+
this.renderGeoChart = function (chart) {
|
709
|
+
waitForLoaded(function () {
|
710
|
+
var chartOptions = {
|
711
|
+
legend: "none",
|
712
|
+
colorAxis: {
|
713
|
+
colors: chart.options.colors || ["#f6c7b6", "#ce502d"]
|
714
|
+
}
|
715
|
+
};
|
716
|
+
var options = merge(merge(defaultOptions, chartOptions), chart.options.library || {});
|
717
|
+
|
718
|
+
var data = new google.visualization.DataTable();
|
719
|
+
data.addColumn("string", "");
|
720
|
+
data.addColumn("number", chart.options.label || "Value");
|
721
|
+
data.addRows(chart.data);
|
722
|
+
|
723
|
+
chart.chart = new google.visualization.GeoChart(chart.element);
|
724
|
+
resize(function () {
|
725
|
+
chart.chart.draw(data, options);
|
726
|
+
});
|
727
|
+
});
|
728
|
+
};
|
729
|
+
|
730
|
+
this.renderScatterChart = function (chart) {
|
731
|
+
waitForLoaded(function () {
|
732
|
+
var chartOptions = {};
|
733
|
+
var options = jsOptions(chart.data, chart.options, chartOptions);
|
734
|
+
var data = createDataTable(chart.data, "number");
|
735
|
+
|
736
|
+
chart.chart = new google.visualization.ScatterChart(chart.element);
|
737
|
+
resize(function () {
|
738
|
+
chart.chart.draw(data, options);
|
739
|
+
});
|
740
|
+
});
|
741
|
+
};
|
742
|
+
|
743
|
+
this.renderTimeline = function (chart) {
|
744
|
+
waitForLoaded("timeline", function () {
|
745
|
+
var chartOptions = {
|
746
|
+
legend: "none"
|
747
|
+
};
|
748
|
+
|
749
|
+
if (chart.options.colors) {
|
750
|
+
chartOptions.colors = chart.options.colors;
|
751
|
+
}
|
752
|
+
var options = merge(merge(defaultOptions, chartOptions), chart.options.library || {});
|
753
|
+
|
754
|
+
var data = new google.visualization.DataTable();
|
755
|
+
data.addColumn({type: "string", id: "Name"});
|
756
|
+
data.addColumn({type: "date", id: "Start"});
|
757
|
+
data.addColumn({type: "date", id: "End"});
|
758
|
+
data.addRows(chart.data);
|
759
|
+
|
760
|
+
chart.element.style.lineHeight = "normal";
|
761
|
+
chart.chart = new google.visualization.Timeline(chart.element);
|
762
|
+
|
763
|
+
resize(function () {
|
764
|
+
chart.chart.draw(data, options);
|
765
|
+
});
|
766
|
+
});
|
767
|
+
};
|
768
|
+
};
|
769
|
+
|
770
|
+
adapters.push(GoogleChartsAdapter);
|
771
|
+
}
|
772
|
+
if (!ChartjsAdapter && "Chart" in window) {
|
773
|
+
ChartjsAdapter = new function () {
|
774
|
+
var Chart = window.Chart;
|
775
|
+
|
776
|
+
this.name = "chartjs";
|
777
|
+
|
778
|
+
var baseOptions = {
|
779
|
+
maintainAspectRatio: false,
|
780
|
+
animation: false
|
781
|
+
};
|
782
|
+
|
783
|
+
var defaultOptions = {
|
784
|
+
scales: {
|
785
|
+
yAxes: [
|
786
|
+
{
|
787
|
+
ticks: {
|
788
|
+
maxTicksLimit: 4
|
789
|
+
},
|
790
|
+
scaleLabel: {
|
791
|
+
fontSize: 16,
|
792
|
+
// fontStyle: "bold",
|
793
|
+
fontColor: "#333"
|
794
|
+
}
|
795
|
+
}
|
796
|
+
],
|
797
|
+
xAxes: [
|
798
|
+
{
|
799
|
+
gridLines: {
|
800
|
+
drawOnChartArea: false
|
801
|
+
},
|
802
|
+
scaleLabel: {
|
803
|
+
fontSize: 16,
|
804
|
+
// fontStyle: "bold",
|
805
|
+
fontColor: "#333"
|
806
|
+
},
|
807
|
+
time: {},
|
808
|
+
ticks: {}
|
809
|
+
}
|
810
|
+
]
|
811
|
+
},
|
812
|
+
legend: {}
|
813
|
+
};
|
814
|
+
|
815
|
+
// http://there4.io/2012/05/02/google-chart-color-list/
|
816
|
+
var defaultColors = [
|
817
|
+
"#3366CC", "#DC3912", "#FF9900", "#109618", "#990099", "#3B3EAC", "#0099C6",
|
818
|
+
"#DD4477", "#66AA00", "#B82E2E", "#316395", "#994499", "#22AA99", "#AAAA11",
|
819
|
+
"#6633CC", "#E67300", "#8B0707", "#329262", "#5574A6", "#3B3EAC"
|
820
|
+
];
|
821
|
+
|
822
|
+
var hideLegend = function (options) {
|
823
|
+
options.legend.display = false;
|
824
|
+
};
|
825
|
+
|
826
|
+
var setMin = function (options, min) {
|
827
|
+
if (min !== null) {
|
828
|
+
options.scales.yAxes[0].ticks.min = min;
|
829
|
+
}
|
830
|
+
};
|
831
|
+
|
832
|
+
var setMax = function (options, max) {
|
833
|
+
options.scales.yAxes[0].ticks.max = max;
|
834
|
+
};
|
835
|
+
|
836
|
+
var setBarMin = function (options, min) {
|
837
|
+
if (min !== null) {
|
838
|
+
options.scales.xAxes[0].ticks.min = min;
|
839
|
+
}
|
840
|
+
};
|
841
|
+
|
842
|
+
var setBarMax = function (options, max) {
|
843
|
+
options.scales.xAxes[0].ticks.max = max;
|
844
|
+
};
|
845
|
+
|
846
|
+
var setStacked = function (options, stacked) {
|
847
|
+
options.scales.xAxes[0].stacked = !!stacked;
|
848
|
+
options.scales.yAxes[0].stacked = !!stacked;
|
849
|
+
};
|
850
|
+
|
851
|
+
var setXtitle = function (options, title) {
|
852
|
+
options.scales.xAxes[0].scaleLabel.display = true;
|
853
|
+
options.scales.xAxes[0].scaleLabel.labelString = title;
|
854
|
+
};
|
855
|
+
|
856
|
+
var setYtitle = function (options, title) {
|
857
|
+
options.scales.yAxes[0].scaleLabel.display = true;
|
858
|
+
options.scales.yAxes[0].scaleLabel.labelString = title;
|
859
|
+
};
|
860
|
+
|
861
|
+
var drawChart = function(chart, type, data, options) {
|
862
|
+
chart.element.innerHTML = "<canvas></canvas>";
|
863
|
+
var ctx = chart.element.getElementsByTagName("CANVAS")[0];
|
864
|
+
|
865
|
+
chart.chart = new Chart(ctx, {
|
866
|
+
type: type,
|
867
|
+
data: data,
|
868
|
+
options: options
|
869
|
+
});
|
870
|
+
};
|
871
|
+
|
872
|
+
// http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
|
873
|
+
var addOpacity = function(hex, opacity) {
|
874
|
+
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
875
|
+
return result ? "rgba(" + parseInt(result[1], 16) + ", " + parseInt(result[2], 16) + ", " + parseInt(result[3], 16) + ", " + opacity + ")" : hex;
|
876
|
+
};
|
877
|
+
|
878
|
+
var setLabelSize = function (chart, data, options) {
|
879
|
+
var maxLabelSize = Math.ceil(chart.element.offsetWidth / 4.0 / data.labels.length);
|
880
|
+
if (maxLabelSize > 25) {
|
881
|
+
maxLabelSize = 25;
|
882
|
+
}
|
883
|
+
options.scales.xAxes[0].ticks.callback = function (value) {
|
884
|
+
value = toStr(value);
|
885
|
+
if (value.length > maxLabelSize) {
|
886
|
+
return value.substring(0, maxLabelSize - 2) + "...";
|
887
|
+
} else {
|
888
|
+
return value;
|
889
|
+
}
|
890
|
+
};
|
891
|
+
};
|
892
|
+
|
893
|
+
var jsOptions = jsOptionsFunc(merge(baseOptions, defaultOptions), hideLegend, setMin, setMax, setStacked, setXtitle, setYtitle);
|
894
|
+
|
895
|
+
var createDataTable = function (chart, options, chartType) {
|
896
|
+
var datasets = [];
|
897
|
+
var labels = [];
|
898
|
+
|
899
|
+
var colors = chart.options.colors || defaultColors;
|
900
|
+
|
901
|
+
var day = true;
|
902
|
+
var week = true;
|
903
|
+
var dayOfWeek;
|
904
|
+
var month = true;
|
905
|
+
var year = true;
|
906
|
+
var hour = true;
|
907
|
+
var minute = true;
|
908
|
+
var detectType = (chartType === "line" || chartType === "area") && !chart.options.discrete;
|
909
|
+
|
910
|
+
var series = chart.data;
|
911
|
+
|
912
|
+
var sortedLabels = [];
|
913
|
+
|
914
|
+
var i, j, s, d, key, rows = [];
|
915
|
+
for (i = 0; i < series.length; i++) {
|
916
|
+
s = series[i];
|
917
|
+
|
918
|
+
for (j = 0; j < s.data.length; j++) {
|
919
|
+
d = s.data[j];
|
920
|
+
key = detectType ? d[0].getTime() : d[0];
|
921
|
+
if (!rows[key]) {
|
922
|
+
rows[key] = new Array(series.length);
|
923
|
+
}
|
924
|
+
rows[key][i] = toFloat(d[1]);
|
925
|
+
if (sortedLabels.indexOf(key) === -1) {
|
926
|
+
sortedLabels.push(key);
|
927
|
+
}
|
928
|
+
}
|
929
|
+
}
|
930
|
+
|
931
|
+
if (detectType) {
|
932
|
+
sortedLabels.sort(sortByNumber);
|
933
|
+
}
|
934
|
+
|
935
|
+
var rows2 = [];
|
936
|
+
for (j = 0; j < series.length; j++) {
|
937
|
+
rows2.push([]);
|
938
|
+
}
|
939
|
+
|
940
|
+
var value;
|
941
|
+
var k;
|
942
|
+
for (k = 0; k < sortedLabels.length; k++) {
|
943
|
+
i = sortedLabels[k];
|
944
|
+
if (detectType) {
|
945
|
+
value = new Date(toFloat(i));
|
946
|
+
// TODO make this efficient
|
947
|
+
day = day && isDay(value);
|
948
|
+
if (!dayOfWeek) {
|
949
|
+
dayOfWeek = value.getDay();
|
950
|
+
}
|
951
|
+
week = week && isWeek(value, dayOfWeek);
|
952
|
+
month = month && isMonth(value);
|
953
|
+
year = year && isYear(value);
|
954
|
+
hour = hour && isHour(value);
|
955
|
+
minute = minute && isMinute(value);
|
956
|
+
} else {
|
957
|
+
value = i;
|
958
|
+
}
|
959
|
+
labels.push(value);
|
960
|
+
for (j = 0; j < series.length; j++) {
|
961
|
+
rows2[j].push(rows[i][j]);
|
962
|
+
}
|
963
|
+
}
|
964
|
+
|
965
|
+
for (i = 0; i < series.length; i++) {
|
966
|
+
s = series[i];
|
967
|
+
|
968
|
+
var backgroundColor = chartType !== "line" ? addOpacity(colors[i], 0.5) : colors[i];
|
969
|
+
|
970
|
+
var dataset = {
|
971
|
+
label: s.name,
|
972
|
+
data: rows2[i],
|
973
|
+
fill: chartType === "area",
|
974
|
+
borderColor: colors[i],
|
975
|
+
backgroundColor: backgroundColor,
|
976
|
+
pointBackgroundColor: colors[i],
|
977
|
+
borderWidth: 2
|
978
|
+
};
|
979
|
+
|
980
|
+
datasets.push(merge(dataset, s.library || {}));
|
981
|
+
}
|
982
|
+
|
983
|
+
if (detectType && labels.length > 0) {
|
984
|
+
var minTime = labels[0].getTime();
|
985
|
+
var maxTime = labels[0].getTime();
|
986
|
+
for (i = 1; i < labels.length; i++) {
|
987
|
+
value = labels[i].getTime();
|
988
|
+
if (value < minTime) {
|
989
|
+
minTime = value;
|
990
|
+
}
|
991
|
+
if (value > maxTime) {
|
992
|
+
maxTime = value;
|
993
|
+
}
|
994
|
+
}
|
995
|
+
|
996
|
+
var timeDiff = (maxTime - minTime) / (86400 * 1000.0);
|
997
|
+
|
998
|
+
if (!options.scales.xAxes[0].time.unit) {
|
999
|
+
var step;
|
1000
|
+
if (year || timeDiff > 365 * 10) {
|
1001
|
+
options.scales.xAxes[0].time.unit = "year";
|
1002
|
+
step = 365;
|
1003
|
+
} else if (month || timeDiff > 30 * 10) {
|
1004
|
+
options.scales.xAxes[0].time.unit = "month";
|
1005
|
+
step = 30;
|
1006
|
+
} else if (day || timeDiff > 10) {
|
1007
|
+
options.scales.xAxes[0].time.unit = "day";
|
1008
|
+
step = 1;
|
1009
|
+
} else if (hour) {
|
1010
|
+
options.scales.xAxes[0].time.unit = "hour";
|
1011
|
+
step = 1 / 24.0;
|
1012
|
+
} else if (minute) {
|
1013
|
+
options.scales.xAxes[0].time.displayFormats = {minute: "h:mm a"};
|
1014
|
+
options.scales.xAxes[0].time.unit = "minute";
|
1015
|
+
step = 1 / 24.0 / 60.0;
|
1016
|
+
}
|
1017
|
+
|
1018
|
+
|
1019
|
+
if (step && timeDiff > 0) {
|
1020
|
+
var unitStepSize = Math.ceil(timeDiff / step / (chart.element.offsetWidth / 100.0));
|
1021
|
+
if (week && step === 1) {
|
1022
|
+
unitStepSize = Math.ceil(unitStepSize / 7.0) * 7;
|
1023
|
+
}
|
1024
|
+
options.scales.xAxes[0].time.unitStepSize = unitStepSize;
|
1025
|
+
}
|
1026
|
+
}
|
1027
|
+
|
1028
|
+
if (!options.scales.xAxes[0].time.tooltipFormat) {
|
1029
|
+
if (day) {
|
1030
|
+
options.scales.xAxes[0].time.tooltipFormat = "ll";
|
1031
|
+
} else if (hour) {
|
1032
|
+
options.scales.xAxes[0].time.tooltipFormat = "MMM D, h a";
|
1033
|
+
} else if (minute) {
|
1034
|
+
options.scales.xAxes[0].time.tooltipFormat = "h:mm a";
|
1035
|
+
}
|
1036
|
+
}
|
1037
|
+
}
|
1038
|
+
|
1039
|
+
var data = {
|
1040
|
+
labels: labels,
|
1041
|
+
datasets: datasets
|
1042
|
+
};
|
1043
|
+
|
1044
|
+
return data;
|
1045
|
+
};
|
1046
|
+
|
1047
|
+
this.renderLineChart = function (chart, chartType) {
|
1048
|
+
var areaOptions = {};
|
1049
|
+
if (chartType === "area") {
|
1050
|
+
// TODO fix area stacked
|
1051
|
+
// areaOptions.stacked = true;
|
1052
|
+
}
|
1053
|
+
// fix for https://github.com/chartjs/Chart.js/issues/2441
|
1054
|
+
if (!chart.options.max && allZeros(chart.data)) {
|
1055
|
+
chart.options.max = 1;
|
1056
|
+
}
|
1057
|
+
|
1058
|
+
var options = jsOptions(chart.data, merge(areaOptions, chart.options));
|
1059
|
+
|
1060
|
+
var data = createDataTable(chart, options, chartType || "line");
|
1061
|
+
|
1062
|
+
options.scales.xAxes[0].type = chart.options.discrete ? "category" : "time";
|
1063
|
+
|
1064
|
+
drawChart(chart, "line", data, options);
|
1065
|
+
};
|
1066
|
+
|
1067
|
+
this.renderPieChart = function (chart) {
|
1068
|
+
var options = merge(baseOptions, chart.options.library || {});
|
1069
|
+
|
1070
|
+
var labels = [];
|
1071
|
+
var values = [];
|
1072
|
+
for (var i = 0; i < chart.data.length; i++) {
|
1073
|
+
var point = chart.data[i];
|
1074
|
+
labels.push(point[0]);
|
1075
|
+
values.push(point[1]);
|
1076
|
+
}
|
1077
|
+
|
1078
|
+
var data = {
|
1079
|
+
labels: labels,
|
1080
|
+
datasets: [
|
1081
|
+
{
|
1082
|
+
data: values,
|
1083
|
+
backgroundColor: chart.options.colors || defaultColors
|
1084
|
+
}
|
1085
|
+
]
|
1086
|
+
};
|
1087
|
+
|
1088
|
+
drawChart(chart, "pie", data, options);
|
1089
|
+
};
|
1090
|
+
|
1091
|
+
this.renderColumnChart = function (chart, chartType) {
|
1092
|
+
var options;
|
1093
|
+
if (chartType === "bar") {
|
1094
|
+
options = jsOptionsFunc(merge(baseOptions, defaultOptions), hideLegend, setBarMin, setBarMax, setStacked, setXtitle, setYtitle)(chart.data, chart.options);
|
1095
|
+
} else {
|
1096
|
+
options = jsOptions(chart.data, chart.options);
|
1097
|
+
}
|
1098
|
+
var data = createDataTable(chart, options, "column");
|
1099
|
+
setLabelSize(chart, data, options);
|
1100
|
+
drawChart(chart, (chartType === "bar" ? "horizontalBar" : "bar"), data, options);
|
1101
|
+
};
|
1102
|
+
|
1103
|
+
var self = this;
|
1104
|
+
|
1105
|
+
this.renderAreaChart = function (chart) {
|
1106
|
+
self.renderLineChart(chart, "area");
|
1107
|
+
};
|
1108
|
+
|
1109
|
+
this.renderBarChart = function (chart) {
|
1110
|
+
self.renderColumnChart(chart, "bar");
|
1111
|
+
};
|
1112
|
+
|
1113
|
+
this.renderScatterChart = function (chart) {
|
1114
|
+
var options = jsOptions(chart.data, chart.options);
|
1115
|
+
|
1116
|
+
var colors = chart.options.colors || defaultColors;
|
1117
|
+
|
1118
|
+
var datasets = [];
|
1119
|
+
var series = chart.data;
|
1120
|
+
for (var i = 0; i < series.length; i++) {
|
1121
|
+
var s = series[i];
|
1122
|
+
var d = [];
|
1123
|
+
for (var j = 0; j < s.data.length; j++) {
|
1124
|
+
d.push({
|
1125
|
+
x: toFloat(s.data[j][0]),
|
1126
|
+
y: toFloat(s.data[j][1])
|
1127
|
+
});
|
1128
|
+
}
|
1129
|
+
|
1130
|
+
datasets.push({
|
1131
|
+
label: s.name,
|
1132
|
+
showLine: false,
|
1133
|
+
data: d,
|
1134
|
+
borderColor: colors[i],
|
1135
|
+
backgroundColor: colors[i],
|
1136
|
+
pointBackgroundColor: colors[i]
|
1137
|
+
})
|
1138
|
+
}
|
1139
|
+
|
1140
|
+
var data = {datasets: datasets};
|
1141
|
+
|
1142
|
+
options.scales.xAxes[0].type = "linear";
|
1143
|
+
options.scales.xAxes[0].position = "bottom";
|
1144
|
+
|
1145
|
+
drawChart(chart, "line", data, options);
|
1146
|
+
};
|
1147
|
+
};
|
1148
|
+
|
1149
|
+
adapters.unshift(ChartjsAdapter);
|
1150
|
+
}
|
1151
|
+
}
|
1152
|
+
|
1153
|
+
// TODO remove chartType if cross-browser way
|
1154
|
+
// to get the name of the chart class
|
1155
|
+
function renderChart(chartType, chart) {
|
1156
|
+
var i, adapter, fnName, adapterName;
|
1157
|
+
fnName = "render" + chartType;
|
1158
|
+
adapterName = chart.options.adapter;
|
1159
|
+
|
1160
|
+
loadAdapters();
|
1161
|
+
|
1162
|
+
for (i = 0; i < adapters.length; i++) {
|
1163
|
+
adapter = adapters[i];
|
1164
|
+
if ((!adapterName || adapterName === adapter.name) && isFunction(adapter[fnName])) {
|
1165
|
+
return adapter[fnName](chart);
|
1166
|
+
}
|
1167
|
+
}
|
1168
|
+
throw new Error("No adapter found");
|
1169
|
+
}
|
1170
|
+
|
1171
|
+
// process data
|
1172
|
+
|
1173
|
+
var toFormattedKey = function (key, keyType) {
|
1174
|
+
if (keyType === "number") {
|
1175
|
+
key = toFloat(key);
|
1176
|
+
} else if (keyType === "datetime") {
|
1177
|
+
key = toDate(key);
|
1178
|
+
} else {
|
1179
|
+
key = toStr(key);
|
1180
|
+
}
|
1181
|
+
return key;
|
1182
|
+
};
|
1183
|
+
|
1184
|
+
var formatSeriesData = function (data, keyType) {
|
1185
|
+
var r = [], key, j;
|
1186
|
+
for (j = 0; j < data.length; j++) {
|
1187
|
+
key = toFormattedKey(data[j][0], keyType);
|
1188
|
+
r.push([key, toFloat(data[j][1])]);
|
1189
|
+
}
|
1190
|
+
if (keyType === "datetime") {
|
1191
|
+
r.sort(sortByTime);
|
1192
|
+
}
|
1193
|
+
return r;
|
1194
|
+
};
|
1195
|
+
|
1196
|
+
function isMinute(d) {
|
1197
|
+
return d.getMilliseconds() === 0 && d.getSeconds() === 0;
|
1198
|
+
}
|
1199
|
+
|
1200
|
+
function isHour(d) {
|
1201
|
+
return isMinute(d) && d.getMinutes() === 0;
|
1202
|
+
}
|
1203
|
+
|
1204
|
+
function isDay(d) {
|
1205
|
+
return isHour(d) && d.getHours() === 0;
|
1206
|
+
}
|
1207
|
+
|
1208
|
+
function isWeek(d, dayOfWeek) {
|
1209
|
+
return isDay(d) && d.getDay() === dayOfWeek;
|
1210
|
+
}
|
1211
|
+
|
1212
|
+
function isMonth(d) {
|
1213
|
+
return isDay(d) && d.getDate() === 1;
|
1214
|
+
}
|
1215
|
+
|
1216
|
+
function isYear(d) {
|
1217
|
+
return isMonth(d) && d.getMonth() === 0;
|
1218
|
+
}
|
1219
|
+
|
1220
|
+
function isDate(obj) {
|
1221
|
+
return !isNaN(toDate(obj)) && toStr(obj).length >= 6;
|
1222
|
+
}
|
1223
|
+
|
1224
|
+
function allZeros(data) {
|
1225
|
+
var i, j, d;
|
1226
|
+
for (i = 0; i < data.length; i++) {
|
1227
|
+
d = data[i].data;
|
1228
|
+
for (j = 0; j < d.length; j++) {
|
1229
|
+
if (d[j][1] != 0) {
|
1230
|
+
return false;
|
1231
|
+
}
|
1232
|
+
}
|
1233
|
+
}
|
1234
|
+
return true;
|
1235
|
+
}
|
1236
|
+
|
1237
|
+
function detectDiscrete(series) {
|
1238
|
+
var i, j, data;
|
1239
|
+
for (i = 0; i < series.length; i++) {
|
1240
|
+
data = toArr(series[i].data);
|
1241
|
+
for (j = 0; j < data.length; j++) {
|
1242
|
+
if (!isDate(data[j][0])) {
|
1243
|
+
return true;
|
1244
|
+
}
|
1245
|
+
}
|
1246
|
+
}
|
1247
|
+
return false;
|
1248
|
+
}
|
1249
|
+
|
1250
|
+
function processSeries(series, opts, keyType) {
|
1251
|
+
var i;
|
1252
|
+
|
1253
|
+
// see if one series or multiple
|
1254
|
+
if (!isArray(series) || typeof series[0] !== "object" || isArray(series[0])) {
|
1255
|
+
series = [{name: opts.label || "Value", data: series}];
|
1256
|
+
opts.hideLegend = true;
|
1257
|
+
} else {
|
1258
|
+
opts.hideLegend = false;
|
1259
|
+
}
|
1260
|
+
if ((opts.discrete === null || opts.discrete === undefined)) {
|
1261
|
+
opts.discrete = detectDiscrete(series);
|
1262
|
+
}
|
1263
|
+
if (opts.discrete) {
|
1264
|
+
keyType = "string";
|
1265
|
+
}
|
1266
|
+
|
1267
|
+
// right format
|
1268
|
+
for (i = 0; i < series.length; i++) {
|
1269
|
+
series[i].data = formatSeriesData(toArr(series[i].data), keyType);
|
1270
|
+
}
|
1271
|
+
|
1272
|
+
return series;
|
1273
|
+
}
|
1274
|
+
|
1275
|
+
function processSimple(data) {
|
1276
|
+
var perfectData = toArr(data), i;
|
1277
|
+
for (i = 0; i < perfectData.length; i++) {
|
1278
|
+
perfectData[i] = [toStr(perfectData[i][0]), toFloat(perfectData[i][1])];
|
1279
|
+
}
|
1280
|
+
return perfectData;
|
1281
|
+
}
|
1282
|
+
|
1283
|
+
function processTime(data)
|
1284
|
+
{
|
1285
|
+
var i;
|
1286
|
+
for (i = 0; i < data.length; i++) {
|
1287
|
+
data[i][1] = toDate(data[i][1]);
|
1288
|
+
data[i][2] = toDate(data[i][2]);
|
1289
|
+
}
|
1290
|
+
return data;
|
1291
|
+
}
|
1292
|
+
|
1293
|
+
function processLineData(chart) {
|
1294
|
+
chart.data = processSeries(chart.data, chart.options, "datetime");
|
1295
|
+
renderChart("LineChart", chart);
|
1296
|
+
}
|
1297
|
+
|
1298
|
+
function processColumnData(chart) {
|
1299
|
+
chart.data = processSeries(chart.data, chart.options, "string");
|
1300
|
+
renderChart("ColumnChart", chart);
|
1301
|
+
}
|
1302
|
+
|
1303
|
+
function processPieData(chart) {
|
1304
|
+
chart.data = processSimple(chart.data);
|
1305
|
+
renderChart("PieChart", chart);
|
1306
|
+
}
|
1307
|
+
|
1308
|
+
function processBarData(chart) {
|
1309
|
+
chart.data = processSeries(chart.data, chart.options, "string");
|
1310
|
+
renderChart("BarChart", chart);
|
1311
|
+
}
|
1312
|
+
|
1313
|
+
function processAreaData(chart) {
|
1314
|
+
chart.data = processSeries(chart.data, chart.options, "datetime");
|
1315
|
+
renderChart("AreaChart", chart);
|
1316
|
+
}
|
1317
|
+
|
1318
|
+
function processGeoData(chart) {
|
1319
|
+
chart.data = processSimple(chart.data);
|
1320
|
+
renderChart("GeoChart", chart);
|
1321
|
+
}
|
1322
|
+
|
1323
|
+
function processScatterData(chart) {
|
1324
|
+
chart.data = processSeries(chart.data, chart.options, "number");
|
1325
|
+
renderChart("ScatterChart", chart);
|
1326
|
+
}
|
1327
|
+
|
1328
|
+
function processTimelineData(chart) {
|
1329
|
+
chart.data = processTime(chart.data);
|
1330
|
+
renderChart("Timeline", chart);
|
1331
|
+
}
|
1332
|
+
|
1333
|
+
function setElement(chart, element, dataSource, opts, callback) {
|
1334
|
+
var elementId;
|
1335
|
+
if (typeof element === "string") {
|
1336
|
+
elementId = element;
|
1337
|
+
element = document.getElementById(element);
|
1338
|
+
if (!element) {
|
1339
|
+
throw new Error("No element with id " + elementId);
|
1340
|
+
}
|
1341
|
+
}
|
1342
|
+
chart.element = element;
|
1343
|
+
chart.options = opts || {};
|
1344
|
+
chart.dataSource = dataSource;
|
1345
|
+
chart.getElement = function () {
|
1346
|
+
return element;
|
1347
|
+
};
|
1348
|
+
chart.getData = function () {
|
1349
|
+
return chart.data;
|
1350
|
+
};
|
1351
|
+
chart.getOptions = function () {
|
1352
|
+
return opts || {};
|
1353
|
+
};
|
1354
|
+
chart.getChartObject = function () {
|
1355
|
+
return chart.chart;
|
1356
|
+
};
|
1357
|
+
Chartkick.charts[element.id] = chart;
|
1358
|
+
fetchDataSource(chart, callback);
|
1359
|
+
}
|
1360
|
+
|
1361
|
+
// define classes
|
1362
|
+
|
1363
|
+
Chartkick = {
|
1364
|
+
LineChart: function (element, dataSource, opts) {
|
1365
|
+
setElement(this, element, dataSource, opts, processLineData);
|
1366
|
+
},
|
1367
|
+
PieChart: function (element, dataSource, opts) {
|
1368
|
+
setElement(this, element, dataSource, opts, processPieData);
|
1369
|
+
},
|
1370
|
+
ColumnChart: function (element, dataSource, opts) {
|
1371
|
+
setElement(this, element, dataSource, opts, processColumnData);
|
1372
|
+
},
|
1373
|
+
BarChart: function (element, dataSource, opts) {
|
1374
|
+
setElement(this, element, dataSource, opts, processBarData);
|
1375
|
+
},
|
1376
|
+
AreaChart: function (element, dataSource, opts) {
|
1377
|
+
setElement(this, element, dataSource, opts, processAreaData);
|
1378
|
+
},
|
1379
|
+
GeoChart: function (element, dataSource, opts) {
|
1380
|
+
setElement(this, element, dataSource, opts, processGeoData);
|
1381
|
+
},
|
1382
|
+
ScatterChart: function (element, dataSource, opts) {
|
1383
|
+
setElement(this, element, dataSource, opts, processScatterData);
|
1384
|
+
},
|
1385
|
+
Timeline: function (element, dataSource, opts) {
|
1386
|
+
setElement(this, element, dataSource, opts, processTimelineData);
|
1387
|
+
},
|
1388
|
+
charts: {}
|
1389
|
+
};
|
1390
|
+
|
1391
|
+
if (typeof module === "object" && typeof module.exports === "object") {
|
1392
|
+
module.exports = Chartkick;
|
1393
|
+
} else {
|
1394
|
+
window.Chartkick = Chartkick;
|
1395
|
+
}
|
1396
|
+
}(window));
|