graphene 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2012 Jordan Hollinger
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/README.rdoc ADDED
@@ -0,0 +1,101 @@
1
+ = Graphene
2
+
3
+ Graphene is a library for transforming collections of Ruby objects into subtotals, percentages, tables and graphs.
4
+ It's most useful feature is probably the Gruff helpers. True, some types of Gruff charts are pretty simple to begin
5
+ with, but others can require tedious boilerplate code to calculate percentages, well-spaced labels, and more.
6
+ Graphene hides all of that, accepting an array of objects and transforming them into a graph.
7
+
8
+ Read the full documentation at http://jordanhollinger.com/docs/graphene/.
9
+
10
+ Nobody likes you, Ruby 1.8. Now go away.
11
+
12
+ == Installation
13
+
14
+ $ [sudo] gem install graphene
15
+ # Or just add "graphene" to your Gemfile
16
+
17
+ Gruff is required only by Graphene's graphing component. Because of this, and because Gruff's dependencies can be difficult to install,
18
+ you will need to install Gruff manually. If you do not/cannot, Graphene's other components will continue to function.
19
+
20
+ # On Debian/Ubuntu
21
+ $ sudo apt-get install librmagick-ruby libmagickcore-dev libmagickwand-dev
22
+
23
+ $ [sudo] gem install rmagick gruff
24
+ # Or just add "rmagick" and "gruff" to your Gemfile
25
+
26
+ See http://nubyonrails.com/pages/gruff/ for more on Gruff.
27
+
28
+ == Percentages
29
+
30
+ # An array of objects which respond to methods like :browser, :platform, :date
31
+ logs = SomeLogParser.parse('/var/log/nginx/access.log.*')
32
+
33
+ percentages = Graphene.percentages(logs, :browser)
34
+
35
+ puts percentages.to_a
36
+ => [["Firefox", 40.0], ["Chrome", 35.0], ["Internet Explorer", 25.0]]
37
+
38
+ percentages.each do |browser, count|
39
+ puts "There were #{count} hits from #{browser}"
40
+ end
41
+
42
+ # You can also calculate by multiple criteria, and may use lambdas instead of symbols
43
+ percentages = Graphene.percentages(logs, ->(l) { l.browser.downcase }, :platform)
44
+
45
+ puts percentages.to_a
46
+ => [["chrome", "OS X", 40], ["internet explorer", "Windows", 25], ["firefox", "Windows", 25], ["firefox", "GNU/Linux", 8], ["chrome", "GNU/Linux", 2]]
47
+
48
+ See Graphene.percentages for more info.
49
+
50
+ == Subtotals
51
+
52
+ Same as percentages above, except that subtotals are returned instead. See Graphene.subtotals for more info.
53
+
54
+ == Tablizer
55
+
56
+ Integration with the tablizer gem provides quick ASCII tables.
57
+
58
+ puts percentages.tablize
59
+ => +-----------------+------------+----------+
60
+ | Browser | Platform |Percentage|
61
+ +-----------------+------------+----------+
62
+ |Firefox |Windows |50.0 |
63
+ |Internet Explorer|Windows |20.0 |
64
+ |Safari |OS X |20.0 |
65
+ |Firefox |GNU/Linux |10.0 |
66
+ +-----------------+------------+----------+
67
+
68
+ == Graphs
69
+
70
+ Provides helpers for generating Gruff graphs. Requires the "gruff" Ruby gem.
71
+
72
+ # A pie chart of Firefox version shares
73
+ ff = logs.select { |e| e.browser == 'Firefox' }
74
+ Graphene.percentages(ff, :browser).pie_chart('/path/to/graph.png', 'FF Version Share')
75
+
76
+ # A line graph of daily browser numbers over time, tricked out with lots of options
77
+ Graphene.subtotals(logs, :browser).over(:date).line_graph('/path/to/graph.png') do |chart, labeler|
78
+ chart.title = 'Browser Share'
79
+ chart.font = '/path/to/awesome/font.ttf'
80
+ chart.theme = {
81
+ :colors => %w(orange purple green white red),
82
+ :marker_color => 'blue',
83
+ :background_colors => %w(black grey)
84
+ }
85
+
86
+ # Only show every 7th label, and make dates pretty
87
+ labeler.call(7) do |date|
88
+ date.strftime('%b %e')
89
+ end
90
+ end
91
+
92
+ See Graphene::OneDGraphs and Graphene::TwoDGraphs for more types and examples. See http://gruff.rubyforge.org/classes/Gruff/Base.html for more Gruff options.
93
+
94
+ == TODO
95
+
96
+ Graphene::OverX and the graph helpers needs the ability to fill in empty points
97
+
98
+ == License
99
+ Copyright 2012 Jordan Hollinger
100
+
101
+ Licensed under the Apache License
@@ -0,0 +1,55 @@
1
+ module Graphene
2
+ # Class for Graphene Exceptions
3
+ class GrapheneException < StandardError; end
4
+
5
+ # For the given resources, returns the share of the count that each attr(s) has.
6
+ #
7
+ # "resources" is an array of objects which responds to the "args" method(s).
8
+ #
9
+ # "args" is one or more method symbols or proc/lambda which each object in "resources" responds to.
10
+ # Subtotals will be calculated from the returned values.
11
+ #
12
+ # Returns an instance of Graphene::Subtotals, which implements Enumerable. Each member is
13
+ # an array of [attribute(s), count]
14
+ #
15
+ # Example, Browser Family share:
16
+ #
17
+ # Graphene.subtotals(logins, :browser_family).to_a
18
+ # => [['Firefox', 5040], ['Chrome', 1960], ['Internet Explorer', 1500], ['Safari', 1000], ['Unknown', 500]]
19
+ #
20
+ # Example, Browser/OS share, asking for symbols back:
21
+ #
22
+ # Graphene.subtotals(server_log_entries, :browser_sym, :os_sym).to_a
23
+ # => [[:firefox, :windows_7, 50.4, 5040], [:chrome, :osx, 19.6, 1960], [:msie, :windows_xp, 15, 1500], [:safari, :osx, 10, 1000], [:other, :other, 5, 100]]
24
+ #
25
+ def self.subtotals(resources, *args)
26
+ Subtotals.new(resources, *args)
27
+ end
28
+
29
+ # For the given "resources", returns the % share of the group that each attr(s) has.
30
+ #
31
+ # "resources" is an array of objects which responds to the "args" method(s).
32
+ #
33
+ # "args" is one or more method symbols or proc/lambda which each object in "resources" responds to.
34
+ # Percentages will be calculated from the returned values.
35
+ #
36
+ # "args" may have, as it's last member, :threshold => n, where n is the number of the lowest
37
+ # percentage you want returned.
38
+ #
39
+ # Returns an instance of Graphene::Percentages, which implements Enumerable. Each member is
40
+ # an array of [attribute(s), percentage]
41
+ #
42
+ # Example, Browser Family share:
43
+ #
44
+ # Graphene.percentages(logins, :browser_family).to_a
45
+ # => [['Firefox', 50.4], ['Chrome', 19.6], ['Internet Explorer', 15], ['Safari', 10], ['Unknown', 5]]
46
+ #
47
+ # Example, Browser/OS share, asking for symbols back:
48
+ #
49
+ # Graphene.percentages(server_log_entries, :browser_sym, :os_sym).to_a
50
+ # => [[:firefox, :windows_7, 50.4], [:chrome, :osx, 19.6], [:msie, :windows_xp, 15], [:safari, :osx, 10], [:other, :other, 5]]
51
+ #
52
+ def self.percentages(resources, *args)
53
+ Percentages.new(resources, *args)
54
+ end
55
+ end
@@ -0,0 +1,360 @@
1
+ module Graphene
2
+ # Executes the given block if Gruff is available. Raises a GrapheneException if not.
3
+ def self.gruff
4
+ if defined? Gruff
5
+ yield if block_given?
6
+ else
7
+ raise GrapheneException, "Gruff integration is disabled because Gruff could not be loaded; install the \"gruff\" gem"
8
+ end
9
+ end
10
+
11
+ # Extends calculators with one-dimensional graphs, like pie charts.
12
+ module OneDGraphs
13
+ # Returns a Gruff::Pie object with the stats set.
14
+ #
15
+ # Optionally you may pass a file path and graph title. If you pass a file path, the graph will
16
+ # be written to file automatically. Otherwise, you would call "write('/path/to/graph.png')" on the
17
+ # returned graph object.
18
+ #
19
+ # If you pass a block, it will be called, giving you access to the Gruff::Pie object before it is
20
+ # written to file (that is, if you also passed a file path).
21
+ #
22
+ # Example 1:
23
+ #
24
+ # Graphene.percentages(logs, :browser).pie_chart('/path/to/browser-share.png', 'Browser Share')
25
+ #
26
+ # Example 2:
27
+ #
28
+ # Graphene.percentages(logs, :browser).pie_chart('/path/to/browser-share.png') do |pie|
29
+ # pie.title = 'Browser Share'
30
+ # pie.font = '/path/to/font.ttf'
31
+ # pie.theme = pie.theme_37signals
32
+ # end
33
+ #
34
+ # Example 3:
35
+ #
36
+ # blog = Graphene.percentages(logs, :browser).pie_chart.to_blob
37
+ #
38
+ def pie_chart(path=nil, title=nil, &block)
39
+ Graphene.gruff do
40
+ chart(Gruff::Pie.new, path, title, &block)
41
+ end
42
+ end
43
+ alias_method :pie_graph, :pie_chart
44
+
45
+ # Returns a Gruff::Bar object with the stats set.
46
+ #
47
+ # Optionally you may pass a file path and chart title. If you pass a file path, the chart will
48
+ # be written to file automatically. Otherwise, you would call "write('/path/to/graph.png')" on the
49
+ # returned chart object.
50
+ #
51
+ # If you pass a block, it will be called, giving you access to the Gruff::Bar object before it is
52
+ # written to file (that is, if you also passed a file path).
53
+ #
54
+ # Example 1:
55
+ #
56
+ # Graphene.percentages(logs, :browser).bar_chart('/path/to/browser-share.png', 'Browser Share')
57
+ #
58
+ # Example 2:
59
+ #
60
+ # Graphene.percentages(logs, :browser).bar_chart('/path/to/browser-share.png') do |chart|
61
+ # chart.title = 'Browser Share'
62
+ # chart.font = '/path/to/font.ttf'
63
+ # chart.theme = chart.theme_37signals
64
+ # end
65
+ #
66
+ # Example 3:
67
+ #
68
+ # blog = Graphene.subtotals(logs, :browser).bar_chart.to_blob
69
+ #
70
+ def bar_chart(path=nil, title=nil, &block)
71
+ Graphene.gruff do
72
+ chart(Gruff::Bar.new, path, title, false, &block)
73
+ end
74
+ end
75
+ alias_method :bar_graph, :bar_chart
76
+
77
+ # Returns a Gruff::StackedBar object with the stats set.
78
+ #
79
+ # Optionally you may pass a file path and chart title. If you pass a file path, the chart will
80
+ # be written to file automatically. Otherwise, you would call "write('/path/to/graph.png')" on the
81
+ # returned chart object.
82
+ #
83
+ # If you pass a block, it will be called, giving you access to the Gruff::StackedBar object before it is
84
+ # written to file (that is, if you also passed a file path).
85
+ #
86
+ # Example 1:
87
+ #
88
+ # Graphene.percentages(logs, :browser).stacked_bar_chart('/path/to/browser-share.png', 'Browser Share')
89
+ #
90
+ # Example 2:
91
+ #
92
+ # Graphene.percentages(logs, :browser).stacked_bar_chart('/path/to/browser-share.png') do |chart|
93
+ # chart.title = 'Browser Share'
94
+ # chart.font = '/path/to/font.ttf'
95
+ # chart.theme = chart.theme_37signals
96
+ # end
97
+ #
98
+ # Example 3:
99
+ #
100
+ # blog = Graphene.subtotals(logs, :browser).stacked_bar_chart.to_blob
101
+ #
102
+ def stacked_bar_chart(path=nil, title=nil, &block)
103
+ Graphene.gruff do
104
+ chart(Gruff::StackedBar.new, path, title, true, &block)
105
+ end
106
+ end
107
+ alias_method :stacked_bar_graph, :stacked_bar_chart
108
+
109
+ # Returns a Gruff::SideBar object with the stats set.
110
+ #
111
+ # Optionally you may pass a file path and chart title. If you pass a file path, the chart will
112
+ # be written to file automatically. Otherwise, you would call "write('/path/to/graph.png')" on the
113
+ # returned chart object.
114
+ #
115
+ # If you pass a block, it will be called, giving you access to the Gruff::SideBar object before it is
116
+ # written to file (that is, if you also passed a file path).
117
+ #
118
+ # Example 1:
119
+ #
120
+ # Graphene.percentages(logs, :browser).side_bar_chart('/path/to/browser-share.png', 'Browser Share')
121
+ #
122
+ # Example 2:
123
+ #
124
+ # Graphene.percentages(logs, :browser).side_bar_chart('/path/to/browser-share.png') do |chart|
125
+ # chart.title = 'Browser Share'
126
+ # chart.font = '/path/to/font.ttf'
127
+ # chart.theme = chart.theme_37signals
128
+ # end
129
+ #
130
+ # Example 3:
131
+ #
132
+ # blog = Graphene.subtotals(logs, :browser).side_bar_chart.to_blob
133
+ #
134
+ def side_bar_chart(path=nil, title=nil, &block)
135
+ Graphene.gruff do
136
+ chart(Gruff::SideBar.new, path, title, true, &block)
137
+ end
138
+ end
139
+ alias_method :side_bar_graph, :side_bar_chart
140
+
141
+ # Returns a Gruff::StackedSideBar object with the stats set.
142
+ #
143
+ # Optionally you may pass a file path and chart title. If you pass a file path, the chart will
144
+ # be written to file automatically. Otherwise, you would call "write('/path/to/graph.png')" on the
145
+ # returned chart object.
146
+ #
147
+ # If you pass a block, it will be called, giving you access to the Gruff::StackedSideBar object before it is
148
+ # written to file (that is, if you also passed a file path).
149
+ #
150
+ # Example 1:
151
+ #
152
+ # Graphene.percentages(logs, :browser).side_stacked_bar_chart('/path/to/browser-share.png', 'Browser Share')
153
+ #
154
+ # Example 2:
155
+ #
156
+ # Graphene.percentages(logs, :browser).side_stacked_bar_chart('/path/to/browser-share.png') do |chart|
157
+ # chart.title = 'Browser Share'
158
+ # chart.font = '/path/to/font.ttf'
159
+ # chart.theme = chart.theme_37signals
160
+ # end
161
+ #
162
+ # Example 3:
163
+ #
164
+ # blog = Graphene.subtotals(logs, :browser).side_stacked_bar_chart.to_blob
165
+ #
166
+ def side_stacked_bar_chart(path=nil, title=nil, &block)
167
+ Graphene.gruff do
168
+ chart(Gruff::SideStackedBar.new, path, title, true, &block)
169
+ end
170
+ end
171
+ alias_method :side_stacked_bar_graph, :side_stacked_bar_chart
172
+
173
+ # Returns a Gruff::Spider object with the stats set.
174
+ #
175
+ # Optionally you may pass a file path and chart title. If you pass a file path, the chart will
176
+ # be written to file automatically. Otherwise, you would call "write('/path/to/graph.png')" on the
177
+ # returned chart object.
178
+ #
179
+ # If you pass a block, it will be called, giving you access to the Gruff::Spider object before it is
180
+ # written to file (that is, if you also passed a file path).
181
+ #
182
+ # Example 1:
183
+ #
184
+ # Graphene.percentages(logs, :browser).spider_chart('/path/to/browser-share.png', 'Browser Share')
185
+ #
186
+ # Example 2:
187
+ #
188
+ # Graphene.percentages(logs, :browser).spider_chart('/path/to/browser-share.png') do |chart|
189
+ # chart.title = 'Browser Share'
190
+ # chart.font = '/path/to/font.ttf'
191
+ # chart.theme = chart.theme_37signals
192
+ # end
193
+ #
194
+ # Example 3:
195
+ #
196
+ # blog = Graphene.subtotals(logs, :browser).spider_chart.to_blob
197
+ #
198
+ def spider_chart(path=nil, title=nil, &block)
199
+ Graphene.gruff do
200
+ chart(Gruff::Spider.new(max_result), path, title, false, &block)
201
+ end
202
+ end
203
+ alias_method :spider_graph, :spider_chart
204
+
205
+ private
206
+
207
+ # Builds a chart
208
+ def chart(chart, path=nil, title=nil, hack=false, &block)
209
+ chart.title = title unless title.nil?
210
+ block.call(chart) if block
211
+
212
+ each do |result|
213
+ name = result[0..attributes.size-1].join(' / ')
214
+ n = result[attributes.size]
215
+ chart.data name, n
216
+ end
217
+ # XXX Required by SideBar and SideStackedBar. Probably a bug.
218
+ chart.labels = {0 => ' '} if hack
219
+
220
+ chart.write(path) unless path.nil?
221
+ chart
222
+ end
223
+ end
224
+
225
+ # Extends calculators with two-dimensional graphs, like line graphs.
226
+ module TwoDGraphs
227
+ # Returns a Gruff::Line object with the stats set.
228
+ #
229
+ # Optionally you may pass a file path and graph title. If you pass a file path, the graph will
230
+ # be written to file automatically. Otherwise, you would call "write('/path/to/graph.png')" on the
231
+ # returned graph object.
232
+ #
233
+ # If you pass a block, it will be called, giving you access to the Gruff::Line object before it is
234
+ # written to file (that is, if you also passed a file path). It will also give you access to a Proc
235
+ # for labeling the X axis.
236
+ #
237
+ # Example 1:
238
+ #
239
+ # Graphene.percentages(logs, :browser).over(:date).line_graph('/path/to/browser-share.png', 'Browser Share')
240
+ #
241
+ # Example 2:
242
+ #
243
+ # Graphene.subtotals(logs, :browser).over(:date).line_graph('/path/to/browser-share.png') do |chart, labeler|
244
+ # chart.title = 'Browser Share'
245
+ # chart.font = '/path/to/font.ttf'
246
+ # chart.theme = pie.theme_37signals
247
+ # end
248
+ #
249
+ # Example 3:
250
+ #
251
+ # Graphene.subtotals(logs, :browser).over(:date).line_graph('/path/to/browser-share.png') do |chart, labeler|
252
+ # chart.title = 'Browser Share'
253
+ #
254
+ # # Both the 10 and the block are optional.
255
+ # # - "10" means that only every 10'th label will be printed. Otherwise, each would be.
256
+ # # - The block is passed each label (the return value of the "over attribute") and may return a formatted version.
257
+ # labeler.call(10) do |date|
258
+ # date.strftime('%m/%d/%Y')
259
+ # end
260
+ # end
261
+ #
262
+ # Example 4:
263
+ #
264
+ # Graphene.percentages(logs, :platform, :browser).over(->(l) { l.date.strftime('%m/%Y') }).line_graph('/path/to/os-browser-share.png', 'OS / Browser Share by Month')
265
+ #
266
+ def line_graph(path=nil, title=nil, &block)
267
+ Graphene.gruff do
268
+ graph(Gruff::Line.new, path, title, &block)
269
+ end
270
+ end
271
+ alias_method :line_chart, :line_graph
272
+
273
+ # Returns a Gruff::Net object with the stats set.
274
+ #
275
+ # Optionally you may pass a file path and graph title. If you pass a file path, the graph will
276
+ # be written to file automatically. Otherwise, you would call "write('/path/to/graph.png')" on the
277
+ # returned graph object.
278
+ #
279
+ # If you pass a block, it will be called, giving you access to the Gruff::Net object before it is
280
+ # written to file (that is, if you also passed a file path). It will also give you access to a Proc
281
+ # for labeling the X axis.
282
+ #
283
+ # Example 1:
284
+ #
285
+ # Graphene.percentages(logs, :browser).over(:date).net_graph('/path/to/browser-share.png', 'Browser Share')
286
+ #
287
+ # Example 2:
288
+ #
289
+ # Graphene.subtotals(logs, :browser).over(:date).net_graph('/path/to/browser-share.png') do |chart, labeler|
290
+ # chart.title = 'Browser Share'
291
+ # chart.font = '/path/to/font.ttf'
292
+ # chart.theme = pie.theme_37signals
293
+ # end
294
+ #
295
+ # Example 3:
296
+ #
297
+ # Graphene.subtotals(logs, :browser).over(:date).net_graph('/path/to/browser-share.png') do |chart, labeler|
298
+ # chart.title = 'Browser Share'
299
+ #
300
+ # # Both the 10 and the block are optional.
301
+ # # - "10" means that only every 10'th label will be printed. Otherwise, each would be.
302
+ # # - The block is passed each label (the return value of the "over attribute") and may return a formatted version.
303
+ # labeler.call(10) do |date|
304
+ # date.strftime('%m/%d/%Y')
305
+ # end
306
+ # end
307
+ #
308
+ # Example 4:
309
+ #
310
+ # Graphene.percentages(logs, :platform, :browser).over(->(l) { l.date.strftime('%m/%Y') }).net_graph('/path/to/os-browser-share.png', 'OS / Browser Share by Month')
311
+ #
312
+ def net_graph(path=nil, title=nil, &block)
313
+ Graphene.gruff do
314
+ graph(Gruff::Net.new, path, title, &block)
315
+ end
316
+ end
317
+ alias_method :net_chart, :net_graph
318
+
319
+ private
320
+
321
+ # Builds a graph
322
+ def graph(graph, path=nil, title=nil, &block)
323
+ graph.title = title unless title.nil?
324
+
325
+ # Create an empty array for each group (e.g. {["Firefox"] => [], ["Safari"] => []}), even if it's empty.
326
+ data = inject({}) do |dat, (label, rows)|
327
+ for row in rows
328
+ attrs = row[0..-2]
329
+ dat[attrs] ||= []
330
+ end
331
+ dat
332
+ end
333
+
334
+ # Group the data on the x axis
335
+ to_a.each do |x_attr, rows|
336
+ groups = rows.group_by { |row| row[0..-2] }
337
+ for attrs, dat in data
338
+ dat << (groups[attrs] ? groups[attrs].last.last : 0)
339
+ end
340
+ end
341
+
342
+ # Build the labeling proc
343
+ label_every_n, labeler = 1, :to_s.to_proc
344
+ get_labeler = proc do |n=1, &block|
345
+ label_every_n = n
346
+ labeler = block if block
347
+ end
348
+ yield(graph, get_labeler) if block_given?
349
+ # Build labels and add them to graph
350
+ labels = @results.keys
351
+ graph.labels = Hash[*labels.select { |x| labels.index(x) % label_every_n == 0 }.map { |x| [*labels.index(x), labeler[x]] }.flatten]
352
+
353
+ # Add data to the graph
354
+ data.each { |attrs, dat| graph.data(attrs.join(' / '), dat) }
355
+
356
+ graph.write(path) unless path.nil?
357
+ graph
358
+ end
359
+ end
360
+ end
File without changes
@@ -0,0 +1,33 @@
1
+ module Graphene
2
+ # Includes Enumerable and alculates @results lazily. An enumerate! method must be implemented.
3
+ module LazyEnumerable
4
+ def self.included(base) # :nodoc:
5
+ base.send(:include, Enumerable)
6
+ end
7
+
8
+ # Implements the "each" method required by Enumerable.
9
+ def each(&block)
10
+ lazily_enumerate!
11
+ @results.each &block
12
+ end
13
+
14
+ # Tests equality between this and another set
15
+ def ==(other)
16
+ lazily_enumerate!
17
+ to_a == other.to_a
18
+ end
19
+
20
+ # Tests equality between this and another set
21
+ def ===(other)
22
+ lazily_enumerate!
23
+ to_a === other.to_a
24
+ end
25
+
26
+ private
27
+
28
+ # Run the calculation if it hasn't already been
29
+ def lazily_enumerate!
30
+ enumerate! if @results.empty?
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,49 @@
1
+ module Graphene
2
+ # Groups the stats of resources by the given method symbol or lambda.
3
+ #
4
+ # Example by date
5
+ #
6
+ # Graphene.percentages(logs, :browser).over(:date)
7
+ # => {#<Date: 2012-07-22> => [["Firefox", 45], ["Chrome", 40], ["Internet Explorer", 15]],
8
+ # #<Date: 2012-07-23> => [["Firefox", 41], ["Chrome", 40], ["Internet Explorer", 19]],
9
+ # #<Date: 2012-07-24> => [["Chrome", 50], ["Firefox", 40], ["Internet Explorer", 10]]}
10
+ #
11
+ # See Graphene::LazyEnumerable, Graphene::Tablize and Graphene::TwoDGraphs for more documentation.
12
+ class OverX
13
+ include LazyEnumerable
14
+ include TwoDGraphs
15
+
16
+ # The attribute that are being statted, passed in the constructor
17
+ attr_reader :result_set
18
+
19
+ # Accepts a ResultSet object (i.e. Graphene::Subtotals or Graphene::Percentages), and a
20
+ # method symbol or proc/lambda to build the X axis
21
+ def initialize(result_set, attr_or_lambda)
22
+ @result_set = result_set
23
+ @attribute = attr_or_lambda
24
+ @results = {}
25
+ end
26
+
27
+ # Returns a string representation of the results
28
+ def to_s
29
+ to_hash.to_s
30
+ end
31
+
32
+ # Returns a Hash representation of the results
33
+ def to_hash
34
+ @results.clone
35
+ end
36
+
37
+ private
38
+
39
+ # Run the calculation
40
+ def enumerate!
41
+ resources_by_x = result_set.resources.group_by(&@attribute).sort_by(&:first)
42
+ @results = resources_by_x.inject({}) do |results, (x, group)|
43
+ results[x] ||= []
44
+ results[x] += result_set.class.new(group, *result_set.attributes).to_a
45
+ results
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,36 @@
1
+ require 'graphene/subtotals'
2
+
3
+ module Graphene
4
+ # Calculates and contains the percent subtotals of each attr (or attr group). Inherits from Graphene::ResultSet.
5
+ # See Graphene::LazyEnumerable, Graphene::Tablize and Graphene::OneDGraphs for more documentation.
6
+ #
7
+ # If you passed an options Hash containing :threshold to the constructor,
8
+ # any results falling below it will be excluded.
9
+ #
10
+ # Don't create instance manually. Instead, use the Graphene.percentages method, which will return
11
+ # a properly instantiated object.
12
+ class Percentages < ResultSet
13
+ # Convert the percentages to subtotals
14
+ def subtotals(opts=nil)
15
+ @subtotals ||= transmogrify(Subtotals, opts)
16
+ end
17
+
18
+ private
19
+
20
+ # Perform the calculations
21
+ def enumerate!
22
+ # Now replace them with the percentages
23
+ total = resources.size.to_f
24
+ # Replace the subtotal with the percent
25
+ @results = subtotals.map do |*args, count|
26
+ percent = ((count * 100) / total).round(2)
27
+ [*args, percent]
28
+ end
29
+
30
+ # Drop results that are too small
31
+ if options.has_key? :threshold
32
+ @results.reject! { |result| result[-1] < options[:threshold] }
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,59 @@
1
+ module Graphene
2
+ # A generic class for Graphene stat result sets, which implements Enumerable and Graphene::LazyEnumerable.
3
+ # Calculations are performed lazily, so ResultSet objects are *cheap to create, but not necessarily cheap to use*.
4
+ class ResultSet
5
+ include LazyEnumerable
6
+ include Tablize
7
+ include OneDGraphs
8
+
9
+ # The options Hash passed in the constructor
10
+ attr_reader :options
11
+ # The attribute(s) that are being statted, passed in the constructor
12
+ attr_reader :attributes
13
+ # The original array of objects from which the stats were generated
14
+ attr_reader :resources
15
+
16
+ # Accepts an array of objects, and an unlimited number of method symbols (which sould be methods in the objects).
17
+ # Optionally accepts an options hash as a last argument.
18
+ #
19
+ # Implements Enumerable, so the results can be accessed by any of those methods, including each and to_a.
20
+ def initialize(resources, *args)
21
+ @options = args.last.is_a?(Hash) ? args.pop : {}
22
+ @attributes = args
23
+ @resources = resources
24
+ @results = []
25
+ end
26
+
27
+ # Calculates the resources, gropuing them by "over_x", which can be a method symbol or lambda
28
+ #
29
+ # Example by date
30
+ #
31
+ # Graphene.percentages(logs, :browser).over(:date)
32
+ # => {#<Date: 2012-07-22> => [["Firefox", 45], ["Chrome", 40], ["Internet Explorer", 15]],
33
+ # #<Date: 2012-07-23> => [["Firefox", 41], ["Chrome", 40], ["Internet Explorer", 19]],
34
+ # #<Date: 2012-07-24> => [["Chrome", 50], ["Firefox", 40], ["Internet Explorer", 10]]}
35
+ #
36
+ def over(over_x)
37
+ OverX.new(self, over_x)
38
+ end
39
+
40
+ # Returns a string representation of the results
41
+ def to_s
42
+ to_a.to_s
43
+ end
44
+
45
+ # Returns the maximum result
46
+ def max_result
47
+ x = attributes.size
48
+ sort_by { |result| result[x] }.last[x]
49
+ end
50
+
51
+ private
52
+
53
+ # Change it to another ResultSet subclass
54
+ def transmogrify(klass, opts=nil)
55
+ opts ||= self.options
56
+ klass.new(resources, *attributes, opts)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,32 @@
1
+ require 'graphene/result_set'
2
+
3
+ module Graphene
4
+ # Calculates and contains the subtotals of each attr (or attr group). Inherits from Graphene::ResultSet.
5
+ # See Graphene::LazyEnumerable, Graphene::Tablize and Graphene::OneDGraphs for more documentation.
6
+ #
7
+ # Don't create instance manually. Instead, use the Graphene.subtotals method, which will return
8
+ # a properly instantiated object.
9
+ class Subtotals < ResultSet
10
+ # Convert the percentages to subtotals
11
+ def percentages(opts=nil)
12
+ transmogrify(Percentages, opts)
13
+ end
14
+
15
+ private
16
+
17
+ # Calculates the subtotals
18
+ def enumerate!
19
+ # Count the occurrence of each
20
+ results = resources.inject({}) do |res, resource|
21
+ attrs = attributes.map { |attr| attr.respond_to?(:call) ? attr.call(resource) : resource.send(attr) }
22
+ res[attrs] ||= 0
23
+ res[attrs] += 1
24
+ res
25
+ end.to_a
26
+ results.each(&:flatten!)
27
+ # Sort in ascending order
28
+ results.sort! { |a,b| b.last <=> a.last }
29
+ @results = results
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ module Graphene
2
+ module Tablize
3
+ # Return the results formatted by the tablizer gem. Do disable headers, pass :header => false. Set alignment with :align.
4
+ def tablize(options={})
5
+ rows = to_a
6
+ # Default to including headers
7
+ unless options[:header] == false
8
+ headers = attributes.map(&:to_s) << self.class.name.scan(/\w+$/).last.to_s.gsub(/s$/, '').capitalize
9
+ rows.unshift(headers)
10
+ options[:header] = true
11
+ end
12
+ Tablizer::Table.new(rows, options)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,4 @@
1
+ module Graphene
2
+ # Version number
3
+ VERSION = '0.0.1'
4
+ end
data/lib/graphene.rb ADDED
@@ -0,0 +1,21 @@
1
+ # Required gems
2
+ require 'tablizer'
3
+ begin
4
+ require 'gruff'
5
+ rescue LoadError => e
6
+ $stderr.puts "NOTICE Graphene cannot find Gruff; graphing will not be not available. Install the \"gruff\" gem in enable it."
7
+ end
8
+
9
+ require 'graphene/version'
10
+ require 'graphene/graphene'
11
+
12
+ # Formatters and helpers
13
+ require 'graphene/tablizer'
14
+ require 'graphene/gruff'
15
+
16
+ # Calculators
17
+ require 'graphene/lazy_enumerable'
18
+ require 'graphene/result_set'
19
+ require 'graphene/subtotals'
20
+ require 'graphene/percentages'
21
+ require 'graphene/over_x'
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: graphene
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Jordan Hollinger
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2012-07-22 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: tablizer
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 1
30
+ - 0
31
+ version: "1.0"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ description: Library for calculating subtotals, percentages, tables and graphs from collections of Ruby objects
35
+ email: jordan@jordanhollinger.com
36
+ executables: []
37
+
38
+ extensions: []
39
+
40
+ extra_rdoc_files: []
41
+
42
+ files:
43
+ - lib/graphene.rb
44
+ - lib/graphene/graphene.rb
45
+ - lib/graphene/gruff.rb
46
+ - lib/graphene/gruff_helpers.rb
47
+ - lib/graphene/percentages.rb
48
+ - lib/graphene/result_set.rb
49
+ - lib/graphene/subtotals.rb
50
+ - lib/graphene/tablizer.rb
51
+ - lib/graphene/version.rb
52
+ - lib/graphene/over_x.rb
53
+ - lib/graphene/lazy_enumerable.rb
54
+ - README.rdoc
55
+ - LICENSE
56
+ has_rdoc: true
57
+ homepage: http://github.com/jhollinger/graphene
58
+ licenses: []
59
+
60
+ post_install_message:
61
+ rdoc_options: []
62
+
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ segments:
71
+ - 0
72
+ version: "0"
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ segments:
79
+ - 0
80
+ version: "0"
81
+ requirements: []
82
+
83
+ rubyforge_project:
84
+ rubygems_version: 1.3.7
85
+ signing_key:
86
+ specification_version: 3
87
+ summary: Easily create stats and graphs from collections of Ruby objects
88
+ test_files: []
89
+