graphene 0.0.2 → 0.1.0
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/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
|