scruffy 0.2.0 → 0.2.1

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.
@@ -1,30 +1,43 @@
1
- module Scruffy
2
- module Layers
3
- class Bar < Base
4
-
5
- def draw(svg, coords, options = {})
6
- coords.each do |coord|
7
- x, y, bar_height = (coord.first-(@bar_width * 0.5)), coord.last, (height - coord.last)
1
+ module Scruffy::Layers
2
+ # ==Scruffy::Layers::Bar
3
+ #
4
+ # Author:: Brasten Sager
5
+ # Date:: August 6th, 2006
6
+ #
7
+ # Standard bar graph.
8
+ class Bar < Base
9
+
10
+ # Draw bar graph.
11
+ def draw(svg, coords, options = {})
12
+ coords.each do |coord|
13
+ x, y, bar_height = (coord.first-(@bar_width * 0.5)), coord.last, (height - coord.last)
8
14
 
9
- svg.rect( :x => x, :y => y, :width => @bar_width, :height => bar_height,
10
- :fill => color.to_s, :stroke => color.to_s, 'style' => "opacity: #{opacity}" )
11
- end
15
+ svg.rect( :x => x, :y => y, :width => @bar_width, :height => bar_height,
16
+ :fill => color.to_s, 'style' => "opacity: #{opacity}; stroke: none;" )
12
17
  end
18
+ end
13
19
 
14
- protected
15
- def generate_coordinates(options = {})
16
- @bar_width = (width / points.size) * 0.9
17
- options[:point_distance] = (width - (width / points.size)) / (points.size - 1).to_f
20
+ protected
21
+
22
+ # Due to the size of the bar graph, X-axis coords must
23
+ # be squeezed so that the bars do not hang off the ends
24
+ # of the graph.
25
+ #
26
+ # Unfortunately this just mean that bar-graphs and most other graphs
27
+ # end up on different points. Maybe adding a padding to the coordinates
28
+ # should be a graph-wide thing?
29
+ def generate_coordinates(options = {})
30
+ @bar_width = (width / points.size) * 0.9
31
+ options[:point_distance] = (width - (width / points.size)) / (points.size - 1).to_f
18
32
 
19
- coords = (0...points.size).map do |idx|
20
- x_coord = (options[:point_distance] * idx) + (width / points.size * 0.5)
33
+ coords = (0...points.size).map do |idx|
34
+ x_coord = (options[:point_distance] * idx) + (width / points.size * 0.5)
21
35
 
22
- relative_percent = ((points[idx] == min_value) ? 0 : ((points[idx] - min_value) / (max_value - min_value).to_f))
23
- y_coord = (height - (height * relative_percent))
24
- [x_coord, y_coord]
25
- end
26
- coords
36
+ relative_percent = ((points[idx] == min_value) ? 0 : ((points[idx] - min_value) / (max_value - min_value).to_f))
37
+ y_coord = (height - (height * relative_percent))
38
+ [x_coord, y_coord]
27
39
  end
28
- end
40
+ coords
41
+ end
29
42
  end
30
43
  end
@@ -1,144 +1,156 @@
1
- module Scruffy
2
- # ==Scruffy::Layers
1
+ module Scruffy::Layers
2
+ # ==Scruffy::Layers::Base
3
3
  #
4
4
  # Author:: Brasten Sager
5
- # Date:: August 10th, 2006
5
+ # Date:: August 5th, 2006
6
6
  #
7
- # See documentation in Scruffy::Layers::Base
7
+ # Scruffy::Layers::Base contains the basic functionality needed by the various types of graphs. The Base
8
+ # class is responsible holding layer information such as the title and data points.
8
9
  #
9
- module Layers
10
- # ==Scruffy::Layers::Base
11
- #
12
- # Author:: Brasten Sager
13
- # Date:: August 5th, 2006
10
+ # When the graph is rendered, the graph renderer calls Base#render. Base#render sets up
11
+ # some standard information, and calculates the x,y coordinates of each data point. The draw() method,
12
+ # which should have been overridden by the current instance, is then called. The actual rendering of
13
+ # the graph takes place there.
14
+ #
15
+ # ====Create New Graph Types
16
+ #
17
+ # Assuming the information generated by Scruffy::Layers::Base is sufficient, you can create a new graph type
18
+ # simply by overriding the draw() method. See Base#draw for arguments.
19
+ #
20
+ class Base
21
+ # The following attributes are user-definable at any time.
22
+ # title, points, relevant_data, preferred_color, options
23
+ attr_accessor :title
24
+ attr_accessor :points
25
+ attr_accessor :relevant_data
26
+ attr_accessor :preferred_color
27
+ attr_accessor :options # On-the-fly values for easy customization / acts as attributes.
28
+
29
+ # The following attributes are set during the layer's render process,
30
+ # and act more as a record of what just happened for later processes.
31
+ # height, width, min_value, max_value, color, opacity, complexity
32
+ attr_reader :height, :width
33
+ attr_reader :min_value, :max_value
34
+ attr_reader :color
35
+ attr_reader :opacity
36
+ attr_reader :complexity
37
+
38
+ # Returns a new Base object.
14
39
  #
