samhendley-scruffy 0.2.7

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.
Files changed (54) hide show
  1. data/CHANGES.txt +115 -0
  2. data/LICENCE.txt +20 -0
  3. data/README.txt +66 -0
  4. data/lib/scruffy/components/background.rb +24 -0
  5. data/lib/scruffy/components/base.rb +57 -0
  6. data/lib/scruffy/components/data_markers.rb +26 -0
  7. data/lib/scruffy/components/graphs.rb +48 -0
  8. data/lib/scruffy/components/grid.rb +19 -0
  9. data/lib/scruffy/components/label.rb +17 -0
  10. data/lib/scruffy/components/legend.rb +105 -0
  11. data/lib/scruffy/components/style_info.rb +22 -0
  12. data/lib/scruffy/components/title.rb +19 -0
  13. data/lib/scruffy/components/value_markers.rb +32 -0
  14. data/lib/scruffy/components/viewport.rb +37 -0
  15. data/lib/scruffy/components.rb +21 -0
  16. data/lib/scruffy/formatters.rb +213 -0
  17. data/lib/scruffy/graph.rb +190 -0
  18. data/lib/scruffy/graph_state.rb +24 -0
  19. data/lib/scruffy/helpers/canvas.rb +41 -0
  20. data/lib/scruffy/helpers/layer_container.rb +95 -0
  21. data/lib/scruffy/helpers/meta.rb +5 -0
  22. data/lib/scruffy/helpers/point_container.rb +70 -0
  23. data/lib/scruffy/helpers.rb +12 -0
  24. data/lib/scruffy/layers/all_smiles.rb +137 -0
  25. data/lib/scruffy/layers/area.rb +46 -0
  26. data/lib/scruffy/layers/average.rb +67 -0
  27. data/lib/scruffy/layers/bar.rb +52 -0
  28. data/lib/scruffy/layers/base.rb +191 -0
  29. data/lib/scruffy/layers/line.rb +46 -0
  30. data/lib/scruffy/layers/pie.rb +123 -0
  31. data/lib/scruffy/layers/pie_slice.rb +119 -0
  32. data/lib/scruffy/layers/scatter.rb +21 -0
  33. data/lib/scruffy/layers/sparkline_bar.rb +39 -0
  34. data/lib/scruffy/layers/stacked.rb +87 -0
  35. data/lib/scruffy/layers.rb +24 -0
  36. data/lib/scruffy/rasterizers/batik_rasterizer.rb +39 -0
  37. data/lib/scruffy/rasterizers/rmagick_rasterizer.rb +27 -0
  38. data/lib/scruffy/rasterizers.rb +14 -0
  39. data/lib/scruffy/renderers/base.rb +95 -0
  40. data/lib/scruffy/renderers/cubed.rb +44 -0
  41. data/lib/scruffy/renderers/cubed3d.rb +53 -0
  42. data/lib/scruffy/renderers/empty.rb +22 -0
  43. data/lib/scruffy/renderers/pie.rb +20 -0
  44. data/lib/scruffy/renderers/reversed.rb +17 -0
  45. data/lib/scruffy/renderers/sparkline.rb +10 -0
  46. data/lib/scruffy/renderers/split.rb +48 -0
  47. data/lib/scruffy/renderers/standard.rb +36 -0
  48. data/lib/scruffy/renderers.rb +22 -0
  49. data/lib/scruffy/themes.rb +156 -0
  50. data/lib/scruffy/version.rb +9 -0
  51. data/lib/scruffy.rb +30 -0
  52. data/test/graph_creation_test.rb +101 -0
  53. data/test/test_helper.rb +2 -0
  54. metadata +135 -0
