graphene 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+