ar_to_chart 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/CHANGELOG +3 -0
- data/MIT-LICENSE +20 -0
- data/README.textile +36 -0
- data/ar_to_chart.gemspec +2 -3
- data/files/ar_to_chart.css +33 -0
- data/files/ar_to_chart.js +238 -0
- data/lib/ar_to_chart.rb +16 -0
- data/lib/ar_to_chart/active_record_array.rb +74 -0
- data/lib/ar_to_chart/charting/highcharts.rb +113 -0
- data/lib/ar_to_chart/charting/highcharts/area.rb +25 -0
- data/lib/ar_to_chart/charting/highcharts/base.rb +130 -0
- data/lib/ar_to_chart/charting/highcharts/funnel.rb +10 -0
- data/lib/ar_to_chart/charting/highcharts/pie.rb +22 -0
- data/lib/ar_to_chart/charting/period.rb +157 -0
- data/lib/ar_to_chart/charting/sparklines.rb +65 -0
- data/lib/ar_to_chart/charting/sparklines/base.rb +54 -0
- data/lib/ar_to_chart/charting/sparklines/line.rb +12 -0
- data/lib/ar_to_chart/charting/transforms.rb +51 -0
- data/lib/ar_to_chart/version.rb +1 -1
- data/lib/locale/to_chart-en.yml +3 -0
- data/lib/tasks/at_to_chart_tasks.rake +4 -0
- metadata +24 -5
data/.gitignore
CHANGED
data/CHANGELOG
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Kip Cole
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
h1. Description
|
2
|
+
|
3
|
+
p. ar_to_chart generates Highcharts-based charts from an ActiveRecord result set. For example:
|
4
|
+
|
5
|
+
bc. Product.all.to_chart(:price, :product)
|
6
|
+
|
7
|
+
p. Will generate HTML for a chart container and javascript to render the chart in the browser.
|
8
|
+
|
9
|
+
h2. Dependencies
|
10
|
+
|
11
|
+
ar_to_chart depends on:
|
12
|
+
|
13
|
+
* jQuery (jquery.com)
|
14
|
+
* Highcharts (www.highcharts.com)
|
15
|
+
* arToChart javascript object (found in the files directory of the gem). This is the javascript interface between ar_to_chart and the Highcharts library
|
16
|
+
* ar_to_chart.css (found in the files directory of the gem). This css defines the attributes used by the ar_to_chart javascript.
|
17
|
+
|
18
|
+
h2. Examples
|
19
|
+
|
20
|
+
p. Coming soon
|
21
|
+
|
22
|
+
h2. Options
|
23
|
+
|
24
|
+
p. Also coming soon
|
25
|
+
|
26
|
+
h1. License
|
27
|
+
|
28
|
+
(The MIT License)
|
29
|
+
|
30
|
+
Copyright © 2010 Kip Cole
|
31
|
+
|
32
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
33
|
+
|
34
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
35
|
+
|
36
|
+
THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/ar_to_chart.gemspec
CHANGED
@@ -12,9 +12,8 @@ Gem::Specification.new do |s|
|
|
12
12
|
s.summary = %q{Render an ActiveRecord result set as a chart}
|
13
13
|
s.description = <<-EOF
|
14
14
|
Defines Array#to_chart that will accept ActiveRecord result sets
|
15
|
-
and render them as a chart. Currently assumes
|
16
|
-
|
17
|
-
soon.
|
15
|
+
and render them as a chart. Currently assumes Highcharts (highcharts.com)
|
16
|
+
as the charting engine.
|
18
17
|
EOF
|
19
18
|
|
20
19
|
s.rubyforge_project = "ar_to_chart"
|
@@ -0,0 +1,33 @@
|
|
1
|
+
/*
|
2
|
+
For Highcharts
|
3
|
+
These are sort of proxy classes. They are here just so we can access the
|
4
|
+
attributes in JS and apply them to charts.
|
5
|
+
*/
|
6
|
+
|
7
|
+
/* Chart background */
|
8
|
+
#highcharts { background-color: #ddd; }
|
9
|
+
|
10
|
+
/* Used for axis text */
|
11
|
+
#highcharts-text {
|
12
|
+
color: black;
|
13
|
+
font-size: 90%;
|
14
|
+
font-weight: normal;
|
15
|
+
}
|
16
|
+
|
17
|
+
/* Area graph color for the area */
|
18
|
+
#highcharts-area {
|
19
|
+
color: rgb(25,135,213);
|
20
|
+
color: rgba(25,135,213, 0.3);
|
21
|
+
}
|
22
|
+
|
23
|
+
/* Chart line color */
|
24
|
+
#highcharts-line { color: rgb(25,135,213); }
|
25
|
+
|
26
|
+
/* Gridlines color */
|
27
|
+
#highcharts-gridlines { color: white;}
|
28
|
+
|
29
|
+
/* Axis line color */
|
30
|
+
#highcharts-axis { color: #777; }
|
31
|
+
|
32
|
+
/* X-Axis plotbands color */
|
33
|
+
#highcharts-xbands { color: #ccc }
|
@@ -0,0 +1,238 @@
|
|
1
|
+
function arToChart() {
|
2
|
+
// Set up the various colours we want to use here
|
3
|
+
var self = this;
|
4
|
+
|
5
|
+
// Add some hidden structure that we can then use to get color and style information from
|
6
|
+
var template = "<div style='display:none;width=1em'><p id=highcharts> </p>" +
|
7
|
+
"<p id=highcharts-text>&npsb</p><p id=highcharts-area>&npsb</p>" +
|
8
|
+
"<p id=highcharts-line>&npsb</p><p id=highcharts-gridlines>&npsb</p>" +
|
9
|
+
"<p id=highcharts-axis>&npsb</p><p id=highcharts-xbands>&npsb</p></div>";
|
10
|
+
$('body').append(template);
|
11
|
+
|
12
|
+
this.font = {
|
13
|
+
size: $('#highcharts-text').css('font-size'),
|
14
|
+
family: $('#highcharts-text').css('font-family'),
|
15
|
+
weight: $('#highcharts-text').css('font-weight'),
|
16
|
+
color: $('#highcharts-text').css('color')
|
17
|
+
}
|
18
|
+
|
19
|
+
this.lineWeight = {
|
20
|
+
grid: 1,
|
21
|
+
axis: 1
|
22
|
+
}
|
23
|
+
|
24
|
+
this.colors = {
|
25
|
+
background: $('#highcharts').css('background-color'),
|
26
|
+
areaFill: $('#highcharts-area').css('color'),
|
27
|
+
areaOpacity: $('#highcharts-area').css('opacity'),
|
28
|
+
gridLines: $('#highcharts-gridlines').css('color'),
|
29
|
+
lineColor: $('#highcharts-line').css('color'),
|
30
|
+
xAxisLine: $('#highcharts-axis').css('color'),
|
31
|
+
yAxisLine: $('#highcharts-axis').css('color'),
|
32
|
+
plotBands: $('#highcharts-xbands').css('color')
|
33
|
+
};
|
34
|
+
|
35
|
+
function will_print() {
|
36
|
+
return window.location.href.match(/print=/) || pdf_export();
|
37
|
+
}
|
38
|
+
|
39
|
+
function pdf_export() {
|
40
|
+
return $('meta[name=pdf-output]').length > 0;
|
41
|
+
}
|
42
|
+
|
43
|
+
function getLineWidth(series) {
|
44
|
+
if (pdf_export) {
|
45
|
+
return 1;
|
46
|
+
} else {
|
47
|
+
return (series.length > 50) ? 1 : self.lineWeight.axis;
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
// Render an Area chart with one or more data series
|
52
|
+
this.area = function(container, categories, series_data, options) {
|
53
|
+
|
54
|
+
/* Put colors into plotbands */
|
55
|
+
if (options.x_plot_bands) {
|
56
|
+
$(options.x_plot_bands).each(function(index, item) {
|
57
|
+
item.color = self.colors.plotBands;
|
58
|
+
});
|
59
|
+
};
|
60
|
+
|
61
|
+
return chart = new Highcharts.Chart({
|
62
|
+
chart: {
|
63
|
+
credits: {
|
64
|
+
enabled: false
|
65
|
+
},
|
66
|
+
borderWidth: 0,
|
67
|
+
borderColor: self.colors.background,
|
68
|
+
renderTo: container,
|
69
|
+
defaultSeriesType: 'area',
|
70
|
+
backgroundColor: self.colors.background,
|
71
|
+
marginTop: 15,
|
72
|
+
zoomType: 'x'
|
73
|
+
},
|
74
|
+
navigation: {
|
75
|
+
buttonOptions: {
|
76
|
+
backgroundColor: self.colors.background
|
77
|
+
}
|
78
|
+
},
|
79
|
+
exporting: {
|
80
|
+
enabled: !will_print()
|
81
|
+
},
|
82
|
+
title: {
|
83
|
+
text: options.title || ''
|
84
|
+
},
|
85
|
+
subtitle: {
|
86
|
+
text: options.subtitle || ''
|
87
|
+
},
|
88
|
+
xAxis: {
|
89
|
+
type: (categories ? 'linear' : 'datetime'),
|
90
|
+
categories: categories,
|
91
|
+
gridLineColor: self.colors.gridLines,
|
92
|
+
gridLineWidth: (series_data[0].data.length > 50) ? 0 : self.lineWeight.grid,
|
93
|
+
lineColor: self.colors.xAxisLine,
|
94
|
+
lineWidth: getLineWidth(series_data[0].data),
|
95
|
+
title: {
|
96
|
+
text: options.x_axis || ''
|
97
|
+
},
|
98
|
+
labels: {
|
99
|
+
step: options.x_step || 1,
|
100
|
+
staggerLines: options.staggerLines || 1,
|
101
|
+
style: {
|
102
|
+
fontSize: self.font.size,
|
103
|
+
fontFamily: self.font.family,
|
104
|
+
fontWeight: self.font.weight,
|
105
|
+
color: self.font.color
|
106
|
+
},
|
107
|
+
formatter: function() {
|
108
|
+
return this.value;
|
109
|
+
}
|
110
|
+
},
|
111
|
+
plotBands: options.x_plot_bands
|
112
|
+
},
|
113
|
+
yAxis: {
|
114
|
+
gridLineColor: self.colors.gridLines,
|
115
|
+
gridLineWidth: self.lineWeight.grid,
|
116
|
+
lineColor: self.colors.yAxisLine,
|
117
|
+
lineWidth: self.lineWeight.axis,
|
118
|
+
title: {
|
119
|
+
text: options.y_axis || ''
|
120
|
+
},
|
121
|
+
labels: {
|
122
|
+
style: {
|
123
|
+
fontSize: self.font.size,
|
124
|
+
fontFamily: self.font.family,
|
125
|
+
fontWeight: self.font.weight,
|
126
|
+
color: self.font.color
|
127
|
+
},
|
128
|
+
formatter: function() {
|
129
|
+
return this.value;
|
130
|
+
}
|
131
|
+
}
|
132
|
+
},
|
133
|
+
tooltip: {
|
134
|
+
formatter: function() {
|
135
|
+
if (this.series.type == 'pie') {
|
136
|
+
return this.series.name + ':<br>' + this.point.name + ": " + Highcharts.numberFormat(this.y, 0)
|
137
|
+
} else {
|
138
|
+
return 'On: ' + this.x + "<br>" + this.series.name +': ' + Highcharts.numberFormat(this.y, 0)
|
139
|
+
}
|
140
|
+
}
|
141
|
+
},
|
142
|
+
legend: {
|
143
|
+
enabled: (series_data.size > 1)
|
144
|
+
},
|
145
|
+
plotOptions: {
|
146
|
+
series: {
|
147
|
+
enableMouseTracking: !will_print(),
|
148
|
+
shadow: false,
|
149
|
+
animation: !will_print()
|
150
|
+
},
|
151
|
+
area: {
|
152
|
+
fillColor: self.colors.areaFill,
|
153
|
+
color: self.colors.lineColor,
|
154
|
+
lineWidth: 3,
|
155
|
+
marker: {
|
156
|
+
enabled: false,
|
157
|
+
symbol: 'circle',
|
158
|
+
radius: 2,
|
159
|
+
states: {
|
160
|
+
hover: {
|
161
|
+
enabled: true
|
162
|
+
}
|
163
|
+
}
|
164
|
+
}
|
165
|
+
},
|
166
|
+
pie: {
|
167
|
+
allowPointSelect: true,
|
168
|
+
cursor: 'pointer',
|
169
|
+
dataLabels: {
|
170
|
+
enabled: true,
|
171
|
+
formatter: function() {
|
172
|
+
if (this.y > 0) return this.point.name + ": " + Highcharts.numberFormat(this.percentage, 1) +'%';
|
173
|
+
},
|
174
|
+
color: self.font.color
|
175
|
+
}
|
176
|
+
}
|
177
|
+
},
|
178
|
+
series: series_data
|
179
|
+
});
|
180
|
+
};
|
181
|
+
|
182
|
+
// Render a Pie chart
|
183
|
+
this.pie = function(container, categories, series_data, options) {
|
184
|
+
this.area(container, categories, series_data, options);
|
185
|
+
}
|
186
|
+
|
187
|
+
// Render Funnel chart
|
188
|
+
this.funnel = function(container, categories, series_data, options) {
|
189
|
+
return chart = new Highcharts.Chart({
|
190
|
+
chart: {
|
191
|
+
backgroundColor: self.colors.background,
|
192
|
+
borderWidth: 0,
|
193
|
+
borderColor: self.colors.background,
|
194
|
+
renderTo: container,
|
195
|
+
defaultSeriesType: 'funnel',
|
196
|
+
margin: [20, 100, 40, 180]
|
197
|
+
},
|
198
|
+
navigation: {
|
199
|
+
buttonOptions: {
|
200
|
+
backgroundColor: self.colors.background
|
201
|
+
}
|
202
|
+
},
|
203
|
+
title: {
|
204
|
+
text: options.title || ''
|
205
|
+
},
|
206
|
+
subtitle: {
|
207
|
+
text: options.subtitle || ''
|
208
|
+
},
|
209
|
+
plotArea: {
|
210
|
+
shadow: null,
|
211
|
+
borderWidth: null,
|
212
|
+
backgroundColor: null
|
213
|
+
},
|
214
|
+
tooltip: {
|
215
|
+
formatter: function() {
|
216
|
+
return '<b>'+ this.point.name +'</b>: '+ Highcharts.numberFormat(this.y, 0);
|
217
|
+
}
|
218
|
+
},
|
219
|
+
plotOptions: {
|
220
|
+
series: {
|
221
|
+
dataLabels: {
|
222
|
+
align: 'left',
|
223
|
+
x: -300,
|
224
|
+
enabled: true,
|
225
|
+
color: self.font.color,
|
226
|
+
formatter: function() {
|
227
|
+
return '<b>'+ this.point.name +'</b> ('+ Highcharts.numberFormat(this.point.y, 0) +')';
|
228
|
+
}
|
229
|
+
}
|
230
|
+
}
|
231
|
+
},
|
232
|
+
legend: {
|
233
|
+
enabled: false
|
234
|
+
},
|
235
|
+
series: series_data
|
236
|
+
});
|
237
|
+
};
|
238
|
+
};
|
data/lib/ar_to_chart.rb
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/ar_to_chart/charting/highcharts.rb'
|
2
|
+
require File.dirname(__FILE__) + '/ar_to_chart/charting/sparklines.rb'
|
3
|
+
require File.dirname(__FILE__) + '/ar_to_chart/charting/period.rb'
|
4
|
+
require File.dirname(__FILE__) + '/ar_to_chart/charting/highcharts/base'
|
5
|
+
require File.dirname(__FILE__) + '/ar_to_chart/charting/highcharts/area'
|
6
|
+
require File.dirname(__FILE__) + '/ar_to_chart/charting/highcharts/pie'
|
7
|
+
require File.dirname(__FILE__) + '/ar_to_chart/charting/highcharts/funnel'
|
8
|
+
|
9
|
+
require File.dirname(__FILE__) + '/ar_to_chart/active_record_array.rb'
|
10
|
+
Array.send :include, Charting::ActiveRecordArray
|
11
|
+
|
12
|
+
require File.dirname(__FILE__) + '/ar_to_chart/charting/transforms.rb'
|
13
|
+
ActiveRecord::Base.send :include, Charting::Transforms
|
14
|
+
|
15
|
+
I18n.load_path << Dir[ File.join(File.dirname(__FILE__), 'locale', '*.{rb,yml}') ]
|
16
|
+
|
1
17
|
module ArToChart
|
2
18
|
# Your code goes here...
|
3
19
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# Adds method to Array to allow output of flash-based charts from
|
2
|
+
# active record result sets
|
3
|
+
module Charting
|
4
|
+
module ActiveRecordArray
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
extend ClassMethods
|
8
|
+
include InstanceMethods
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module InstanceMethods
|
13
|
+
# Render a chart based upon an ActiveRecord result set.
|
14
|
+
# Requires jQuery.
|
15
|
+
#
|
16
|
+
# ====Parameters
|
17
|
+
#
|
18
|
+
# columns: column name (or array of names) representing the
|
19
|
+
# Y-Axis
|
20
|
+
# label_column: The category (X-Axis) column
|
21
|
+
#
|
22
|
+
# ====Options
|
23
|
+
#
|
24
|
+
# See the options in Charting::Highcharts::Renderer
|
25
|
+
def to_chart(columns, label_column, options = {})
|
26
|
+
chart_object(columns, label_column, options).to_html
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns the chart container (the <div>) and the javascript
|
30
|
+
# separately so you can put the container where you want and
|
31
|
+
# the javascript at the end of the document
|
32
|
+
def to_container_and_script(column, label_column, options = {})
|
33
|
+
chart = chart_object(column, label_column, options)
|
34
|
+
return chart.container, chart.script
|
35
|
+
end
|
36
|
+
|
37
|
+
# Render a sparkline based upon an ActiveRecord result set.
|
38
|
+
# Requires jQuery and jQuery sparklines plugin.
|
39
|
+
#
|
40
|
+
# ====Parameters
|
41
|
+
#
|
42
|
+
# column: column name representing the
|
43
|
+
# Y-Axis
|
44
|
+
# label_column: The category (X-Axis) column
|
45
|
+
#
|
46
|
+
# ====Options
|
47
|
+
#
|
48
|
+
# See the options in Charting::Sparklines::Renderer
|
49
|
+
def to_sparkline(column, options = {})
|
50
|
+
sparkline_object(column, options).to_html
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
# Eventually we'll do charting engine selection here. For now
|
55
|
+
# only Highcharts or Sparklines
|
56
|
+
def chart_object(label_column, columns, options)
|
57
|
+
Charting::Highcharts::Renderer.new(self, columns, label_column, merged_options(options))
|
58
|
+
end
|
59
|
+
|
60
|
+
def sparkline_object(column, options)
|
61
|
+
Charting::Sparklines::Renderer.new(self, column, merged_options(options))
|
62
|
+
end
|
63
|
+
|
64
|
+
def merged_options(options)
|
65
|
+
{}.merge(options)
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
module ClassMethods
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Charting
|
2
|
+
module Highcharts
|
3
|
+
class Renderer
|
4
|
+
DEFAULT_OPTIONS = {
|
5
|
+
:container_height => '200px',
|
6
|
+
:type => :area,
|
7
|
+
}
|
8
|
+
DEFAULT_CHARTING_OBJECT = 'arToChart'
|
9
|
+
|
10
|
+
attr_accessor :categories, :series, :options, :chart
|
11
|
+
cattr_accessor :charting_object
|
12
|
+
|
13
|
+
# Generate Highchart based charts. CSS is used
|
14
|
+
# for the colouring. See the file ar_to_chart.css included
|
15
|
+
# in the files directory of the gem.
|
16
|
+
#
|
17
|
+
# ====Parameters
|
18
|
+
#
|
19
|
+
# data_source: The active record result set
|
20
|
+
# category_column: The column used for the x-axis
|
21
|
+
# data_columns: one column name or an array of column names to be charted or a hash
|
22
|
+
# with pairs of :column_names => :series_type
|
23
|
+
# options: options hash
|
24
|
+
#
|
25
|
+
# ====Options
|
26
|
+
#
|
27
|
+
# :type Chart type to render. Defauly is :area. Other options
|
28
|
+
# are :pie and :funnel. Subclass Base to create other
|
29
|
+
# chart types available in Highcharts. The name of the subclass
|
30
|
+
# becomes the chart type.
|
31
|
+
# :container DOM id of the container to render to. Default is to
|
32
|
+
# generate a name.
|
33
|
+
# :container_height Requested height of the container. Defaults to 200px
|
34
|
+
# :charting_object The javascript object that is instantiated as the
|
35
|
+
# browser renderer. Defaults to arToChar (supplied in
|
36
|
+
# the files directory of the gem)
|
37
|
+
#
|
38
|
+
def initialize(data_source, category_column, data_columns, options = {})
|
39
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
40
|
+
@options[:container] ||= generate_container_name
|
41
|
+
@options[:charting_object] ||= self.class.charting_object || DEFAULT_CHARTING_OBJECT
|
42
|
+
@data_columns = data_columns.respond_to?(:each) ? data_columns : [data_columns]
|
43
|
+
@chart = chart_class.new(data_source, category_column, @data_columns, @options)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns the <div> into which the chart will be rendered.
|
47
|
+
def container
|
48
|
+
<<-EOF
|
49
|
+
<div id='#{container_id}' #{styles}"></div>
|
50
|
+
EOF
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the javscript (without <script> tag) that is sent
|
54
|
+
# to the browser to render the chart. Note that there is a
|
55
|
+
# dependency on the ar_to_chart.js javascript library being
|
56
|
+
# loaded in the <head> of the document.
|
57
|
+
#
|
58
|
+
# See the ar_to_chart.js file included in the files directory
|
59
|
+
# of the gem.
|
60
|
+
def script
|
61
|
+
<<-EOF
|
62
|
+
$(document).ready(function() {
|
63
|
+
chart = new #{charting_object};
|
64
|
+
#{chart.to_js}
|
65
|
+
});
|
66
|
+
EOF
|
67
|
+
end
|
68
|
+
|
69
|
+
# Return the HTML of the container <div> and the
|
70
|
+
# script to render the chart.
|
71
|
+
def to_html
|
72
|
+
<<-EOF
|
73
|
+
#{container}
|
74
|
+
<script type="text/javascript">
|
75
|
+
#{script}
|
76
|
+
</script>
|
77
|
+
EOF
|
78
|
+
end
|
79
|
+
|
80
|
+
# Set configuration options. Currently no options
|
81
|
+
# are available.
|
82
|
+
def self.configure
|
83
|
+
yield self
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
def chart_class
|
88
|
+
@chart_class ||= "Charting::Highcharts::#{options[:type].to_s.titleize}".constantize
|
89
|
+
end
|
90
|
+
|
91
|
+
def container_id
|
92
|
+
@container_id ||= options[:container]
|
93
|
+
end
|
94
|
+
|
95
|
+
def styles
|
96
|
+
container_height ? "style='height:#{container_height}'" : ''
|
97
|
+
end
|
98
|
+
|
99
|
+
def container_height
|
100
|
+
options[:container_height]
|
101
|
+
end
|
102
|
+
|
103
|
+
def charting_object
|
104
|
+
options[:charting_object]
|
105
|
+
end
|
106
|
+
|
107
|
+
def generate_container_name
|
108
|
+
"chart_" + ActiveSupport::SecureRandom.hex(3)
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Charting
|
2
|
+
module Highcharts
|
3
|
+
class Area < Charting::Highcharts::Base
|
4
|
+
|
5
|
+
# Generate categories from ActiveRecord data
|
6
|
+
def categories
|
7
|
+
data_source.inject([]) do |categories, row|
|
8
|
+
categories << row.format_column(category_column).try(:strip_tags).try(:strip)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Generate data series array (for each data column)
|
13
|
+
def series
|
14
|
+
data_columns.inject([]) do |series, column|
|
15
|
+
series_data = data_source.inject([]) do |series_data, row|
|
16
|
+
series_data << row[column].to_i
|
17
|
+
end
|
18
|
+
series << {:name => series_name(column), :data => series_data}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module Charting
|
2
|
+
module Highcharts
|
3
|
+
# Base class for chart renderers. Chart renderers should
|
4
|
+
# subclass rather than instantiate Base directly.
|
5
|
+
class Base
|
6
|
+
attr_reader :data_source, :category_column, :data_columns, :options
|
7
|
+
|
8
|
+
WEEKEND = [0,6] # The days of the week that are the weekend (Sun, Sat)
|
9
|
+
AXIS_START_UNIT = 0.5 # Where highcharts starts its x-axis in axis units
|
10
|
+
MAX_X_LABELS = 10 # Display no more than this number of category labels
|
11
|
+
DEFAULT_OPTIONS = {
|
12
|
+
|
13
|
+
}
|
14
|
+
|
15
|
+
# Initialize a new chart.
|
16
|
+
#
|
17
|
+
# ====Parameters
|
18
|
+
#
|
19
|
+
# data_source: Array of ActiveRecord objects for rendering as a chart
|
20
|
+
# category_column: The name of the column which is the category (X) axis
|
21
|
+
# data_columns: The name (or array of names) of the columns which are
|
22
|
+
# the data series.
|
23
|
+
# options: Options hash
|
24
|
+
#
|
25
|
+
# ====Options
|
26
|
+
#
|
27
|
+
# title: Chart title (default: none)
|
28
|
+
# subtitle: Subtitle (default: none)
|
29
|
+
# x_axis_title: X-Axis title (default: none)
|
30
|
+
# y_axis_title: Y-Axis title (defualt: none)
|
31
|
+
# x_step: X-Axis labels are printed every 'x' steps
|
32
|
+
# Requires Highcharts beta 2.1
|
33
|
+
# x_plot_bands: Plot bands in Ruby Hash format according to the
|
34
|
+
# Highcharts documented format
|
35
|
+
# linearize: Linearize the data to ensure continuous
|
36
|
+
# series on the X-Axis.
|
37
|
+
# weekend_plot_bands: Creates plot-bands for the weekends (Saturday/Sunday)
|
38
|
+
# container: DOM id for the container div. Default is to generate one.
|
39
|
+
|
40
|
+
def initialize(data_source, category_column, data_columns, options = {})
|
41
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
42
|
+
@category_column = category_column
|
43
|
+
@data_columns = data_columns
|
44
|
+
@data_source = data_source
|
45
|
+
@data_source = linearize if @options.delete(:linearize)
|
46
|
+
@options[:x_step] ||= (@data_source.size.to_f / MAX_X_LABELS.to_f).round
|
47
|
+
end
|
48
|
+
|
49
|
+
def chart_options
|
50
|
+
{
|
51
|
+
:title => options[:title],
|
52
|
+
:subtitle => options[:subtitle],
|
53
|
+
:x_axis => options[:x_axis_title],
|
54
|
+
:y_axis => options[:y_axis_title],
|
55
|
+
:x_step => options[:x_step],
|
56
|
+
:x_plot_bands => weekend_plot_bands
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
# Define in concrete subclass
|
61
|
+
def series
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
|
65
|
+
# Define in concrete subclass
|
66
|
+
def categories
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns a plotbands definition for the data source
|
71
|
+
# Only if the category column is a date or datetime
|
72
|
+
def weekend_plot_bands
|
73
|
+
return unless options[:weekend_plot_bands] && data_source.first[category_column].respond_to?(:to_date)
|
74
|
+
x_plot_bands = data_source.inject_with_index([]) do |plot_bands, row, index|
|
75
|
+
if WEEKEND.include?(row[category_column].to_date.wday)
|
76
|
+
plot_bands << {:from => (index + AXIS_START_UNIT - 1), :to => (index + AXIS_START_UNIT)}
|
77
|
+
end
|
78
|
+
plot_bands
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def series_name(column)
|
83
|
+
data_source.first.class.human_attribute_name(column)
|
84
|
+
end
|
85
|
+
|
86
|
+
def chart_type
|
87
|
+
@chart_type ||= self.class.name.split('::').last.downcase
|
88
|
+
end
|
89
|
+
|
90
|
+
def container
|
91
|
+
options[:container]
|
92
|
+
end
|
93
|
+
|
94
|
+
def to_js
|
95
|
+
<<-EOF
|
96
|
+
chart.#{chart_type}('#{container}',
|
97
|
+
#{categories.to_json},
|
98
|
+
#{series.to_json},
|
99
|
+
#{chart_options.to_json}
|
100
|
+
);
|
101
|
+
EOF
|
102
|
+
end
|
103
|
+
|
104
|
+
# When we retrieve datat from the data base it may have gaps where not
|
105
|
+
# results are available. For charting we want to have a linearized
|
106
|
+
# series of data so here we plug the gaps.
|
107
|
+
#
|
108
|
+
# Punt that the categeory column tells us enough to know what the data
|
109
|
+
# should be.
|
110
|
+
def linearize
|
111
|
+
return data_source unless range = Charting::Period.range_from(options)
|
112
|
+
range = data_source.first[category_column]..range.last if range.first > data_source.first[category_column]
|
113
|
+
klass = data_source.first.class
|
114
|
+
index = 0
|
115
|
+
range.inject(Array.new) do |linear_data, data_point|
|
116
|
+
if data_source[index] && data_source[index][category_column] == data_point
|
117
|
+
linear_data << data_source[index]
|
118
|
+
index += 1
|
119
|
+
else
|
120
|
+
new_row = klass.new(category_column => data_point)
|
121
|
+
data_columns.each {|column| new_row[column] = 0 }
|
122
|
+
yield new_row if block_given?
|
123
|
+
linear_data << new_row
|
124
|
+
end
|
125
|
+
linear_data
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Charting
|
2
|
+
module Highcharts
|
3
|
+
class Pie < Charting::Highcharts::Base
|
4
|
+
|
5
|
+
# Generate data series array (for each data column)
|
6
|
+
def series
|
7
|
+
data_columns.inject([]) do |series, column|
|
8
|
+
series_data = data_source.inject([]) do |series_data, row|
|
9
|
+
series_data << [row.format_column(category_column).strip_tags.strip, row[column].to_i]
|
10
|
+
end
|
11
|
+
series << {:type => chart_type, :name => series_name(column), :data => series_data}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# No categories for pie charts
|
16
|
+
def categories
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# Parse parameters (usually from a request via ActionController) into
|
2
|
+
# chain of named scopes
|
3
|
+
#
|
4
|
+
# Format of query parameters:
|
5
|
+
# => by=dim1,dim2,dim3 dimensions
|
6
|
+
# => metric=metric1 a metric (only one at this time)
|
7
|
+
# => from=
|
8
|
+
# => to=
|
9
|
+
|
10
|
+
module Charting
|
11
|
+
class Period
|
12
|
+
def from_params(params = {})
|
13
|
+
default_from = (today - 30.days).beginning_of_day
|
14
|
+
default_to = today.end_of_day
|
15
|
+
if params[:period]
|
16
|
+
from, to = from_param(params[:period], default_from..default_to)
|
17
|
+
else
|
18
|
+
to = date_from_param(params[:to], default_to)
|
19
|
+
from = date_from_param(params[:from], default_from)
|
20
|
+
end
|
21
|
+
from..to
|
22
|
+
end
|
23
|
+
|
24
|
+
def date_from_param(date, default)
|
25
|
+
return default unless date
|
26
|
+
return date.to_date if date.is_a?(Date) || date.is_a?(Time)
|
27
|
+
Time.parse(date) rescue default
|
28
|
+
end
|
29
|
+
|
30
|
+
def from_param(period, default)
|
31
|
+
return default.first, default.last unless period
|
32
|
+
period_range = case period.to_sym
|
33
|
+
when :today then today.beginning_of_day..today.end_of_day
|
34
|
+
when :yesterday then yesterday.beginning_of_day..yesterday.end_of_day
|
35
|
+
when :this_week then first_day_of_this_week.beginning_of_day..last_day_of_this_week.end_of_day
|
36
|
+
when :this_month then first_day_of_this_month.beginning_of_day..last_day_of_this_month.end_of_day
|
37
|
+
when :this_year then first_day_of_this_year.beginning_of_day..last_day_of_this_year.end_of_day
|
38
|
+
when :last_week then first_day_of_last_week.beginning_of_day..last_day_of_last_week.end_of_day
|
39
|
+
when :last_month then first_day_of_last_month.beginning_of_day..last_day_of_last_month.end_of_day
|
40
|
+
when :last_year then first_day_of_last_year.beginning_of_day..last_day_of_last_year.end_of_day
|
41
|
+
when :last_30_days then (today - 30.days).beginning_of_day..today.end_of_day
|
42
|
+
when :last_12_months then (today - 12.months).beginning_of_day..today.end_of_day
|
43
|
+
when :lifetime then beginning_of_epoch.beginning_of_day..today.end_of_day;
|
44
|
+
else default
|
45
|
+
end
|
46
|
+
return period_range.first, period_range.last
|
47
|
+
end
|
48
|
+
|
49
|
+
def range_from(params)
|
50
|
+
period = self.from_params(params)
|
51
|
+
case params[:time_group]
|
52
|
+
when 'date'
|
53
|
+
period.first.to_date..period.last.to_date
|
54
|
+
when 'day_of_week'
|
55
|
+
0..6
|
56
|
+
when 'day_of_month'
|
57
|
+
1..31
|
58
|
+
when 'hour'
|
59
|
+
0..24
|
60
|
+
when 'month'
|
61
|
+
1..12
|
62
|
+
when 'year'
|
63
|
+
period.first.to_date.year..period.last.to_date.year
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Basic markers
|
68
|
+
def today
|
69
|
+
Time.zone.now.to_date
|
70
|
+
end
|
71
|
+
|
72
|
+
def yesterday
|
73
|
+
today - 1.day
|
74
|
+
end
|
75
|
+
|
76
|
+
def tomorrow
|
77
|
+
today + 1.day
|
78
|
+
end
|
79
|
+
|
80
|
+
def beginning_of_epoch
|
81
|
+
today - 20.years
|
82
|
+
end
|
83
|
+
|
84
|
+
# This week
|
85
|
+
def first_day_of_this_week
|
86
|
+
today - today.wday.days
|
87
|
+
end
|
88
|
+
|
89
|
+
def last_day_of_this_week
|
90
|
+
first_day_of_this_week + 7.days
|
91
|
+
end
|
92
|
+
|
93
|
+
# This month
|
94
|
+
def first_day_of_this_month
|
95
|
+
Date.new(today.year, today.month, 1)
|
96
|
+
end
|
97
|
+
|
98
|
+
def last_day_of_this_month
|
99
|
+
first_day_of_this_month + 1.month - 1.day
|
100
|
+
end
|
101
|
+
|
102
|
+
# Last week
|
103
|
+
def first_day_of_last_week
|
104
|
+
first_day_of_this_week - 7.days
|
105
|
+
end
|
106
|
+
|
107
|
+
def last_day_of_last_week
|
108
|
+
first_day_of_last_week + 7.days
|
109
|
+
end
|
110
|
+
|
111
|
+
# Last month
|
112
|
+
def first_day_of_last_month
|
113
|
+
last_month = Date.today - 1.month
|
114
|
+
Date.new(last_month.year, last_month.month, 1)
|
115
|
+
end
|
116
|
+
|
117
|
+
def last_day_of_last_month
|
118
|
+
first_day_of_last_month + 1.month - 1.day
|
119
|
+
end
|
120
|
+
|
121
|
+
# This year
|
122
|
+
def first_day_of_this_year
|
123
|
+
Date.new(today.year, 1, 1)
|
124
|
+
end
|
125
|
+
|
126
|
+
def last_day_of_this_year
|
127
|
+
Date.new(today.year, 12, 31)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Last year
|
131
|
+
def first_day_of_last_year
|
132
|
+
Date.new(today.year - 1, 1, 1)
|
133
|
+
end
|
134
|
+
|
135
|
+
def last_day_of_last_year
|
136
|
+
Date.new(first_day_of_last_year.year, 12, 31)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Clear the saved instance which we should do at the start
|
140
|
+
# of each Rails request
|
141
|
+
def self.clear
|
142
|
+
Thread.current[:period2] = nil
|
143
|
+
end
|
144
|
+
|
145
|
+
# Called when we're configured as a before_filter which we need to
|
146
|
+
# ensure our thread data is ours and no left over from somebody else
|
147
|
+
def self.filter(controller)
|
148
|
+
clear
|
149
|
+
end
|
150
|
+
|
151
|
+
# Arrange for instance methods to be called as if class methods. Make threadsafe.
|
152
|
+
def self.method_missing(method, *args)
|
153
|
+
Thread.current[:period2] = new unless Thread.current[:period2]
|
154
|
+
self.instance_methods.include?(method.to_s) ? Thread.current[:period2].send(method, *args) : super
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Charting
|
2
|
+
module Sparklines
|
3
|
+
class Renderer
|
4
|
+
DEFAULT_OPTIONS = {
|
5
|
+
:type => :line,
|
6
|
+
}
|
7
|
+
|
8
|
+
attr_accessor :series, :options, :chart
|
9
|
+
|
10
|
+
# Generate jQuery sparkline based charts.
|
11
|
+
#
|
12
|
+
# Ensure jquery-sparkine library is loaded before use, for example:
|
13
|
+
#
|
14
|
+
# <script src="/javascripts/jquery.sparkline.js" type="text/javascript"></script>
|
15
|
+
#
|
16
|
+
# And that you invoke somethink like:
|
17
|
+
#
|
18
|
+
# /* Initialize sparklines */
|
19
|
+
# $(document).ready(function() {
|
20
|
+
# $('.sparkline').sparkline();
|
21
|
+
# });
|
22
|
+
#
|
23
|
+
# in your document script.
|
24
|
+
#
|
25
|
+
# Styling in CSS is required for the <span> container which
|
26
|
+
# has a class of 'sparkline'
|
27
|
+
#
|
28
|
+
# ====Parameters
|
29
|
+
#
|
30
|
+
# data_source: The active record result set
|
31
|
+
# data_column: one column name to be charted
|
32
|
+
# options: options hash
|
33
|
+
#
|
34
|
+
# ====Options
|
35
|
+
#
|
36
|
+
# Currently no options for Sparklines.
|
37
|
+
def initialize(data_source, data_column, options = {})
|
38
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
39
|
+
@options[:container] ||= generate_container_name
|
40
|
+
@data_column = data_column
|
41
|
+
@chart = chart_class.new(data_source, @data_column, @options)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Output the HTML representation of the chart
|
45
|
+
def to_html
|
46
|
+
@chart.to_html
|
47
|
+
end
|
48
|
+
|
49
|
+
# Convenience method for configuring the chart.
|
50
|
+
def self.configure
|
51
|
+
yield self
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
def chart_class
|
56
|
+
@chart_class ||= "Charting::Sparklines::#{options[:type].to_s.titleize}".constantize
|
57
|
+
end
|
58
|
+
|
59
|
+
def generate_container_name
|
60
|
+
"sparkline_" + ActiveSupport::SecureRandom.hex(3)
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Charting
|
2
|
+
module Sparklines
|
3
|
+
class Base
|
4
|
+
attr_reader :data_source, :data_column, :options
|
5
|
+
|
6
|
+
DEFAULT_OPTIONS = {}
|
7
|
+
|
8
|
+
def initialize(data_source, data_column, options = {})
|
9
|
+
@data_source = data_source
|
10
|
+
@data_column = data_column
|
11
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
12
|
+
end
|
13
|
+
|
14
|
+
# No options currently defined
|
15
|
+
def chart_options
|
16
|
+
{}
|
17
|
+
end
|
18
|
+
|
19
|
+
# Define in concrete subclass
|
20
|
+
def series
|
21
|
+
[]
|
22
|
+
end
|
23
|
+
|
24
|
+
# The name of a data series
|
25
|
+
def series_name
|
26
|
+
data_source.first.class.human_attribute_name(data_column)
|
27
|
+
end
|
28
|
+
|
29
|
+
# The chart type (derived from the class name)
|
30
|
+
def chart_type
|
31
|
+
@chart_type ||= self.class.name.split('::').last.downcase
|
32
|
+
end
|
33
|
+
|
34
|
+
# Render the chart HTML. Requires jQuery and jQuery sparklines plugin
|
35
|
+
def to_html
|
36
|
+
<<-EOF
|
37
|
+
<span id="#{container_id}" class="#{chart_css_class}">
|
38
|
+
#{series.join(',')}
|
39
|
+
</span>
|
40
|
+
EOF
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def chart_css_class
|
45
|
+
"spark#{chart_type}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def container_id
|
49
|
+
@container_id ||= options[:container]
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Adds method to ActiveRecord to support some chart
|
2
|
+
# transformations
|
3
|
+
module Charting
|
4
|
+
module Transforms
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
extend ClassMethods
|
8
|
+
include InstanceMethods
|
9
|
+
const_set(:DEFAULT_PIVOT_COLUMNS, {
|
10
|
+
:attribute => :attribute, :value => :value
|
11
|
+
})
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module InstanceMethods
|
16
|
+
# Takes one row and returns an array of objects of the same class
|
17
|
+
# where columns are pivoted to become rows. The new rows are of the
|
18
|
+
# same class as the current instance.
|
19
|
+
#
|
20
|
+
# ====Arguments
|
21
|
+
#
|
22
|
+
# The columns that become the row/column pairs in the result array.
|
23
|
+
# An empty list means pivot all attributes.
|
24
|
+
#
|
25
|
+
# ====Options
|
26
|
+
#
|
27
|
+
# Specify the optional column names for the result array. The defaults
|
28
|
+
# are:
|
29
|
+
#
|
30
|
+
# :category => :category
|
31
|
+
# :value => value
|
32
|
+
def pivot(*args)
|
33
|
+
options = (args.last.is_a?(Hash) ? args.pop : {}).merge(self.class.const_get(:DEFAULT_PIVOT_COLUMNS))
|
34
|
+
args.flatten!
|
35
|
+
attributes = args.size > 0 ? args : self.attributes.map(&:first)
|
36
|
+
klass = self.class
|
37
|
+
return attributes.inject([]) do |rows, arg|
|
38
|
+
row = klass.new
|
39
|
+
row[options[:attribute]] = arg.to_s
|
40
|
+
row[options[:value]] = self[arg].to_i
|
41
|
+
rows << row
|
42
|
+
rows
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
module ClassMethods
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/ar_to_chart/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ar_to_chart
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Kip Cole
|
@@ -15,11 +15,11 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-11-02 00:00:00 +08:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|
22
|
-
description: " Defines Array#to_chart that will accept ActiveRecord result sets\n and render them as a chart. Currently assumes
|
22
|
+
description: " Defines Array#to_chart that will accept ActiveRecord result sets\n and render them as a chart. Currently assumes Highcharts (highcharts.com)\n as the charting engine.\n"
|
23
23
|
email:
|
24
24
|
- kipcole9@gmail.com
|
25
25
|
executables: []
|
@@ -30,11 +30,30 @@ extra_rdoc_files: []
|
|
30
30
|
|
31
31
|
files:
|
32
32
|
- .gitignore
|
33
|
+
- CHANGELOG
|
33
34
|
- Gemfile
|
35
|
+
- MIT-LICENSE
|
36
|
+
- README.textile
|
34
37
|
- Rakefile
|
35
38
|
- ar_to_chart.gemspec
|
39
|
+
- files/ar_to_chart.css
|
40
|
+
- files/ar_to_chart.js
|
36
41
|
- lib/ar_to_chart.rb
|
42
|
+
- lib/ar_to_chart/.DS_Store
|
43
|
+
- lib/ar_to_chart/active_record_array.rb
|
44
|
+
- lib/ar_to_chart/charting/highcharts.rb
|
45
|
+
- lib/ar_to_chart/charting/highcharts/area.rb
|
46
|
+
- lib/ar_to_chart/charting/highcharts/base.rb
|
47
|
+
- lib/ar_to_chart/charting/highcharts/funnel.rb
|
48
|
+
- lib/ar_to_chart/charting/highcharts/pie.rb
|
49
|
+
- lib/ar_to_chart/charting/period.rb
|
50
|
+
- lib/ar_to_chart/charting/sparklines.rb
|
51
|
+
- lib/ar_to_chart/charting/sparklines/base.rb
|
52
|
+
- lib/ar_to_chart/charting/sparklines/line.rb
|
53
|
+
- lib/ar_to_chart/charting/transforms.rb
|
37
54
|
- lib/ar_to_chart/version.rb
|
55
|
+
- lib/locale/to_chart-en.yml
|
56
|
+
- lib/tasks/at_to_chart_tasks.rake
|
38
57
|
has_rdoc: true
|
39
58
|
homepage: http://rubygems.org/gems/ar_to_chart
|
40
59
|
licenses: []
|