15
- # Scruffy::Layers::Base contains the basic functionality needed by the various types of graphs. The Base
16
- # class is responsible holding layer information such as the title and data points.
40
+ # Any options other that those specified below are stored in the @options variable for
41
+ # possible later use. This would be a good place to store options needed for a custom
42
+ # graph.
17
43
  #
18
- # When the graph is rendered, the graph renderer calls Base#render. Base#render sets up
19
- # some standard information, and calculates the x,y coordinates of each data point. The draw() method,
20
- # which should have been overridden by the current instance, is then called. The actual rendering of
21
- # the graph takes place there.
44
+ # Options:
45
+ # title:: Name/title of data group
46
+ # points:: Array of data points
47
+ # preferred_color:: Color used to render this graph, overrides theme color.
48
+ # relevant_data:: Rarely used - indicates the data on this graph should not
49
+ # included in any graph data aggregations, such as averaging data points.
50
+ def initialize(options = {})
51
+ @title = options.delete(:title) || ''
52
+ @points = options.delete(:points) || []
53
+ @preferred_color = options.delete(:preferred_color)
54
+ @relevant_data = options.delete(:relevant_data) || true
55
+ @options = options
56
+ end
57
+
58
+ # Builds SVG code for this graph using the provided Builder object.
59
+ # This method actually generates data needed by this graph, then passes the
60
+ # rendering responsibilities to Base#draw.
22
61
  #
23
- # ====Create New Graph Types
62
+ # svg:: a Builder object used to create SVG code.
63
+ def render(svg, options = {})
64
+ setup_variables(options)
65
+ coords = generate_coordinates(options)
66
+
67
+ draw(svg, coords, options)
68
+ end
69
+
70
+ # The method called by Base#draw to render the graph.
71
+ #
72
+ # svg:: a Builder object to use for creating SVG code.
73
+ # coords:: An array of coordinates relating to the graph's data points. ie: [[100, 120], [200, 140], [300, 40]]
74
+ # options:: Optional arguments.
75
+ def draw(svg, coords, options={})
76
+ raise RenderError, "You must override the Base#draw method."
77
+ end
78
+
79
+ # Returns a hash with information to be used by the legend.
24
80
  #
25
- # Assuming the information generated by Scruffy::Layers::Base is sufficient, you can create a new graph type
26
- # simply by overriding the draw() method. See Base#draw for arguments.
81
+ # Alternatively, returns nil if you don't want this layer to be in the legend,
82
+ # or an array of hashes if this layer should have multiple legend entries (stacked?)
27
83
  #
