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 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