@@ -0,0 +1,213 @@
1
+ # ===Scruffy Formatters
2
+ #
3
+ # Author:: Brasten Sager
4
+ # Date:: August 16th, 2006
5
+ #
6
+ # Formatters are used to format the values displayed on the y-axis by
7
+ # setting graph.value_formatter.
8
+ #
9
+ # Example:
10
+ #
11
+ # graph.value_formatter = Scruffy::Formatters::Currency.new(:precision => 0)
12
+ #
13
+ module Scruffy::Formatters
14
+
15
+ # == Scruffy::Formatters::Base
16
+ #
17
+ # Author:: Brasten Sager
18
+ # Date:: August 16th, 2006
19
+ #
20
+ # Formatters are used to format the values displayed on the y-axis by
21
+ # setting graph.value_formatter.
22
+ class Base
23
+
24
+ # Called by the value marker component. Routes the format call
25
+ # to one of a couple possible methods.
26
+ #
27
+ # If the formatter defines a #format method, the returned value is used
28
+ # as the value. If the formatter defines a #format! method, the value passed is
29
+ # expected to be modified, and is used as the value. (This may not actually work,
30
+ # in hindsight.)
31
+ def route_format(target, idx, options = {})
32
+ args = [target, idx, options]
33
+ if respond_to?(:format)
34
+ send :format, *args[0...self.method(:format).arity]
35
+ elsif respond_to?(:format!)
36
+ send :format!, *args[0...self.method(:format!).arity]
37
+ target
38
+ else
39
+ raise NameError, "Formatter subclass must container either a format() method or format!() method."
40
+ end
41
+ end
42
+
43
+ protected
44
+ def number_with_precision(number, precision=3) #:nodoc:
45
+ sprintf("%01.#{precision}f", number)
46
+ end
47
+ end
48
+
49
+ # Allows you to pass in a Proc for use as a formatter.
50
+ #
51
+ # Use:
52
+ #
53
+ # graph.value_formatter = Scruffy::Formatters::Custom.new { |value, idx, options| "Displays Returned Value" }
54
+ class Custom < Base
55
+ attr_reader :proc
56
+
57
+ def initialize(&block)
58
+ @proc = block
59
+ end
60
+
61
+ def format(target, idx, options)
62
+ proc.call(target, idx, options)
63
+ end
64
+ end
65
+
66
+
67
+
68
+ # Default number formatter.
69
+ # Limits precision, beautifies numbers.
70
+ class Number < Base
71
+ attr_accessor :precision, :separator, :delimiter, :precision_limit
72
+
73
+ # Returns a new Number formatter.
74
+ #
75
+ # Options:
76
+ # precision:: precision to use for value. Can be set to an integer, :none or :auto.
77
+ # :auto will use whatever precision is necessary to portray all the numerical
78
+ # information, up to :precision_limit.
79
+ #
80
+ # Example: [100.1, 100.44, 200.323] will result in [100.100, 100.440, 200.323]
81
+ #
82
+ # separator:: decimal separator. Defaults to '.'
83
+ # delimiter:: delimiter character. Defaults to ','
84
+ # precision_limit:: upper limit for auto precision. (Ignored if roundup is specified)
85
+ # roundup:: round up the number to the given interval
86
+ def initialize(options = {})
87
+ @precision = options[:precision] || :none
88
+ @roundup = options[:roundup] || :none
89
+ @separator = options[:separator] || '.'
90
+ @delimiter = options[:delimiter] || ','
91
+ @precision_limit = options[:precision_limit] || 4
92
+ end
93
+
94
+ # Formats the value.
95
+ def format(target, idx, options)
96
+ my_precision = @precision
97
+
98
+ if @precision == :auto
99
+ my_precision = options[:all_values].inject(0) do |highest, current|
100
+ cur = current.to_f.to_s.split(".").last.size
101
+ cur > highest ? cur : highest
102
+ end
103
+
104
+ my_precision = @precision_limit if my_precision > @precision_limit
105
+ elsif @precision == :none
106
+ my_precision = 0
107
+ end
108
+
109
+ my_separator = @separator
110
+ my_separator = "" unless my_precision > 0
111
+ begin
112
+ number = ""
113
+
114
+ if @roundup == :none
115
+ parts = number_with_precision(target, my_precision).split('.')
116
+ number = parts[0].to_s.gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{@delimiter}") + my_separator + parts[1].to_s
117
+ else
118
+ number = roundup(target.to_f, @roundup).to_i.to_s
119
+ end
120
+
121
+ number
122
+ rescue StandardError => e
123
+ target
124
+ end
125
+ end
126
+
127
+
128
+ def roundup(target, nearest=100)
129
+ target % nearest == 0 ? target : target + nearest - (target % nearest)
130
+ end
131
+ def rounddown(target, nearest=100)
132
+ target % nearest == 0 ? target : target - (target % nearest)
133
+ end
134
+ end
135
+
136
+
137
+
138
+ # Currency formatter.
139
+ #
140
+ # Provides formatting for currencies.
141
+ class Currency < Base
142
+
143
+ # Returns a new Currency class.
144
+ #
145
+ # Options:
146
+ # precision:: precision of value
147
+ # unit:: Defaults to '$'
148
+ # separator:: Defaults to '.'
149
+ # delimiter:: Defaults to ','
150
+ # negative_color:: Color of value marker for negative values. Defaults to 'red'
151
+ # special_negatives:: If set to true, parenthesizes negative numbers. ie: -$150.50 becomes ($150.50).
152
+ # Defaults to false.
153
+ def initialize(options = {})
154
+ @precision = options[:precision] || 2
155
+ @unit = options[:unit] || '$'
156
+ @separator = options[:separator] || '.'
157
+ @delimiter = options[:delimiter] || ','
158
+ @negative_color = options[:negative_color] || 'red'
159
+ @special_negatives = options[:special_negatives] || false
160
+ end
161
+
162
+ # Formats value marker.
163
+ def format(target, idx, options)
164
+ @separator = "" unless @precision > 0
165
+ begin
166
+ parts = number_with_precision(target, @precision).split('.')
167
+ if @special_negatives && (target.to_f < 0)
168
+ number = "(" + @unit + parts[0].to_i.abs.to_s.gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{@delimiter}") + @separator + parts[1].to_s + ")"
169
+ else
170
+ number = @unit + parts[0].to_s.gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{@delimiter}") + @separator + parts[1].to_s
171
+ end
172
+ if (target.to_f < 0) && @negative_color
173
+ options[:marker_color_override] = @negative_color
174
+ end
175
+ number
176
+ rescue
177
+ target
178
+ end
179
+ end
180
+ end
181
+
182
+ # Percentage formatter.
183
+ #
184
+ # Provides formatting for percentages.
185
+ class Percentage < Base
186
+
187
+ # Returns new Percentage formatter.
188
+ #
189
+ # Options:
190
+ # precision:: Defaults to 3.
191
+ # separator:: Defaults to '.'
192
+ def initialize(options = {})
193
+ @precision = options[:precision] || 3
194
+ @separator = options[:separator] || '.'
195
+ end
196
+
197
+ # Formats percentages.
198
+ def format(target)
199
+ begin
200
+ number = number_with_precision(target, @precision)
201
+ parts = number.split('.')
202
+ if parts.at(1).nil?
203
+ parts[0] + "%"
204
+ else
205
+ parts[0] + @separator + parts[1].to_s + "%"
206
+ end
207
+ rescue
208
+ target
209
+ end
210
+ end
211
+ end
212
+
213
+ end
@@ -0,0 +1,190 @@
1
+ require 'forwardable'
2
+
3
+ module Scruffy
4
+
5
+ # ==Scruffy Graphs
6
+ #
7
+ # Author:: Brasten Sager
8
+ # Date:: August 5th, 2006
9
+ #
10
+ #
11
+ # ====Graphs vs. Layers (Graph Types)
12
+ #
13
+ # Scruffy::Graph is the primary class you will use to generate your graphs. A Graph does not
14
+ # define a graph type nor does it directly hold any data. Instead, a Graph object can be thought
15
+ # of as a canvas on which other graphs are draw. (The actual graphs themselves are subclasses of Scruffy::Layers::Base)
16
+ # Despite the technical distinction, we will refer to Scruffy::Graph objects as 'graphs' and Scruffy::Layers as
17
+ # 'layers' or 'graph types.'
18
+ #
19
+ #
20
+ # ==== Creating a Graph
21
+ #
22
+ # You can begin building a graph by instantiating a Graph object and optionally passing a hash
23
+ # of properties.
24
+ #
25
+ # graph = Scruffy::Graph.new
26
+ #
27
+ # OR
28
+ #
29
+ # graph = Scruffy::Graph.new(:title => "Monthly Profits", :theme => Scruffy::Themes::RubyBlog.new)
30
+ #
31
+ # Once you have a Graph object, you can set any Graph-level properties (title, theme, etc), or begin adding
32
+ # graph layers. You can add a graph layer to a graph by using the Graph#add or Graph#<< methods. The two
33
+ # methods are identical and used to accommodate syntax preferences.
34
+ #
35
+ # graph.add(:line, 'John', [100, -20, 30, 60])
36
+ # graph.add(:line, 'Sara', [120, 50, -80, 20])
37
+ #
38
+ # OR
39
+ #
40
+ # graph << Scruffy::Layers::Line.new(:title => 'John', :points => [100, -20, 30, 60])
41
+ # graph << Scruffy::Layers::Line.new(:title => 'Sara', :points => [120, 50, -80, 20])
42
+ #
43
+ # Now that we've created our graph and added a layer to it, we're ready to render! You can render the graph
44
+ # directly to SVG or any other image format (supported by RMagick) with the Graph#render method:
45
+ #
46
+ # graph.render # Renders a 600x400 SVG graph
47
+ #
48
+ # OR
49
+ #
50
+ # graph.render(:width => 1200)
51
+ #
52
+ # # For image formats other than SVG:
53
+ # graph.render(:width => 1200, :as => 'PNG')
54
+ #
55
+ # # To render directly to a file:
56
+ # graph.render(:width => 5000, :to => '<filename>')
57
+ #
58
+ # graph.render(:width => 700, :as => 'PNG', :to => '<filename>')
59
+ #
60
+ # And that's your basic Scruffy graph! Please check the documentation for the various methods and
61
+ # classes you'll be using, as there are a bunch of options not demonstrated here.
62
+ #
63
+ # A couple final things worth noting:
64
+ # * You can call Graph#render as often as you wish with different rendering options. In
65
+ # fact, you can modify the graph any way you wish between renders.
66
+ #
67
+ #
68
+ # * There are no restrictions to the combination of graph layers you can add. It is perfectly
69
+ # valid to do something like:
70
+ # graph.add(:line, [100, 200, 300])
71
+ # graph.add(:bar, [200, 150, 150])
72
+ #
73
+ # Of course, while you may be able to combine some things such as pie charts and line graphs, that
74
+ # doesn't necessarily mean they will make any logical sense together. We leave those decisions up to you. :)
75
+
76
+ class Graph
77
+ extend Forwardable;
78
+
79
+ include Scruffy::Helpers::LayerContainer
80
+
81
+ # Delegating these getters to the internal state object.
82
+ def_delegators :internal_state, :title, :theme, :default_type,
83
+ :point_markers, :value_formatter, :rasterizer
84
+
85
+ def_delegators :internal_state, :title=, :theme=, :default_type=,
86
+ :point_markers=, :value_formatter=, :rasterizer=
87
+
88
+ attr_reader :renderer # Writer defined below
89
+
90
+ # Returns a new Graph. You can optionally pass in a default graph type and an options hash.
91
+ #
92
+ # Graph.new # New graph
93
+ # Graph.new(:line) # New graph with default graph type of Line
94
+ # Graph.new({...}) # New graph with options.
95
+ #
96
+ # Options:
97
+ #
98
+ # title:: Graph's title
99
+ # theme:: A theme object to use when rendering graph
100
+ # layers:: An array of Layers for this graph to use
101
+ # default_type:: A symbol indicating the default type of Layer for this graph
102
+ # value_formatter:: Sets a formatter used to modify marker values prior to rendering
103
+ # point_markers:: Sets the x-axis marker values
104
+ # rasterizer:: Sets the rasterizer to use when rendering to an image format. Defaults to RMagick.
105
+ def initialize(*args)
106
+ self.default_type = args.shift if args.first.is_a?(Symbol)
107
+ options = args.shift.dup if args.first.is_a?(Hash)
108
+ raise ArgumentError, "The arguments provided are not supported." if args.size > 0
109
+
110
+ options ||= {}
111
+ self.theme = Scruffy::Themes::Standard.new
112
+ self.renderer = Scruffy::Renderers::Standard.new
113
+ self.rasterizer = Scruffy::Rasterizers::RMagickRasterizer.new
114
+ self.value_formatter = Scruffy::Formatters::Number.new
115
+
116
+ %w(title theme layers default_type value_formatter point_markers rasterizer).each do |arg|
117
+ self.send("#{arg}=".to_sym, options.delete(arg.to_sym)) unless options[arg.to_sym].nil?
118
+ end
119
+
120
+ raise ArgumentError, "Some options provided are not supported: #{options.keys.join(' ')}." if options.size > 0
121
+ end
122
+
123
+ # Renders the graph in it's current state to an SVG object.
124
+ #
125
+ # Options:
126
+ # size:: An array indicating the size you wish to render the graph. ( [x, y] )
127
+ # width:: The width of the rendered graph. A height is calculated at 3/4th of the width.
128
+ # theme:: Theme used to render graph for this render only.
129
+ # min_value:: Overrides the calculated minimum value used for the graph.
130
+ # max_value:: Overrides the calculated maximum value used for the graph.
131
+ # renderer:: Provide a Renderer object to use instead of the default.
132
+ #
133
+ # For other image formats:
134
+ # as:: File format to render to ('PNG', 'JPG', etc)
135
+ # to:: Name of file to save graph to, if desired. If not provided, image is returned as blob/string.
136
+ def render(options = {})
137
+ options[:theme] ||= theme
138
+ options[:value_formatter] ||= value_formatter
139
+ options[:point_markers] ||= point_markers
140
+ options[:size] ||= (options[:width] ? [options[:width], (options.delete(:width) * 0.6).to_i] : [600, 360])
141
+ options[:title] ||= title
142
+ options[:layers] ||= layers
143
+ options[:min_value] ||= bottom_value(:padded)
144
+ options[:max_value] ||= top_value
145
+ options[:graph] ||= self
146
+
147
+
148
+ # Removed for now.
149
+ # Added for making smaller fonts more legible, but may not be needed after all.
150
+ #
151
+ # if options[:as] && (options[:size][0] <= 300 || options[:size][1] <= 200)
152
+ # options[:actual_size] = options[:size]
153
+ # options[:size] = [800, (800.to_f * (options[:actual_size][1].to_f / options[:actual_size][0].to_f))]
154
+ # end
155
+
156
+ svg = ( options[:renderer].nil? ? self.renderer.render( options ) : options[:renderer].render( options ) )
157
+
158
+ # SVG to file.
159
+ if options[:to] && options[:as].nil?
160
+ File.open(options[:to], 'w') { |file|
161
+ file.write(svg)
162
+ }
163
+ end
164
+
165
+ options[:as] ? rasterizer.rasterize(svg, options) : svg
166
+ end
167
+
168
+ def renderer=(val)
169
+ raise ArgumentError, "Renderer must include a #render(options) method." unless (val.respond_to?(:render) && val.method(:render).arity.abs > 0)
170
+
171
+ @renderer = val
172
+ end
173
+
174
+ alias :layout :renderer
175
+
176
+ def component(id)
177
+ renderer.component(id)
178
+ end
179
+
180
+ def remove(id)
181
+ renderer.remove(id)
182
+ end
183
+
184
+ private
185
+ def internal_state
186
+ @internal_state ||= GraphState.new
187
+ end
188
+
189
+ end
190
+ end
@@ -0,0 +1,24 @@
1
+ # ===GraphState
2
+ #
3
+ # Author:: Brasten Sager
4
+ # Date:: September 27th, 2007
5
+ #
6
+ # State object for holding all of the graph's
7
+ # settings. Attempting to clean up the
8
+ # graph interface a bit.
9
+
10
+ module Scruffy
11
+ class GraphState
12
+
13
+ attr_accessor :title
14
+ attr_accessor :theme
15
+ attr_accessor :default_type
16
+ attr_accessor :point_markers
17
+ attr_accessor :value_formatter
18
+ attr_accessor :rasterizer
19
+
20
+ def initialize
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,41 @@
1
+ module Scruffy::Helpers
2
+
3
+ # ==Scruffy::Helpers::Canvas
4
+ #
5
+ # Author:: Brasten Sager
6
+ # Date:: August 16th, 2006
7
+ #
8
+ # Provides common methods for canvas objects. Primarily used for providing
9
+ # spacial-type calculations where necessary.
10
+ module Canvas
11
+ attr_accessor :components
12
+
13
+ def reset_settings!
14
+ self.options = {}
15
+ end
16
+
17
+ def component(id, components=self.components)
18
+ components.find {|elem| elem.id == id}
19
+ end
20
+
21
+ def remove(id, components=self.components)
22
+ components.delete(component(id))
23
+ end
24
+
25
+ protected
26
+ # Converts percentage values into actual pixel values based on the known
27
+ # render size.
28
+ #
29
+ # Returns a hash consisting of :x, :y, :width, and :height elements.
30
+ def bounds_for(canvas_size, position, size)
31
+ return nil if (position.nil? || size.nil?)
32
+ bounds = {}
33
+ bounds[:x] = canvas_size.first * (position.first / 100.to_f)
34
+ bounds[:y] = canvas_size.last * (position.last / 100.to_f)
35
+ bounds[:width] = canvas_size.first * (size.first / 100.to_f)
36
+ bounds[:height] = canvas_size.last * (size.last / 100.to_f)
37
+ bounds
38
+ end
39
+ end # canvas
40
+
41
+ end # scruffy::helpers
@@ -0,0 +1,95 @@
1
+ module Scruffy::Helpers
2
+
3
+ # ==Scruffy::Helpers::LayerContainer
4
+ #
5
+ # Author:: Brasten Sager
6
+ # Date:: August 16th, 2006
7
+ #
8
+ # Adds some common functionality to any object which needs to act as a
9
+ # container for graph layers. The best example of this is the Scruffy::Graph
10
+ # object itself, but this module is also used by Scruffy::Layer::Stacked.
11
+ module LayerContainer
12
+
13
+ # Adds a Layer to the Graph/Container. Accepts either a list of
14
+ # arguments used to build a new layer, or a Scruffy::Layers::Base-derived
15
+ # object. When passing a list of arguments, all arguments are optional,
16
+ # but the arguments specified must be provided in a particular order:
17
+ # type (Symbol), title (String), points (Array), options (Hash).
18
+ #
19
+ # Both #add and #<< can be used.
20
+ #
21
+ # graph.add(:line, [100, 200, 150]) # Create and add an untitled line graph
22
+ #
23
+ # graph << (:line, "John's Sales", [150, 100]) # Create and add a titled line graph
24
+ #
25
+ # graph << Scruffy::Layers::Bar.new({...}) # Adds Bar layer to graph
26
+ #
27
+ def <<(*args, &block)
28
+ if args[0].kind_of?(Scruffy::Layers::Base)
29
+ layers << args[0]
30
+ else
31
+ type = args.first.is_a?(Symbol) ? args.shift : @default_type
32
+ title = args.shift if args.first.is_a?(String)
33
+
34
+ # Layer handles PointContainer mixin, don't do it here
35
+ points = [Array, Hash].include?(args.first.class) ? args.shift : []
36
+ options = args.first.is_a?(Hash) ? args.shift : {}
37
+
38
+ title ||= ''
39
+
40
+ raise ArgumentError,
41
+ 'You must specify a graph type (:area, :bar, :line, etc) if you do not have a default type specified.' if type.nil?
42
+
43
+ class_name = "Scruffy::Layers::#{to_camelcase(type.to_s)}"
44
+ layer_class = Kernel::module_eval(class_name)
45
+ options = {:points => points, :title => title}.merge options
46
+ layer = layer_class.new(options, &block)
47
+ layers << layer
48
+ end
49
+ layer
50
+ end
51
+
52
+ alias :add :<<
53
+
54
+
55
+ # Layer Writer
56
+ def layers=(val)
57
+ @layers = val
58
+ end
59
+
60
+ # Layer Reader
61
+ def layers
62
+ @layers ||= []
63
+ end
64
+
65
+ # Returns the highest value in any of this container's layers.
66
+ #
67
+ # If padding is set to :padded, a 15% padding is added to the highest value.
68
+ def top_value(padding=nil) # :nodoc:
69
+ topval = layers.inject(0) { |max, layer| (max = ((max < layer.top_value) ? layer.top_value : max)) unless layer.top_value.nil?; max }
70
+ padding == :padded ? (topval - ((topval - bottom_value) * 0.15)) : topval
71
+ end
72
+
73
+ # Returns the lowest value in any of this container's layers.
74
+ #
75
+ # If padding is set to :padded, a 15% padding is added below the lowest value.
76
+ # If the lowest value is greater than zero, then the padding will not cross the zero line, preventing
77
+ # negative values from being introduced into the graph purely due to padding.
78
+ def bottom_value(padding=nil) # :nodoc:
79
+ botval = layers.inject(top_value) { |min, layer| (min = ((min > layer.bottom_value) ? layer.bottom_value : min)) unless layer.bottom_value.nil?; min }
80
+ above_zero = (botval > 0)
81
+ botval = (botval - ((top_value - botval) * 0.15))
82
+
83
+ # Don't introduce negative values solely due to padding.
84
+ # A user-provided value must be negative before padding will extend into negative values.
85
+ (above_zero && botval < 0) ? 0 : botval
86
+ end
87
+
88
+
89
+ protected
90
+ def to_camelcase(type) # :nodoc:
91
+ type.split('_').map { |e| e.capitalize }.join('')
92
+ end
93
+
94
+ end
95
+ end
@@ -0,0 +1,5 @@
1
+ # module Scruffy::Helpers::MetaAttributes
2
+ # def singleton_class
3
+ # (class << self; self; end)
4
+ # end
5
+ # end
@@ -0,0 +1,70 @@
1
+ module Scruffy::Helpers
2
+
3
+ # ==Scruffy::Helpers::PointContainer
4
+ #
5
+ # Author:: Mat Schaffer
6
+ # Date:: March 22nd, 2007
7
+ #
8
+ # Allows all standard point operations to be called on both Array and Hash
9
+ module PointContainer
10
+ def self.extended point_set
11
+ point_set.extend(const_get(point_set.class.to_s))
12
+ end
13
+
14
+ def sortable_values
15
+ values.find_all { |v| v.respond_to? :<=> }
16
+ end
17
+
18
+ def summable_values
19
+ values.find_all { |v| v.respond_to? :+ }
20
+ end
21
+
22
+ def maximum_value
23
+ sortable_values.sort.last
24
+ end
25
+
26
+ def minimum_value
27
+ sortable_values.sort.first
28
+ end
29
+
30
+ def sum
31
+ summable_values.inject(0) { |sum, i| sum += i }
32
+ end
33
+
34
+ def inject_with_index memo
35
+ index = 0
36
+ inject(memo) do |memo, item|
37
+ ret = yield memo, item, index
38
+ index = index.succ
39
+ ret
40
+ end
41
+ end
42
+
43
+ module Array
44
+ def values
45
+ self
46
+ end
47
+ end
48
+
49
+ module Hash
50
+ def minimum_key
51
+ self.keys.sort.first
52
+ end
53
+
54
+ def maximum_key
55
+ self.keys.sort.last
56
+ end
57
+
58
+ def inject memo
59
+ (minimum_key..maximum_key).each do |i|
60
+ memo = yield memo, self[i]
61
+ end
62
+ memo
63
+ end
64
+
65
+ def size
66
+ maximum_key - minimum_key + 1
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,12 @@
1
+ # ==Scruffy Helpers
2
+ #
3
+ # Author:: Brasten Sager
4
+ # Date:: August 16th, 2006
5
+ #
6
+ # Modules which provide helper methods for various situations.
7
+ module Scruffy::Helpers; end
8
+
9
+ require 'scruffy/helpers/canvas'
10
+ require 'scruffy/helpers/layer_container'
11
+ require 'scruffy/helpers/point_container'
12
+ #require 'scruffy/helpers/meta'