28
- class Base
29
- attr_accessor :title
30
- attr_accessor :points
31
- attr_accessor :height, :width
32
- attr_accessor :min_value, :max_value
33
- attr_accessor :preferred_color, :color
34
- attr_accessor :opacity
35
- attr_accessor :resolver
36
- attr_accessor :complexity
37
- attr_accessor :relevant_data
38
- attr_accessor :options # On-the-fly values for easy customization / acts as attributes.
39
-
40
- # Returns a new Base object.
41
- #
42
- # Options:
43
- # title::
44
- # points:: Array of data points
45
- # color:: Color used to render this graph, overrides theme color.
46
- # relevant_data:: Rarely used - indicates the data on this graph should not
47
- # included in any graph data aggregations, such as averaging data points.
48
- def initialize(options = {})
49
- @title = options.delete(:title) || ''
50
- @points = options.delete(:points) || []
51
- @preferred_color = options.delete(:color)
52
- @relevant_data = options.delete(:relevant_data) || true
53
- @options = options
54
- end
55
-
56
- # Builds SVG code for this graph using the provided Builder object.
57
- # This method actually generates data needed by this graph, then passes the
58
- # rendering responsibilities to Base#draw.
59
- #
60
- # svg:: a Builder object used to create SVG code.
61
- def render(svg, options = {})
62
- setup_variables(options)
63
- coords = generate_coordinates(options)
64
-
65
- draw(svg, coords, options)
66
- end
67
-
68
- # The method called by Base#draw to render the graph.
69
- #
70
- # svg:: a Builder object to use for creating SVG code.
71
- # coords:: An array of coordinates relating to the graph's data points. ie: [[100, 120], [200, 140], [300, 40]]
72
- # options:: Optional arguments.
73
- def draw(svg, coords, options={})
74
- # This is what the various graphs need to implement.
75
- end
76
-
77
- # Returns a hash with information to be used by the legend.
78
- #
79
- # Alternatively, returns nil if you don't want this layer to be in the legend,
80
- # or an array of hashes if this layer should have multiple legend entries (stacked?)
81
- #
82
- # By default, #legend_data returns nil automatically if relevant_data is set to false
83
- # or the @color attribute is nil. @color is set when the layer is rendered, so legends
84
- # must be rendered AFTER layers.
85
- def legend_data
86
- if relevant_data? && @color
87
- {:title => title,
88
- :color => @color,
89
- :priority => :normal}
90
- else
91
- nil
92
- end
93
- end
94
-
95
- # Returns the value of relevant_data
96
- def relevant_data?
97
- @relevant_data
98
- end
99
-
100
- # The highest data point on this layer, or nil if relevant_data == false
101
- def top_value
102
- @relevant_data ? points.sort.last : nil
103
- end
104
-
105
- # The lowest data point on this layer, or nil if relevant_data == false
106
- def bottom_value
107
- @relevant_data ? points.sort.first: nil
84
+ # By default, #legend_data returns nil automatically if relevant_data is set to false
85
+ # or the @color attribute is nil. @color is set when the layer is rendered, so legends
86
+ # must be rendered AFTER layers.
87
+ def legend_data
88
+ if relevant_data? && @color
89
+ {:title => title,
90
+ :color => @color,
91
+ :priority => :normal}
92
+ else
93
+ nil
108
94
  end
95
+ end
96
+
97
+ # Returns the value of relevant_data
98
+ def relevant_data?
99
+ @relevant_data
100
+ end
101
+
102
+ # The highest data point on this layer, or nil if relevant_data == false
103
+ def top_value
104
+ @relevant_data ? points.sort.last : nil
105
+ end
106
+
107
+ # The lowest data point on this layer, or nil if relevant_data == false
108
+ def bottom_value
109
+ @relevant_data ? points.sort.first: nil
110
+ end
109
111
 
110
- protected
111
- def setup_variables(options = {}) # :nodoc:
112
- @color = (preferred_color || options.delete(:color))
113
- @width, @height = options.delete(:size)
114
- @min_value, @max_value = options[:min_value], options[:max_value]
115
- @opacity = options[:opacity] || 1.0
116
- @complexity = options[:complexity]
117
- end
112
+ protected
113
+ # Sets up several variables that almost every graph layer will need to render
114
+ # itself.
115
+ def setup_variables(options = {})
116
+ @color = (preferred_color || options.delete(:color))
117
+ @width, @height = options.delete(:size)
118
+ @min_value, @max_value = options[:min_value], options[:max_value]
119
+ @opacity = options[:opacity] || 1.0
120
+ @complexity = options[:complexity]
121
+ end
118
122
 
119
- def generate_coordinates(options = {}) # :nodoc:
120
- options[:point_distance] = width / (points.size - 1).to_f
121
-
122
- coords = (0...points.size).map do |idx|
123
- x_coord = options[:point_distance] * idx
123
+ # Optimistic generation of coordinates for layer to use. These coordinates are
124
+ # just a best guess, and can be overridden or thrown away (for example, this is overridden
125
+ # in pie charting and bar charts).
126
+ def generate_coordinates(options = {})
127
+ options[:point_distance] = width / (points.size - 1).to_f
128
+
129
+ coords = (0...points.size).map do |idx|
130
+ x_coord = options[:point_distance] * idx
124
131
 
125
- relative_percent = ((points[idx] == min_value) ? 0 : ((points[idx] - min_value) / (max_value - min_value).to_f))
126
- y_coord = (height - (height * relative_percent))
132
+ relative_percent = ((points[idx] == min_value) ? 0 : ((points[idx] - min_value) / (max_value - min_value).to_f))
133
+ y_coord = (height - (height * relative_percent))
127
134
 
128
- [x_coord, y_coord]
129
- end
130
-
131
- coords
135
+ [x_coord, y_coord]
132
136
  end
