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.
- data/CHANGES.txt +115 -0
- data/LICENCE.txt +20 -0
- data/README.txt +66 -0
- data/lib/scruffy/components/background.rb +24 -0
- data/lib/scruffy/components/base.rb +57 -0
- data/lib/scruffy/components/data_markers.rb +26 -0
- data/lib/scruffy/components/graphs.rb +48 -0
- data/lib/scruffy/components/grid.rb +19 -0
- data/lib/scruffy/components/label.rb +17 -0
- data/lib/scruffy/components/legend.rb +105 -0
- data/lib/scruffy/components/style_info.rb +22 -0
- data/lib/scruffy/components/title.rb +19 -0
- data/lib/scruffy/components/value_markers.rb +32 -0
- data/lib/scruffy/components/viewport.rb +37 -0
- data/lib/scruffy/components.rb +21 -0
- data/lib/scruffy/formatters.rb +213 -0
- data/lib/scruffy/graph.rb +190 -0
- data/lib/scruffy/graph_state.rb +24 -0
- data/lib/scruffy/helpers/canvas.rb +41 -0
- data/lib/scruffy/helpers/layer_container.rb +95 -0
- data/lib/scruffy/helpers/meta.rb +5 -0
- data/lib/scruffy/helpers/point_container.rb +70 -0
- data/lib/scruffy/helpers.rb +12 -0
- data/lib/scruffy/layers/all_smiles.rb +137 -0
- data/lib/scruffy/layers/area.rb +46 -0
- data/lib/scruffy/layers/average.rb +67 -0
- data/lib/scruffy/layers/bar.rb +52 -0
- data/lib/scruffy/layers/base.rb +191 -0
- data/lib/scruffy/layers/line.rb +46 -0
- data/lib/scruffy/layers/pie.rb +123 -0
- data/lib/scruffy/layers/pie_slice.rb +119 -0
- data/lib/scruffy/layers/scatter.rb +21 -0
- data/lib/scruffy/layers/sparkline_bar.rb +39 -0
- data/lib/scruffy/layers/stacked.rb +87 -0
- data/lib/scruffy/layers.rb +24 -0
- data/lib/scruffy/rasterizers/batik_rasterizer.rb +39 -0
- data/lib/scruffy/rasterizers/rmagick_rasterizer.rb +27 -0
- data/lib/scruffy/rasterizers.rb +14 -0
- data/lib/scruffy/renderers/base.rb +95 -0
- data/lib/scruffy/renderers/cubed.rb +44 -0
- data/lib/scruffy/renderers/cubed3d.rb +53 -0
- data/lib/scruffy/renderers/empty.rb +22 -0
- data/lib/scruffy/renderers/pie.rb +20 -0
- data/lib/scruffy/renderers/reversed.rb +17 -0
- data/lib/scruffy/renderers/sparkline.rb +10 -0
- data/lib/scruffy/renderers/split.rb +48 -0
- data/lib/scruffy/renderers/standard.rb +36 -0
- data/lib/scruffy/renderers.rb +22 -0
- data/lib/scruffy/themes.rb +156 -0
- data/lib/scruffy/version.rb +9 -0
- data/lib/scruffy.rb +30 -0
- data/test/graph_creation_test.rb +101 -0
- data/test/test_helper.rb +2 -0
- 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,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'
|