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 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, count|
39
- puts "There were #{count} hits from #{browser}"
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> => [["Firefox", 41], ["Chrome", 40], ["Internet Explorer", 19]],
61
- #<Date: 2012-07-24> => [["Chrome", 50], ["Firefox", 40], ["Internet Explorer", 10]]}
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 and Graphene::TwoDGraphs for more types and examples.
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 => e
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 'graphene/lazy_enumerable'
16
+ require 'lazy_enumerable'
18
17
  require 'graphene/result_set'
19
18
  require 'graphene/subtotals'
20
19
  require 'graphene/percentages'
@@ -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, "Gruff integration is disabled because Gruff could not be loaded; install the \"gruff\" gem"
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.
@@ -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 Graphene::LazyEnumerable, Graphene::Tablize and Graphene::TwoDGraphs for more documentation.
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
- @results = resources_by_x.inject({}) do |results, (x, group)|
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
@@ -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 Graphene::LazyEnumerable, Graphene::Tablize and Graphene::OneDGraphs for more documentation.
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.
@@ -1,5 +1,5 @@
1
1
  module Graphene
2
- # A generic class for Graphene stat result sets, which implements Enumerable and Graphene::LazyEnumerable.
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
@@ -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 Graphene::LazyEnumerable, Graphene::Tablize and Graphene::OneDGraphs for more documentation.
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.
@@ -1,4 +1,4 @@
1
1
  module Graphene
2
2
  # Version number
3
- VERSION = '0.0.2'
3
+ VERSION = '0.1.0'
4
4
  end
@@ -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
- prerelease: false
5
- segments:
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
- date: 2012-07-23 00:00:00 -04:00
18
- default_executable:
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
- prerelease: false
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
- segments:
29
- - 1
30
- - 0
31
- version: "1.0"
20
+ - !ruby/object:Gem::Version
21
+ version: '1.0'
32
22
  type: :runtime
33
- version_requirements: *id001
34
- description: Library for calculating subtotals, percentages, tables and graphs from collections of Ruby objects
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/graphene.rb
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/over_x.rb
52
- - lib/graphene/lazy_enumerable.rb
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
- segments:
70
- - 0
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
- segments:
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.3.7
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