graphene 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +19 -13
- data/lib/graphene.rb +2 -3
- data/lib/graphene/gruff.rb +19 -1
- data/lib/graphene/over_x.rb +51 -3
- data/lib/graphene/percentages.rb +1 -1
- data/lib/graphene/result_set.rb +9 -3
- data/lib/graphene/subtotals.rb +1 -1
- data/lib/graphene/version.rb +1 -1
- data/lib/lazy_enumerable.rb +31 -0
- metadata +41 -56
- data/lib/graphene/lazy_enumerable.rb +0 -33
data/README.rdoc
CHANGED
@@ -5,8 +5,6 @@ It's most useful feature is probably the Gruff helpers. True, some types of Gruf
|
|
5
5
|
with, but others can require tedious boilerplate code to calculate percentages, well-spaced labels, and more.
|
6
6
|
Graphene hides all of that, accepting an array of objects and transforming them into a graph.
|
7
7
|
|
8
|
-
Read the full documentation at http://jordanhollinger.com/docs/graphene/.
|
9
|
-
|
10
8
|
Nobody likes you, Ruby 1.8. Now go away.
|
11
9
|
|
12
10
|
== Installation
|
@@ -35,8 +33,8 @@ See http://nubyonrails.com/pages/gruff/ for more on Gruff.
|
|
35
33
|
puts percentages.to_a
|
36
34
|
=> [["Firefox", 40.0], ["Chrome", 35.0], ["Internet Explorer", 25.0]]
|
37
35
|
|
38
|
-
percentages.each do |browser,
|
39
|
-
puts "
|
36
|
+
percentages.each do |browser, percent|
|
37
|
+
puts "#{percent}% of hits were from from #{browser}"
|
40
38
|
end
|
41
39
|
|
42
40
|
# You can also calculate by multiple criteria, and may use lambdas instead of symbols
|
@@ -53,18 +51,30 @@ Same as percentages above, except that subtotals are returned instead. See Graph
|
|
53
51
|
|
54
52
|
== Adding a dimension
|
55
53
|
|
56
|
-
Marty, you're not thinking fourth-dimensionally!
|
54
|
+
"Marty, you're not thinking fourth-dimensionally!" This is only three dimensions, but whatever, Doc.
|
57
55
|
|
58
56
|
puts Graphene.percentages(logs, :browser).over(:date)
|
59
57
|
=> {#<Date: 2012-07-22> => [["Firefox", 45], ["Chrome", 40], ["Internet Explorer", 15]],
|
60
|
-
#<Date: 2012-07-23> => [
|
61
|
-
#<Date: 2012-07-24> => [["
|
58
|
+
#<Date: 2012-07-23> => [],
|
59
|
+
#<Date: 2012-07-24> => [["Firefox", 41], ["Chrome", 40], ["Internet Explorer", 19]],
|
60
|
+
#<Date: 2012-07-25> => [["Chrome", 50], ["Firefox", 40], ["Internet Explorer", 10]]}
|
61
|
+
|
62
|
+
Notice that in the results above, there were no log entries on 2012-07-23. So how did Graphene know to include that date? Because dates are iterable.
|
63
|
+
If your X axis objects are *not* iterable and you want to ensure that none are left out, you can provide them yourself:
|
64
|
+
|
65
|
+
# Ryan didn't make any sales, so he won't show up unless we include his name
|
66
|
+
puts Graphene.subtotals(sales, :reams).over(:name, ['Jim', 'Dwight', 'Ryan'])
|
67
|
+
|
68
|
+
# Ruby doesn't know how to iterate months, but you do
|
69
|
+
puts Graphene.percentages(logs, :browser).over(->(log) { [log.date.month, log.date.year] }) do |month, year|
|
70
|
+
month < 12 ? [month + 1, year] : [1, year + 1]
|
71
|
+
end
|
62
72
|
|
63
73
|
== Tablizer
|
64
74
|
|
65
75
|
Integration with the tablizer gem provides quick ASCII tables.
|
66
76
|
|
67
|
-
puts percentages.tablize
|
77
|
+
puts Graphene.percentages(logs, :browser, :platform).tablize
|
68
78
|
=> +-----------------+------------+----------+
|
69
79
|
| Browser | Platform |Percentage|
|
70
80
|
+-----------------+------------+----------+
|
@@ -98,16 +108,12 @@ Provides helpers for generating Gruff graphs. Requires the "gruff" Ruby gem.
|
|
98
108
|
end
|
99
109
|
end
|
100
110
|
|
101
|
-
See Graphene::OneDGraphs
|
111
|
+
See Graphene::OneDGraphs, Graphene::TwoDGraphs and http://jordanhollinger.com/2012/07/23/introducing-graphene for more graph types and examples.
|
102
112
|
|
103
113
|
See http://www.ruby-doc.org/gems/docs/f/fhs-gruff-0.3.6.2/README_txt.html for more on Gruff.
|
104
114
|
Specifically, http://www.ruby-doc.org/gems/docs/f/fhs-gruff-0.3.6.2/Gruff/Base.html will let
|
105
115
|
you know many things that can be customized in the optional block.
|
106
116
|
|
107
|
-
== TODO
|
108
|
-
|
109
|
-
Graphene::OverX and the graph helpers needs the ability to fill in empty points
|
110
|
-
|
111
117
|
== License
|
112
118
|
Copyright 2012 Jordan Hollinger
|
113
119
|
|
data/lib/graphene.rb
CHANGED
@@ -2,8 +2,7 @@
|
|
2
2
|
require 'tablizer'
|
3
3
|
begin
|
4
4
|
require 'gruff'
|
5
|
-
rescue LoadError
|
6
|
-
$stderr.puts "NOTICE Graphene cannot find Gruff; graphing will not be not available. Install the \"gruff\" gem in enable it."
|
5
|
+
rescue LoadError
|
7
6
|
end
|
8
7
|
|
9
8
|
require 'graphene/version'
|
@@ -14,7 +13,7 @@ require 'graphene/tablizer'
|
|
14
13
|
require 'graphene/gruff'
|
15
14
|
|
16
15
|
# Calculators
|
17
|
-
require '
|
16
|
+
require 'lazy_enumerable'
|
18
17
|
require 'graphene/result_set'
|
19
18
|
require 'graphene/subtotals'
|
20
19
|
require 'graphene/percentages'
|
data/lib/graphene/gruff.rb
CHANGED
@@ -1,13 +1,29 @@
|
|
1
1
|
module Graphene
|
2
|
+
class << self
|
3
|
+
# The default Gruff font path
|
4
|
+
attr_accessor :font
|
5
|
+
|
6
|
+
# The default Gruff theme
|
7
|
+
attr_accessor :theme
|
8
|
+
end
|
9
|
+
|
2
10
|
# Executes the given block if Gruff is available. Raises a GrapheneException if not.
|
3
11
|
def self.gruff
|
4
12
|
if defined? Gruff
|
5
13
|
yield if block_given?
|
6
14
|
else
|
7
|
-
raise GrapheneException,
|
15
|
+
raise GrapheneException, 'Gruff integration is disabled because Gruff could not be found; bundle or install the "rmagick" and "gruff" gems to enable it'
|
8
16
|
end
|
9
17
|
end
|
10
18
|
|
19
|
+
private
|
20
|
+
|
21
|
+
# Apply the default theme and font to the given Gruff chart
|
22
|
+
def self.theme!(chart)
|
23
|
+
chart.font = self.font if self.font
|
24
|
+
chart.theme = self.theme if self.theme
|
25
|
+
end
|
26
|
+
|
11
27
|
# Extends calculators with one-dimensional graphs, like pie charts.
|
12
28
|
module OneDGraphs
|
13
29
|
# Returns a Gruff::Pie object with the stats set.
|
@@ -206,6 +222,7 @@ module Graphene
|
|
206
222
|
|
207
223
|
# Builds a chart
|
208
224
|
def chart(chart, path=nil, title=nil, hack=false, &block)
|
225
|
+
Graphene.theme! chart
|
209
226
|
chart.title = title unless title.nil?
|
210
227
|
block.call(chart) if block
|
211
228
|
|
@@ -394,6 +411,7 @@ module Graphene
|
|
394
411
|
|
395
412
|
# Builds a graph
|
396
413
|
def graph(graph, path=nil, title=nil, &block)
|
414
|
+
Graphene.theme! graph
|
397
415
|
graph.title = title unless title.nil?
|
398
416
|
|
399
417
|
# Create an empty array for each group (e.g. {["Firefox"] => [], ["Safari"] => []}), even if it's empty.
|
data/lib/graphene/over_x.rb
CHANGED
@@ -8,7 +8,14 @@ module Graphene
|
|
8
8
|
# #<Date: 2012-07-23> => [["Firefox", 41], ["Chrome", 40], ["Internet Explorer", 19]],
|
9
9
|
# #<Date: 2012-07-24> => [["Chrome", 50], ["Firefox", 40], ["Internet Explorer", 10]]}
|
10
10
|
#
|
11
|
-
# See
|
11
|
+
# See LazyEnumerable, Graphene::Tablize and Graphene::TwoDGraphs for more documentation.
|
12
|
+
#
|
13
|
+
# If your X objects can be put into a range, Graphene will attempt to fill in any gaps along the X axis.
|
14
|
+
# But if they can't be ranged (e.g. formatted strings), you have several options to fill them in yourself:
|
15
|
+
#
|
16
|
+
# Graphene.percentages(logs, :browser).over(->(l) { l.date.strftime('%B %Y') }, [an array of all month/year in the logs])
|
17
|
+
# Graphene.percentages(logs, :browser).over(->(l) { l.date.strftime('%B %Y') }) { |str| somehow parse the month/year and return the next one }
|
18
|
+
#
|
12
19
|
class OverX
|
13
20
|
include LazyEnumerable
|
14
21
|
include TwoDGraphs
|
@@ -18,9 +25,10 @@ module Graphene
|
|
18
25
|
|
19
26
|
# Accepts a ResultSet object (i.e. Graphene::Subtotals or Graphene::Percentages), and a
|
20
27
|
# method symbol or proc/lambda to build the X axis
|
21
|
-
def initialize(result_set, attr_or_lambda)
|
28
|
+
def initialize(result_set, attr_or_lambda, filler=nil, &filler_block)
|
22
29
|
@result_set = result_set
|
23
30
|
@attribute = attr_or_lambda
|
31
|
+
@filler = filler_block || filler
|
24
32
|
@results = {}
|
25
33
|
end
|
26
34
|
|
@@ -38,12 +46,52 @@ module Graphene
|
|
38
46
|
|
39
47
|
# Run the calculation
|
40
48
|
def enumerate!
|
49
|
+
# Group all the objects by X
|
41
50
|
resources_by_x = result_set.resources.group_by(&@attribute).sort_by(&:first)
|
42
|
-
|
51
|
+
# Attempt to retrieve or calculate all possible X points, so there are no gaps
|
52
|
+
x_points_hash = Hash[x_points(resources_by_x).map { |x| [x, []] }]
|
53
|
+
# Distribute the objects across the X points
|
54
|
+
@results = resources_by_x.inject(x_points_hash) do |results, (x, group)|
|
43
55
|
results[x] ||= []
|
44
56
|
results[x] += result_set.class.new(group, *result_set.attributes).to_a
|
45
57
|
results
|
46
58
|
end
|
47
59
|
end
|
60
|
+
|
61
|
+
# Returns an array of all pre-calculated (or provided) X points. Attempts to fill in any gaps.
|
62
|
+
def x_points(resources_by_x)
|
63
|
+
first_x = resources_by_x.first ? resources_by_x.first.first : nil
|
64
|
+
last_x = resources_by_x.last ? resources_by_x.last.first : nil
|
65
|
+
|
66
|
+
# A range from the first to last point (multi-char strings are excluded, because a range of those is usually not what you want)
|
67
|
+
if @filler.nil? and first_x.respond_to? :succ and first_x.respond_to? :"<=>" and (!first_x.is_a?(String) or first_x.size == 1)
|
68
|
+
(first_x..last_x).to_a
|
69
|
+
# An array
|
70
|
+
elsif @filler.respond_to? :to_a
|
71
|
+
@filler.to_a
|
72
|
+
# A block
|
73
|
+
elsif @filler.respond_to? :call
|
74
|
+
keys = resources_by_x.map(&:first)
|
75
|
+
keys.each_with_index.to_a.inject([]) do |points, (key, i)|
|
76
|
+
points << key
|
77
|
+
# Fill in any gaps between this point and the next
|
78
|
+
if next_available_key = keys[i+1]
|
79
|
+
next_calculated_key = key
|
80
|
+
while next_calculated_key != next_available_key
|
81
|
+
# Try to prevent an infinite loop if someone does something dumb
|
82
|
+
if next_calculated_key.respond_to? :"<"
|
83
|
+
break unless next_calculated_key < next_available_key
|
84
|
+
end
|
85
|
+
points << next_calculated_key
|
86
|
+
next_calculated_key = @filler.call(next_calculated_key)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
points
|
90
|
+
end
|
91
|
+
# Can't do it, so there may be gaps
|
92
|
+
else
|
93
|
+
[]
|
94
|
+
end
|
95
|
+
end
|
48
96
|
end
|
49
97
|
end
|
data/lib/graphene/percentages.rb
CHANGED
@@ -2,7 +2,7 @@ require 'graphene/subtotals'
|
|
2
2
|
|
3
3
|
module Graphene
|
4
4
|
# Calculates and contains the percent subtotals of each attr (or attr group). Inherits from Graphene::ResultSet.
|
5
|
-
# See
|
5
|
+
# See LazyEnumerable, Graphene::Tablize and Graphene::OneDGraphs for more documentation.
|
6
6
|
#
|
7
7
|
# If you passed an options Hash containing :threshold to the constructor,
|
8
8
|
# any results falling below it will be excluded.
|
data/lib/graphene/result_set.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Graphene
|
2
|
-
# A generic class for Graphene stat result sets, which implements Enumerable and
|
2
|
+
# A generic class for Graphene stat result sets, which implements Enumerable and LazyEnumerable.
|
3
3
|
# Calculations are performed lazily, so ResultSet objects are *cheap to create, but not necessarily cheap to use*.
|
4
4
|
class ResultSet
|
5
5
|
include LazyEnumerable
|
@@ -32,9 +32,15 @@ module Graphene
|
|
32
32
|
# => {#<Date: 2012-07-22> => [["Firefox", 45], ["Chrome", 40], ["Internet Explorer", 15]],
|
33
33
|
# #<Date: 2012-07-23> => [["Firefox", 41], ["Chrome", 40], ["Internet Explorer", 19]],
|
34
34
|
# #<Date: 2012-07-24> => [["Chrome", 50], ["Firefox", 40], ["Internet Explorer", 10]]}
|
35
|
+
#
|
36
|
+
# If your X objects can be put into a range, Graphene will attempt to fill in any gaps along the X axis.
|
37
|
+
# But if they can't be ranged (e.g. formatted strings), you have several options to fill them in yourself:
|
38
|
+
#
|
39
|
+
# Graphene.percentages(logs, :browser).over(->(l) { l.date.strftime('%B %Y') }, [an array of all month/year in the logs])
|
40
|
+
# Graphene.percentages(logs, :browser).over(->(l) { l.date.strftime('%B %Y') }) { |str| somehow parse the month/year and return the next one }
|
35
41
|
#
|
36
|
-
def over(over_x)
|
37
|
-
OverX.new(self, over_x)
|
42
|
+
def over(over_x, filler=nil, &filler_block)
|
43
|
+
OverX.new(self, over_x, filler, &filler_block)
|
38
44
|
end
|
39
45
|
|
40
46
|
# Returns a string representation of the results
|
data/lib/graphene/subtotals.rb
CHANGED
@@ -2,7 +2,7 @@ require 'graphene/result_set'
|
|
2
2
|
|
3
3
|
module Graphene
|
4
4
|
# Calculates and contains the subtotals of each attr (or attr group). Inherits from Graphene::ResultSet.
|
5
|
-
# See
|
5
|
+
# See LazyEnumerable, Graphene::Tablize and Graphene::OneDGraphs for more documentation.
|
6
6
|
#
|
7
7
|
# Don't create instance manually. Instead, use the Graphene.subtotals method, which will return
|
8
8
|
# a properly instantiated object.
|
data/lib/graphene/version.rb
CHANGED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Includes Enumerable and alculates @results lazily. An enumerate! method must be implemented.
|
2
|
+
module LazyEnumerable
|
3
|
+
def self.included(base) # :nodoc:
|
4
|
+
base.send(:include, Enumerable)
|
5
|
+
end
|
6
|
+
|
7
|
+
# Implements the "each" method required by Enumerable.
|
8
|
+
def each(&block)
|
9
|
+
lazily_enumerate!
|
10
|
+
@results.each &block
|
11
|
+
end
|
12
|
+
|
13
|
+
# Tests equality between this and another set
|
14
|
+
def ==(other)
|
15
|
+
lazily_enumerate!
|
16
|
+
to_a == other.to_a
|
17
|
+
end
|
18
|
+
|
19
|
+
# Tests equality between this and another set
|
20
|
+
def ===(other)
|
21
|
+
lazily_enumerate!
|
22
|
+
to_a === other.to_a
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# Run the calculation if it hasn't already been
|
28
|
+
def lazily_enumerate!
|
29
|
+
enumerate! if @results.empty?
|
30
|
+
end
|
31
|
+
end
|
metadata
CHANGED
@@ -1,88 +1,73 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphene
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
6
|
-
- 0
|
7
|
-
- 0
|
8
|
-
- 2
|
9
|
-
version: 0.0.2
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
10
6
|
platform: ruby
|
11
|
-
authors:
|
7
|
+
authors:
|
12
8
|
- Jordan Hollinger
|
13
9
|
autorequire:
|
14
10
|
bindir: bin
|
15
11
|
cert_chain: []
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
dependencies:
|
20
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2012-11-13 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
21
15
|
name: tablizer
|
22
|
-
|
23
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
24
17
|
none: false
|
25
|
-
requirements:
|
18
|
+
requirements:
|
26
19
|
- - ~>
|
27
|
-
- !ruby/object:Gem::Version
|
28
|
-
|
29
|
-
- 1
|
30
|
-
- 0
|
31
|
-
version: "1.0"
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.0'
|
32
22
|
type: :runtime
|
33
|
-
|
34
|
-
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.0'
|
30
|
+
description: Library for calculating subtotals, percentages, tables and graphs from
|
31
|
+
collections of Ruby objects
|
35
32
|
email: jordan@jordanhollinger.com
|
36
33
|
executables: []
|
37
|
-
|
38
34
|
extensions: []
|
39
|
-
|
40
35
|
extra_rdoc_files: []
|
41
|
-
|
42
|
-
files:
|
36
|
+
files:
|
43
37
|
- lib/graphene.rb
|
44
|
-
- lib/graphene/
|
45
|
-
- lib/graphene/gruff.rb
|
46
|
-
- lib/graphene/version.rb
|
47
|
-
- lib/graphene/percentages.rb
|
48
|
-
- lib/graphene/result_set.rb
|
38
|
+
- lib/graphene/over_x.rb
|
49
39
|
- lib/graphene/subtotals.rb
|
50
40
|
- lib/graphene/tablizer.rb
|
51
|
-
- lib/graphene/
|
52
|
-
- lib/graphene/
|
41
|
+
- lib/graphene/version.rb
|
42
|
+
- lib/graphene/graphene.rb
|
43
|
+
- lib/graphene/result_set.rb
|
44
|
+
- lib/graphene/percentages.rb
|
45
|
+
- lib/graphene/gruff.rb
|
46
|
+
- lib/lazy_enumerable.rb
|
53
47
|
- README.rdoc
|
54
48
|
- LICENSE
|
55
|
-
has_rdoc: true
|
56
49
|
homepage: http://github.com/jhollinger/graphene
|
57
50
|
licenses: []
|
58
|
-
|
59
51
|
post_install_message:
|
60
52
|
rdoc_options: []
|
61
|
-
|
62
|
-
require_paths:
|
53
|
+
require_paths:
|
63
54
|
- lib
|
64
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
56
|
none: false
|
66
|
-
requirements:
|
67
|
-
- -
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
|
70
|
-
|
71
|
-
version: "0"
|
72
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ! '>='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
62
|
none: false
|
74
|
-
requirements:
|
75
|
-
- -
|
76
|
-
- !ruby/object:Gem::Version
|
77
|
-
|
78
|
-
- 0
|
79
|
-
version: "0"
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
80
67
|
requirements: []
|
81
|
-
|
82
68
|
rubyforge_project:
|
83
|
-
rubygems_version: 1.
|
69
|
+
rubygems_version: 1.8.23
|
84
70
|
signing_key:
|
85
71
|
specification_version: 3
|
86
72
|
summary: Easily create stats and graphs from collections of Ruby objects
|
87
73
|
test_files: []
|
88
|
-
|
@@ -1,33 +0,0 @@
|
|
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
|