133
137
 
134
- # Converts a percentage into a pixel value, related to the height.
135
- def relative(pct)
136
- @height * (pct / 100.to_f)
137
- end
138
+ coords
139
+ end
140
+
141
+ # Converts a percentage into a pixel value, relative to the height.
142
+ #
143
+ # Example:
144
+ # relative(5) # On a 100px high layer, this returns 5. 200px high layer, this returns 10, etc.
145
+ def relative(pct)
146
+ @height * (pct / 100.to_f)
147
+ end
138
148
 
139
- def stringify_coords(coords) # :nodoc:
140
- coords.map { |c| c.join(',') }
141
- end
142
- end
149
+ # Some SVG elements take a long string of multiple coordinates. This is here
150
+ # to make that a little easier.
151
+ def stringify_coords(coords) # :nodoc:
152
+ coords.map { |c| c.join(',') }
153
+ end
143
154
  end
144
- end
155
+
156
+ end # scruffy::layers
@@ -1,13 +1,19 @@
1
- module Scruffy
2
- module Layers
3
- class Line < Base
4
- def draw(svg, coords, options={})
5
- svg.polyline( :points => stringify_coords(coords).join(' '), :fill => 'none',
6
- :stroke => color.to_s, 'stroke-width' => relative(2) )
1
+ module Scruffy::Layers
2
+ # ==Scruffy::Layers::Line
3
+ #
4
+ # Author:: Brasten Sager
5
+ # Date:: August 7th, 2006
6
+ #
7
+ # Line graph.
8
+ class Line < Base
9
+
10
+ # Renders line graph.
11
+ def draw(svg, coords, options={})
12
+ svg.polyline( :points => stringify_coords(coords).join(' '), :fill => 'none',
13
+ :stroke => color.to_s, 'stroke-width' => relative(2) )
7
14
 
8
- coords.each { |coord| svg.circle( :cx => coord.first, :cy => coord.last, :r => relative(2.5),
9
- :fill => color.to_s ) }
10
- end
15
+ coords.each { |coord| svg.circle( :cx => coord.first, :cy => coord.last, :r => relative(2.5),
16
+ :fill => color.to_s ) }
11
17
  end
12
18
  end
13
19
  end
@@ -0,0 +1,38 @@
1
+ module Scruffy
2
+ module Layers
3
+ # Experimental, do not use.
4
+ class SparklineBar < Base
5
+
6
+ def draw(svg, coords, options = {})
7
+ zero_point = @height / 2.0
8
+
9
+ coords.each do |coord|
10
+ x, y, bar_height = (coord.first-(@bar_width * 0.5)), coord.last, (height - coord.last)
11
+
12
+ bar_color = (y > zero_point) ? 'black' : 'red'
13
+ bar_height = (bar_height - zero_point)
14
+
15
+ #y = (bar_height < 0) ?
16
+
17
+ # svg.rect( :x => x, :y => zero_point, :width => @bar_width, :height => ,
18
+ # :fill => bar_color, :stroke => 'none', 'style' => "opacity: #{opacity}" )
19
+ end
20
+ end
21
+
22
+ protected
23
+ def generate_coordinates(options = {})
24
+ @bar_width = (width / points.size) * 0.9
25
+ options[:point_distance] = (width - (width / points.size)) / (points.size - 1).to_f
26
+
27
+ coords = (0...points.size).map do |idx|
28
+ x_coord = (options[:point_distance] * idx) + (width / points.size * 0.5)
29
+
30
+ relative_percent = ((points[idx] == min_value) ? 0 : ((points[idx] - min_value) / (max_value - min_value).to_f))
31
+ y_coord = (height - (height * relative_percent))
32
+ [x_coord, y_coord]
33
+ end
34
+ coords
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,75 +1,90 @@
1
- module Scruffy
2
- module Layers
3
- class Stacked < Base
4
- include Scruffy::Helpers::LayerContainer
5
-
6
- def initialize(options={}, &block)
7
- super(options)
1
+ module Scruffy::Layers
2
+ # ==Scruffy::Layers::Stacked
3
+ #
4
+ # Author:: Brasten Sager
5
+ # Date:: August 12th, 2006
6
+ #
7
+ # Provides a generic way for stacking graphs. This may or may not
8
+ # do what you'd expect under every situation, but it at least kills
9
+ # a couple birds with one stone (stacked bar graphs and stacked area
10
+ # graphs work fine).
11
+ class Stacked < Base
12
+ include Scruffy::Helpers::LayerContainer
13
+
14
+ # Returns new Stacked graph.
15
+ #
16
+ # You can provide a block for easily adding layers during (just after) initialization.
17
+ # Example:
18
+ # Stacked.new do |stacked|
19
+ # stacked << Scruffy::Layers::Line.new( ... )
20
+ # stacked.add(:bar, 'My Bar', [...])
21
+ # end
22
+ #
23
+ # The initialize method passes itself to the block, and since stacked is a LayerContainer,
24
+ # layers can be added just as if they were being added to Graph.
25
+ def initialize(options={}, &block)
26
+ super(options)
8
27
 
