scruffy 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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