chartkick 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of chartkick might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +83 -7
- data/app/assets/javascripts/chartkick.js +442 -85
- data/lib/chartkick/helper.rb +18 -31
- data/lib/chartkick/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7cff1f5410c612571c17f1d8e2f4dd0fef8a2a3f
|
4
|
+
data.tar.gz: a4b70704b7e4b5c233382dda51bb9a45a2b65f5c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2b0e9e917045bcafc1069ef5fc3743de290c4ae42b95adb3b96b403450f334336769144a638390e4b28f3587a734edc292792ad756b879312596ffb3a308177
|
7
|
+
data.tar.gz: 9a40777c914f017f637da7af79c4da86d87ba94de53dacf271a852d7097ac7c5ee92bf06cbf919db01033499f9f89b9b624ed9ad196d1a84e9c9c225494dd096
|
data/README.md
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
# Chartkick
|
2
2
|
|
3
|
-
Create beautiful Javascript charts with one line of Ruby
|
3
|
+
Create beautiful Javascript charts with one line of Ruby. No more fighting with charting libraries!
|
4
4
|
|
5
|
-
[
|
5
|
+
[See it in action](http://ankane.github.io/chartkick/)
|
6
6
|
|
7
|
-
|
7
|
+
Works with Rails 3.1+ and most browsers (including IE 6)
|
8
8
|
|
9
|
-
|
9
|
+
:two_hearts: A perfect companion to [groupdate](http://ankane.github.io/groupdate/)
|
10
10
|
|
11
11
|
## Usage
|
12
12
|
|
@@ -19,7 +19,7 @@ Line chart
|
|
19
19
|
Pie chart
|
20
20
|
|
21
21
|
```erb
|
22
|
-
<%= pie_chart Goal.group("
|
22
|
+
<%= pie_chart Goal.group("name").count %>
|
23
23
|
```
|
24
24
|
|
25
25
|
Column chart
|
@@ -28,18 +28,71 @@ Column chart
|
|
28
28
|
<%= column_chart Task.group_by_hour_of_day(:created_at).count %>
|
29
29
|
```
|
30
30
|
|
31
|
-
Multiple series (
|
31
|
+
Multiple series (except pie chart)
|
32
32
|
|
33
33
|
```erb
|
34
34
|
<%= line_chart Goal.all.map{|goal| {:name => goal.name, :data => goal.feats.group_by_week(:created_at).count } } %>
|
35
35
|
```
|
36
36
|
|
37
|
-
|
37
|
+
### Say Goodbye To Timeouts
|
38
|
+
|
39
|
+
Make your pages load super fast and stop worrying about timeouts. Give each chart its own endpoint.
|
40
|
+
|
41
|
+
```erb
|
42
|
+
<%= line_chart completed_tasks_charts_path %>
|
43
|
+
```
|
44
|
+
|
45
|
+
And in your controller, pass the data as JSON.
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
class ChartsController < ApplicationController
|
49
|
+
def completed_tasks
|
50
|
+
render :json => Task.group_by_day(:completed_at).count
|
51
|
+
end
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
**Note:** This feature requires jQuery at the moment.
|
56
|
+
|
57
|
+
### Options
|
58
|
+
|
59
|
+
id and height
|
38
60
|
|
39
61
|
```erb
|
40
62
|
<%= line_chart User.group_by_day(:created_at).count, :id => "users-chart", :height => "500px" %>
|
41
63
|
```
|
42
64
|
|
65
|
+
min and max values (except pie chart)
|
66
|
+
|
67
|
+
```erb
|
68
|
+
<%= line_chart User.group_by_day(:created_at).count, :min => 1000, :max => 5000 %>
|
69
|
+
```
|
70
|
+
|
71
|
+
**Note:** min defaults to 0 - use `:min => nil` to allow the chart to choose the minimum.
|
72
|
+
|
73
|
+
### Data
|
74
|
+
|
75
|
+
Pass data as a Hash or Array
|
76
|
+
|
77
|
+
```erb
|
78
|
+
<%= pie_chart({"Football" => 10, "Basketball" => 5}) %>
|
79
|
+
<%= pie_chart [["Football", 10], ["Basketball", 5]] %>
|
80
|
+
```
|
81
|
+
|
82
|
+
For multiple series, use the format
|
83
|
+
|
84
|
+
```erb
|
85
|
+
<% series_a = {time_0 => 5, time_1 => 7} # Hash %>
|
86
|
+
<% series_b = [[time_0, 8], [time_1, 9]] # or Array %>
|
87
|
+
<%= line_chart [{:name => "Series A", :data => series_a, {:name => "Series B", :data => series_b}] %>
|
88
|
+
```
|
89
|
+
|
90
|
+
Times can be a time, a timestamp, or a string (strings are parsed)
|
91
|
+
|
92
|
+
```erb
|
93
|
+
<% line_chart({20.day.ago => 5, 1368174456 => 4, "2013-05-07 00:00:00 UTC" => 7}) %>
|
94
|
+
```
|
95
|
+
|
43
96
|
## Installation
|
44
97
|
|
45
98
|
Add this line to your application's Gemfile:
|
@@ -56,6 +109,29 @@ And add the javascript files to your views.
|
|
56
109
|
<%= javascript_include_tag "//www.google.com/jsapi", "chartkick" %>
|
57
110
|
```
|
58
111
|
|
112
|
+
If you prefer Highcharts, use:
|
113
|
+
|
114
|
+
```erb
|
115
|
+
<%= javascript_include_tag "path/to/highcharts.js", "chartkick" %>
|
116
|
+
```
|
117
|
+
|
118
|
+
## No Ruby? No Problem
|
119
|
+
|
120
|
+
Chartkick doesn’t require Ruby.
|
121
|
+
|
122
|
+
```html
|
123
|
+
<script src="/path/to/chartkick.js"></script>
|
124
|
+
<div id="chart-1" style="height: 300px;"></div>
|
125
|
+
<script>
|
126
|
+
var chart = document.getElementById("chart-1");
|
127
|
+
new Chartkick.PieChart(chart, {"Football": 45, "Soccer": 56, "Basketball": 98});
|
128
|
+
// or remote
|
129
|
+
new Chartkick.LineChart(chart, "/charts/stocks");
|
130
|
+
</script>
|
131
|
+
```
|
132
|
+
|
133
|
+
Download [chartkick.js](https://raw.github.com/ankane/chartkick/master/app/assets/javascripts/chartkick.js)
|
134
|
+
|
59
135
|
## Contributing
|
60
136
|
|
61
137
|
1. Fork it
|
@@ -1,125 +1,482 @@
|
|
1
|
-
/*jslint browser: true, indent: 2 */
|
1
|
+
/*jslint browser: true, indent: 2, plusplus: true */
|
2
2
|
/*global google*/
|
3
3
|
|
4
4
|
(function() {
|
5
5
|
'use strict';
|
6
6
|
|
7
|
-
|
7
|
+
// vendor
|
8
8
|
|
9
|
-
//
|
10
|
-
var
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
9
|
+
// http://stackoverflow.com/questions/728360/most-elegant-way-to-clone-a-javascript-object
|
10
|
+
var clone = function(obj) {
|
11
|
+
var copy, i, attr, len;
|
12
|
+
|
13
|
+
// Handle the 3 simple types, and null or undefined
|
14
|
+
if (null === obj || "object" !== typeof obj) {
|
15
|
+
return obj;
|
16
|
+
}
|
17
|
+
|
18
|
+
// Handle Date
|
19
|
+
if (obj instanceof Date) {
|
20
|
+
copy = new Date();
|
21
|
+
copy.setTime(obj.getTime());
|
22
|
+
return copy;
|
23
|
+
}
|
24
|
+
|
25
|
+
// Handle Array
|
26
|
+
if (obj instanceof Array) {
|
27
|
+
copy = [];
|
28
|
+
for (i = 0, len = obj.length; i < len; i++) {
|
29
|
+
copy[i] = clone(obj[i]);
|
30
|
+
}
|
31
|
+
return copy;
|
32
|
+
}
|
33
|
+
|
34
|
+
// Handle Object
|
35
|
+
if (obj instanceof Object) {
|
36
|
+
copy = {};
|
37
|
+
for (attr in obj) {
|
38
|
+
if (obj.hasOwnProperty(attr)) {
|
39
|
+
copy[attr] = clone(obj[attr]);
|
40
|
+
}
|
41
|
+
}
|
42
|
+
return copy;
|
43
|
+
}
|
44
|
+
|
45
|
+
throw new Error("Unable to copy obj! Its type isn't supported.");
|
46
|
+
};
|
47
|
+
|
48
|
+
// https://github.com/Do/iso8601.js
|
49
|
+
var DECIMAL_SEPARATOR, ISO8601_PATTERN;
|
50
|
+
ISO8601_PATTERN = /(\d\d\d\d)(\-)?(\d\d)(\-)?(\d\d)(T)?(\d\d)(:)?(\d\d)?(:)?(\d\d)?([\.,]\d+)?($|Z|([\+\-])(\d\d)(:)?(\d\d)?)/i;
|
51
|
+
DECIMAL_SEPARATOR = String(1.5).charAt(1);
|
52
|
+
|
53
|
+
var parseISO8601 = function(input) {
|
54
|
+
var day, hour, matches, milliseconds, minutes, month, offset, result, seconds, type, year;
|
55
|
+
type = Object.prototype.toString.call(input);
|
56
|
+
if (type === '[object Date]') return input;
|
57
|
+
if (type !== '[object String]') return;
|
58
|
+
if (matches = input.match(ISO8601_PATTERN)) {
|
59
|
+
year = parseInt(matches[1], 10);
|
60
|
+
month = parseInt(matches[3], 10) - 1;
|
61
|
+
day = parseInt(matches[5], 10);
|
62
|
+
hour = parseInt(matches[7], 10);
|
63
|
+
minutes = matches[9] ? parseInt(matches[9], 10) : 0;
|
64
|
+
seconds = matches[11] ? parseInt(matches[11], 10) : 0;
|
65
|
+
milliseconds = matches[12] ? parseFloat(DECIMAL_SEPARATOR + matches[12].slice(1)) * 1000 : 0;
|
66
|
+
result = Date.UTC(year, month, day, hour, minutes, seconds, milliseconds);
|
67
|
+
if (matches[13] && matches[14]) {
|
68
|
+
offset = matches[15] * 60;
|
69
|
+
if (matches[17]) offset += parseInt(matches[17], 10);
|
70
|
+
offset *= matches[14] === '-' ? -1 : 1;
|
71
|
+
result -= offset * 60 * 1000;
|
72
|
+
}
|
73
|
+
return new Date(result);
|
74
|
+
}
|
75
|
+
};
|
76
|
+
|
77
|
+
// source
|
78
|
+
|
79
|
+
if ("Highcharts" in window) {
|
80
|
+
|
81
|
+
var defaultOptions = {
|
82
|
+
xAxis: {
|
83
|
+
labels: {
|
84
|
+
style: {
|
85
|
+
fontSize: "12px"
|
86
|
+
}
|
87
|
+
}
|
17
88
|
},
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
89
|
+
yAxis: {
|
90
|
+
title: {
|
91
|
+
text: null
|
92
|
+
},
|
93
|
+
labels: {
|
94
|
+
style: {
|
95
|
+
fontSize: "12px"
|
96
|
+
}
|
97
|
+
},
|
98
|
+
min: 0
|
25
99
|
},
|
26
|
-
|
27
|
-
|
100
|
+
title: {
|
101
|
+
text: null
|
28
102
|
},
|
29
|
-
|
30
|
-
|
31
|
-
vAxis: {
|
32
|
-
textStyle: {
|
33
|
-
color: "#666",
|
34
|
-
fontSize: 12
|
103
|
+
credits: {
|
104
|
+
enabled: false
|
35
105
|
},
|
36
|
-
|
37
|
-
|
38
|
-
|
106
|
+
legend: {
|
107
|
+
borderWidth: 0
|
108
|
+
},
|
109
|
+
tooltip: {
|
110
|
+
style: {
|
111
|
+
fontSize: "12px"
|
112
|
+
}
|
39
113
|
}
|
40
|
-
}
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
114
|
+
};
|
115
|
+
|
116
|
+
var jsOptions = function(opts) {
|
117
|
+
var options = clone(defaultOptions);
|
118
|
+
if ("min" in opts) {
|
119
|
+
options.yAxis.min = opts.min;
|
120
|
+
}
|
121
|
+
if ("max" in opts) {
|
122
|
+
options.yAxis.max = opts.max;
|
123
|
+
}
|
124
|
+
return options;
|
125
|
+
}
|
126
|
+
|
127
|
+
var renderLineChart = function(element, series, opts) {
|
128
|
+
var options = jsOptions(opts), data, i, j;
|
129
|
+
options.xAxis.type = "datetime";
|
130
|
+
options.chart = {type: "spline"};
|
131
|
+
|
132
|
+
for (i = 0; i < series.length; i++) {
|
133
|
+
data = series[i].data;
|
134
|
+
for (j = 0; j < data.length; j++) {
|
135
|
+
data[j][0] = data[j][0].getTime();
|
136
|
+
}
|
137
|
+
series[i].marker = {symbol: "circle"};
|
138
|
+
}
|
139
|
+
options.series = series;
|
140
|
+
|
141
|
+
if (series.length == 1) {
|
142
|
+
options.legend = {enabled: false};
|
143
|
+
}
|
144
|
+
$(element).highcharts(options);
|
145
|
+
};
|
146
|
+
|
147
|
+
var renderPieChart = function(element, series, opts) {
|
148
|
+
var options = jsOptions(opts);
|
149
|
+
options.series = [{
|
150
|
+
type: "pie",
|
151
|
+
name: "Value",
|
152
|
+
data: series
|
153
|
+
}];
|
154
|
+
$(element).highcharts(options);
|
155
|
+
};
|
156
|
+
|
157
|
+
var renderColumnChart = function(element, series, opts) {
|
158
|
+
var options = jsOptions(opts), data, i, j;
|
159
|
+
options.chart = {type: "column"};
|
160
|
+
|
161
|
+
var i, j, s, d, rows = [];
|
162
|
+
for (i = 0; i < series.length; i++) {
|
163
|
+
s = series[i];
|
164
|
+
|
165
|
+
for (j = 0; j < s.data.length; j++) {
|
166
|
+
d = s.data[j];
|
167
|
+
if (!rows[d[0]]) {
|
168
|
+
rows[d[0]] = new Array(series.length);
|
64
169
|
}
|
170
|
+
rows[d[0]][i] = d[1];
|
65
171
|
}
|
172
|
+
}
|
173
|
+
|
174
|
+
var categories = [];
|
175
|
+
for (i in rows) {
|
176
|
+
categories.push(i);
|
177
|
+
}
|
178
|
+
options.xAxis.categories = categories;
|
66
179
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
180
|
+
var newSeries = [];
|
181
|
+
for (i = 0; i < series.length; i++) {
|
182
|
+
d = [];
|
183
|
+
for (j = 0; j < categories.length; j++) {
|
184
|
+
d.push(rows[categories[j]][i]);
|
71
185
|
}
|
72
|
-
data.addRows(rows2);
|
73
186
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
}
|
187
|
+
newSeries.push({
|
188
|
+
name: series[i].name,
|
189
|
+
data: d
|
190
|
+
});
|
191
|
+
}
|
192
|
+
options.series = newSeries;
|
193
|
+
|
194
|
+
if (series.length == 1) {
|
195
|
+
options.legend.enabled = false;
|
196
|
+
}
|
197
|
+
$(element).highcharts(options);
|
198
|
+
};
|
199
|
+
}
|
200
|
+
else { // Google charts
|
201
|
+
|
202
|
+
var loaded = false;
|
203
|
+
google.setOnLoadCallback( function() {
|
204
|
+
loaded = true;
|
205
|
+
});
|
206
|
+
google.load("visualization", "1.0", {"packages": ["corechart"]});
|
207
|
+
|
208
|
+
var waitForLoaded = function(callback) {
|
209
|
+
google.setOnLoadCallback(callback); // always do this to prevent race conditions (watch out for other issues due to this)
|
210
|
+
if (loaded) {
|
211
|
+
callback();
|
212
|
+
}
|
213
|
+
}
|
214
|
+
|
215
|
+
// Set chart options
|
216
|
+
var defaultOptions = {
|
217
|
+
fontName: "'Lucida Grande', 'Lucida Sans Unicode', Verdana, Arial, Helvetica, sans-serif",
|
218
|
+
pointSize: 6,
|
219
|
+
legend: {
|
220
|
+
textStyle: {
|
221
|
+
fontSize: 12,
|
222
|
+
color: "#444"
|
223
|
+
},
|
224
|
+
alignment: "center",
|
225
|
+
position: "right"
|
226
|
+
},
|
227
|
+
curveType: "function",
|
228
|
+
hAxis: {
|
229
|
+
textStyle: {
|
230
|
+
color: "#666",
|
231
|
+
fontSize: 12
|
232
|
+
},
|
233
|
+
gridlines: {
|
234
|
+
color: "transparent"
|
235
|
+
},
|
236
|
+
baselineColor: "#ccc"
|
237
|
+
},
|
238
|
+
vAxis: {
|
239
|
+
textStyle: {
|
240
|
+
color: "#666",
|
241
|
+
fontSize: 12
|
242
|
+
},
|
243
|
+
baselineColor: "#ccc",
|
244
|
+
viewWindow: {
|
245
|
+
min: 0
|
246
|
+
}
|
247
|
+
},
|
248
|
+
tooltip: {
|
249
|
+
textStyle: {
|
250
|
+
color: "#666",
|
251
|
+
fontSize: 12
|
252
|
+
}
|
253
|
+
}
|
254
|
+
}
|
255
|
+
|
256
|
+
// cant use object as key
|
257
|
+
var createDataTable = function(series, columnType) {
|
258
|
+
var data = new google.visualization.DataTable();
|
259
|
+
data.addColumn(columnType, "");
|
260
|
+
|
261
|
+
var i, j, s, d, key, rows = [];
|
262
|
+
for (i = 0; i < series.length; i++) {
|
263
|
+
s = series[i];
|
264
|
+
data.addColumn("number", s.name);
|
265
|
+
|
266
|
+
for (j = 0; j < s.data.length; j++) {
|
267
|
+
d = s.data[j];
|
268
|
+
key = (columnType === "datetime") ? d[0].getTime() : d[0];
|
269
|
+
if (!rows[key]) {
|
270
|
+
rows[key] = new Array(series.length);
|
271
|
+
}
|
272
|
+
rows[key][i] = toFloat(d[1]);
|
273
|
+
}
|
274
|
+
}
|
275
|
+
|
276
|
+
var rows2 = [];
|
277
|
+
for (i in rows) {
|
278
|
+
rows2.push([(columnType === "datetime") ? new Date(toFloat(i)) : i].concat(rows[i]));
|
279
|
+
}
|
280
|
+
data.addRows(rows2);
|
281
|
+
|
282
|
+
return data;
|
283
|
+
};
|
284
|
+
|
285
|
+
var jsOptions = function(opts) {
|
286
|
+
var options = clone(defaultOptions);
|
287
|
+
if ("min" in opts) {
|
288
|
+
options.vAxis.viewWindow.min = opts.min;
|
289
|
+
}
|
290
|
+
if ("max" in opts) {
|
291
|
+
options.vAxis.viewWindow.max = opts.max;
|
292
|
+
}
|
293
|
+
return options;
|
294
|
+
}
|
295
|
+
|
296
|
+
var renderLineChart = function(element, series, opts) {
|
297
|
+
waitForLoaded(function() {
|
298
|
+
var data = createDataTable(series, "datetime");
|
299
|
+
|
300
|
+
var options = jsOptions(opts);
|
301
|
+
if (series.length == 1) {
|
78
302
|
options.legend.position = "none";
|
79
303
|
}
|
80
|
-
options.chartArea = null;
|
81
304
|
|
82
|
-
chart = new google.visualization.LineChart(
|
305
|
+
var chart = new google.visualization.LineChart(element);
|
83
306
|
chart.draw(data, options);
|
84
|
-
})
|
85
|
-
}
|
86
|
-
PieChart: function(elementId, series) {
|
87
|
-
google.setOnLoadCallback(function() {
|
88
|
-
// Create the data table.
|
89
|
-
var data = new google.visualization.DataTable();
|
307
|
+
})
|
308
|
+
};
|
90
309
|
|
91
|
-
|
310
|
+
var renderPieChart = function(element, series, opts) {
|
311
|
+
waitForLoaded(function() {
|
312
|
+
var data = new google.visualization.DataTable();
|
92
313
|
data.addColumn("string", "");
|
93
314
|
data.addColumn("number", "Value");
|
94
315
|
data.addRows(series);
|
95
316
|
|
96
|
-
var options =
|
97
|
-
options.legend.position = "right";
|
317
|
+
var options = jsOptions(opts);
|
98
318
|
options.chartArea = {
|
99
319
|
top: "10%",
|
100
320
|
height: "80%"
|
101
321
|
};
|
102
322
|
|
103
|
-
var chart = new google.visualization.PieChart(
|
323
|
+
var chart = new google.visualization.PieChart(element);
|
104
324
|
chart.draw(data, options);
|
105
325
|
});
|
106
|
-
}
|
107
|
-
ColumnChart: function(elementId, series) {
|
108
|
-
google.setOnLoadCallback(function() {
|
109
|
-
var data = new google.visualization.DataTable();
|
326
|
+
};
|
110
327
|
|
111
|
-
|
112
|
-
|
113
|
-
data
|
114
|
-
data.addRows(series);
|
328
|
+
var renderColumnChart = function(element, series, opts) {
|
329
|
+
waitForLoaded(function() {
|
330
|
+
var data = createDataTable(series, "string");
|
115
331
|
|
116
|
-
var options =
|
117
|
-
|
118
|
-
|
332
|
+
var options = jsOptions(opts);
|
333
|
+
if (series.length == 1) {
|
334
|
+
options.legend.position = "none";
|
335
|
+
}
|
119
336
|
|
120
|
-
var chart = new google.visualization.ColumnChart(
|
337
|
+
var chart = new google.visualization.ColumnChart(element);
|
121
338
|
chart.draw(data, options);
|
122
339
|
});
|
340
|
+
};
|
341
|
+
}
|
342
|
+
|
343
|
+
var chartError = function(element) {
|
344
|
+
element.innerHTML = "Error Loading Chart";
|
345
|
+
element.style.color = "red";
|
346
|
+
};
|
347
|
+
|
348
|
+
var getJSON = function(element, url, success) {
|
349
|
+
// TODO no jquery
|
350
|
+
// TODO parse JSON in older browsers
|
351
|
+
// https://raw.github.com/douglascrockford/JSON-js/master/json2.js
|
352
|
+
$.ajax({
|
353
|
+
dataType: "json",
|
354
|
+
url: url,
|
355
|
+
success: success,
|
356
|
+
error: function() {
|
357
|
+
chartError(element);
|
358
|
+
}
|
359
|
+
});
|
360
|
+
};
|
361
|
+
|
362
|
+
// not working all the time
|
363
|
+
var errorCatcher = function(element, data, opts, callback) {
|
364
|
+
try {
|
365
|
+
callback(element, data, opts);
|
366
|
+
} catch (err) {
|
367
|
+
chartError(element);
|
368
|
+
throw err;
|
369
|
+
}
|
370
|
+
};
|
371
|
+
|
372
|
+
// TODO catch errors for callback
|
373
|
+
var fetchDataSource = function(element, dataSource, opts, callback) {
|
374
|
+
if (typeof dataSource === "string") {
|
375
|
+
getJSON(element, dataSource, function(data, textStatus, jqXHR) {
|
376
|
+
errorCatcher(element, data, opts, callback);
|
377
|
+
});
|
378
|
+
}
|
379
|
+
else {
|
380
|
+
errorCatcher(element, dataSource, opts, callback);
|
381
|
+
}
|
382
|
+
};
|
383
|
+
|
384
|
+
var isArray = function(variable) {
|
385
|
+
return Object.prototype.toString.call(variable) === "[object Array]"
|
386
|
+
};
|
387
|
+
|
388
|
+
var standardSeries = function(series, time) {
|
389
|
+
var i, j, data, r, key;
|
390
|
+
|
391
|
+
// clean data
|
392
|
+
if (!isArray(series) || typeof series[0] !== "object" || isArray(series[0])) {
|
393
|
+
series = [{name: "Value", data: series}];
|
394
|
+
}
|
395
|
+
|
396
|
+
// right format
|
397
|
+
for (i = 0; i < series.length; i++) {
|
398
|
+
data = toArr(series[i].data);
|
399
|
+
r = [];
|
400
|
+
for (j = 0; j < data.length; j++) {
|
401
|
+
key = data[j][0];
|
402
|
+
if (time) {
|
403
|
+
key = toDate(key);
|
404
|
+
}
|
405
|
+
else {
|
406
|
+
key = toStr(key);
|
407
|
+
}
|
408
|
+
r.push([key, toFloat(data[j][1])]);
|
409
|
+
}
|
410
|
+
if (time) {
|
411
|
+
r.sort(function(a,b){ return a[0].getTime() - b[0].getTime() });
|
412
|
+
}
|
413
|
+
series[i].data = r;
|
414
|
+
}
|
415
|
+
|
416
|
+
return series;
|
417
|
+
}
|
418
|
+
|
419
|
+
var toStr = function(n) {
|
420
|
+
return "" + n;
|
421
|
+
}
|
422
|
+
|
423
|
+
var toFloat = function(n) {
|
424
|
+
return parseFloat(n);
|
425
|
+
};
|
426
|
+
|
427
|
+
var toDate = function(n) {
|
428
|
+
if (typeof n !== "object") {
|
429
|
+
if (typeof n === "number") {
|
430
|
+
n = new Date(n * 1000); // ms
|
431
|
+
}
|
432
|
+
else { // str
|
433
|
+
// try our best to get the str into iso8601
|
434
|
+
// TODO be smarter about this
|
435
|
+
var str = n.replace(/ /, "T").replace(" ", "").replace("UTC", "Z");
|
436
|
+
n = parseISO8601(str) || new Date(n);
|
437
|
+
}
|
438
|
+
}
|
439
|
+
return n;
|
440
|
+
};
|
441
|
+
|
442
|
+
var toArr = function(n) {
|
443
|
+
if (!isArray(n)) {
|
444
|
+
var arr = [], i;
|
445
|
+
for (i in n) {
|
446
|
+
if (n.hasOwnProperty(i)) {
|
447
|
+
arr.push([i, n[i]]);
|
448
|
+
}
|
449
|
+
}
|
450
|
+
n = arr;
|
451
|
+
}
|
452
|
+
return n;
|
453
|
+
}
|
454
|
+
|
455
|
+
var processLineData = function(element, data, opts) {
|
456
|
+
renderLineChart(element, standardSeries(data, true), opts);
|
457
|
+
}
|
458
|
+
|
459
|
+
var processColumnData = function(element, data, opts) {
|
460
|
+
renderColumnChart(element, standardSeries(data, false), opts);
|
461
|
+
}
|
462
|
+
|
463
|
+
var processPieData = function(element, data, opts) {
|
464
|
+
var perfectData = toArr(data), i;
|
465
|
+
for (i = 0; i < perfectData.length; i++) {
|
466
|
+
perfectData[i] = [toStr(perfectData[i][0]), toFloat(perfectData[i][1])];
|
467
|
+
}
|
468
|
+
renderPieChart(element, perfectData, opts);
|
469
|
+
}
|
470
|
+
|
471
|
+
var Chartkick = {
|
472
|
+
LineChart: function(element, dataSource, opts) {
|
473
|
+
fetchDataSource(element, dataSource, opts || {}, processLineData);
|
474
|
+
},
|
475
|
+
ColumnChart: function(element, dataSource, opts) {
|
476
|
+
fetchDataSource(element, dataSource, opts || {}, processColumnData);
|
477
|
+
},
|
478
|
+
PieChart: function(element, dataSource, opts) {
|
479
|
+
fetchDataSource(element, dataSource, opts || {}, processPieData);
|
123
480
|
}
|
124
481
|
};
|
125
482
|
|
data/lib/chartkick/helper.rb
CHANGED
@@ -1,46 +1,33 @@
|
|
1
1
|
module Chartkick
|
2
2
|
module Helper
|
3
3
|
|
4
|
-
def line_chart(
|
5
|
-
|
6
|
-
series = [{:name => "Value", :data => series}]
|
7
|
-
end
|
8
|
-
series.each do |s|
|
9
|
-
s[:data] = s[:data].map{|k, v| [k.is_a?(Time) ? k : Time.parse(k), v] }.sort_by{|k, v| k }.map{|k, v| [k.to_i, v.to_f] }
|
10
|
-
end
|
11
|
-
|
12
|
-
html, element_id = chartkick_div(options)
|
13
|
-
html << content_tag(:script) do
|
14
|
-
concat "new Chartkick.LineChart(#{element_id.to_json}, #{series.to_json});".html_safe
|
15
|
-
end
|
16
|
-
html
|
4
|
+
def line_chart(data_source, options = {})
|
5
|
+
chartkick_chart "LineChart", data_source, options
|
17
6
|
end
|
18
7
|
|
19
|
-
def pie_chart(
|
20
|
-
|
21
|
-
html, element_id = chartkick_div(options)
|
22
|
-
html << content_tag(:script) do
|
23
|
-
concat "new Chartkick.PieChart(#{element_id.to_json}, #{series.to_json});".html_safe
|
24
|
-
end
|
25
|
-
html
|
8
|
+
def pie_chart(data_source, options = {})
|
9
|
+
chartkick_chart "PieChart", data_source, options
|
26
10
|
end
|
27
11
|
|
28
|
-
def column_chart(
|
29
|
-
|
30
|
-
html, element_id = chartkick_div(options)
|
31
|
-
html << content_tag(:script) do
|
32
|
-
concat "new Chartkick.ColumnChart(#{element_id.to_json}, #{series.to_json});".html_safe
|
33
|
-
end
|
34
|
-
html
|
12
|
+
def column_chart(data_source, options = {})
|
13
|
+
chartkick_chart "ColumnChart", data_source, options
|
35
14
|
end
|
36
15
|
|
37
16
|
private
|
38
17
|
|
39
|
-
def
|
18
|
+
def chartkick_chart(klass, data_source, options, &block)
|
40
19
|
@chartkick_chart_id ||= 0
|
41
|
-
|
42
|
-
|
43
|
-
|
20
|
+
options = options.dup
|
21
|
+
element_id = options.delete(:id) || "chart-#{@chartkick_chart_id += 1}"
|
22
|
+
height = options.delete(:height) || "300px"
|
23
|
+
|
24
|
+
# don't quote font-family names due to rails escaping
|
25
|
+
html = content_tag :div, :id => element_id, :style => "height: #{height}; text-align: center; color: #999; line-height: #{height}; font-size: 14px; font-family: Lucida Grande, Lucida Sans Unicode, Verdana, Arial, Helvetica, sans-serif;" do
|
26
|
+
concat "Loading..."
|
27
|
+
end
|
28
|
+
html += javascript_tag do
|
29
|
+
concat "new Chartkick.#{klass}(document.getElementById(#{element_id.to_json}), #{data_source.to_json}, #{options.to_json});".html_safe
|
30
|
+
end
|
44
31
|
end
|
45
32
|
|
46
33
|
end
|
data/lib/chartkick/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chartkick
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-05-
|
11
|
+
date: 2013-05-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: railties
|