9
- block.call(self) # Allow for population of data with a block during initialization.
10
- end
28
+ block.call(self) # Allow for population of data with a block during initialization.
29
+ end
30
+
31
+ # Overrides Base#render to fiddle with layers' points to achieve a stacked effect.
32
+ def render(svg, options = {})
33
+ current_points = points.dup
11
34
 
12
- # Builds SVG code for this graph using the provided Builder object.
13
- # This method actually generates data needed by this graph, then passes the
14
- # rendering responsibilities to Base#draw.
15
- #
16
- # svg:: a Builder object used to create SVG code.
17
- def render(svg, options = {})
18
- current_points = points.dup
35
+ layers.each do |layer|
36
+ real_points = layer.points
37
+ layer.points = current_points
38
+ layer_options = options.dup
39
+ layer_options[:color] = layer.preferred_color || layer.color || options[:theme].next_color
40
+ layer.render(svg, layer_options)
41
+ options.merge(layer_options)
42
+ layer.points = real_points
19
43
 
20
- layers.each do |layer|
21
- real_points = layer.points
22
- layer.points = current_points
23
- layer_options = options.dup
24
- layer_options[:color] = layer.preferred_color || layer.color || options[:theme].next_color
25
- layer.render(svg, layer_options)
26
- options.merge(layer_options)
27
- layer.points = real_points
28
-
29
- layer.points.each_with_index { |val, idx| current_points[idx] -= val }
30
- end
44
+ layer.points.each_with_index { |val, idx| current_points[idx] -= val }
31
45
  end
46
+ end
32
47
 
33
- def legend_data
34
- if relevant_data?
35
- retval = []
36
- layers.each do |layer|
37
- retval << layer.legend_data
38
- end
39
- retval
40
- else
41
- nil
48
+ # A stacked graph has many data sets. Return legend information for all of them.
49
+ def legend_data
50
+ if relevant_data?
51
+ retval = []
52
+ layers.each do |layer|
53
+ retval << layer.legend_data
42
54
  end
55
+ retval
56
+ else
57
+ nil
43
58
  end
59
+ end
44
60
 
45
- # The highest data point on this layer, or nil if relevant_data == false
46
- def top_value
47
- @relevant_data ? points.sort.last : nil
61
+ # The highest data point on this layer, or nil if relevant_data == false
62
+ def top_value
63
+ @relevant_data ? points.sort.last : nil
64
+ end
65
+
66
+ def points
67
+ longest_arr = layers.inject(nil) do |longest, layer|
68
+ longest = layer.points if (longest.nil? || longest.size < layer.points.size)
48
69
  end
49
-
50
- def points
51
- longest_arr = layers.inject(nil) do |longest, layer|
52
- longest = layer.points if (longest.nil? || longest.size < layer.points.size)
53
- end
54
-
55
- summed_points = (0...longest_arr.size).map do |idx|
56
- layers.inject(nil) do |sum, layer|
57
- if sum.nil? && !layer.points[idx].nil?
58
- sum = layer.points[idx]
59
- elsif !layer.points[idx].nil?
60
- sum += layer.points[idx]
61
- end
62
-
63
- sum
70
+
71
+ summed_points = (0...longest_arr.size).map do |idx|
72
+ layers.inject(nil) do |sum, layer|
73
+ if sum.nil? && !layer.points[idx].nil?
74
+ sum = layer.points[idx]
75
+ elsif !layer.points[idx].nil?
76
+ sum += layer.points[idx]
64
77
  end
78
+
79
+ sum
65
80
  end
66
-
67
- summed_points
68
81
  end
69
82
 
70
- def points=(val)
71
- throw ArgumentsError, "Stacked layers cannot accept points, only other layers."
72
- end
83
+ summed_points
84
+ end
85
+
86
+ def points=(val)
87
+ throw ArgumentsError, "Stacked layers cannot accept points, only other layers."
73
88
  end
74
89
  end
75
90
  end