srawlins-scruffy 0.2.9

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 (56) hide show
  1. data/CHANGES.txt +115 -0
  2. data/LICENCE.txt +20 -0
  3. data/Manifest.txt +74 -0
  4. data/README.txt +66 -0
  5. data/lib/scruffy.rb +30 -0
  6. data/lib/scruffy/components.rb +21 -0
  7. data/lib/scruffy/components/background.rb +24 -0
  8. data/lib/scruffy/components/base.rb +57 -0
  9. data/lib/scruffy/components/data_markers.rb +42 -0
  10. data/lib/scruffy/components/graphs.rb +51 -0
  11. data/lib/scruffy/components/grid.rb +54 -0
  12. data/lib/scruffy/components/label.rb +17 -0
  13. data/lib/scruffy/components/legend.rb +147 -0
  14. data/lib/scruffy/components/style_info.rb +22 -0
  15. data/lib/scruffy/components/title.rb +19 -0
  16. data/lib/scruffy/components/value_markers.rb +25 -0
  17. data/lib/scruffy/components/viewport.rb +37 -0
  18. data/lib/scruffy/formatters.rb +230 -0
  19. data/lib/scruffy/graph.rb +205 -0
  20. data/lib/scruffy/graph_state.rb +29 -0
  21. data/lib/scruffy/helpers.rb +13 -0
  22. data/lib/scruffy/helpers/canvas.rb +41 -0
  23. data/lib/scruffy/helpers/layer_container.rb +119 -0
  24. data/lib/scruffy/helpers/marker_helper.rb +25 -0
  25. data/lib/scruffy/helpers/meta.rb +5 -0
  26. data/lib/scruffy/helpers/point_container.rb +99 -0
  27. data/lib/scruffy/layers.rb +28 -0
  28. data/lib/scruffy/layers/all_smiles.rb +137 -0
  29. data/lib/scruffy/layers/area.rb +46 -0
  30. data/lib/scruffy/layers/average.rb +67 -0
  31. data/lib/scruffy/layers/bar.rb +68 -0
  32. data/lib/scruffy/layers/base.rb +211 -0
  33. data/lib/scruffy/layers/line.rb +46 -0
  34. data/lib/scruffy/layers/pie.rb +123 -0
  35. data/lib/scruffy/layers/pie_slice.rb +119 -0
  36. data/lib/scruffy/layers/scatter.rb +29 -0
  37. data/lib/scruffy/layers/sparkline_bar.rb +39 -0
  38. data/lib/scruffy/layers/stacked.rb +87 -0
  39. data/lib/scruffy/rasterizers.rb +14 -0
  40. data/lib/scruffy/rasterizers/batik_rasterizer.rb +39 -0
  41. data/lib/scruffy/rasterizers/rmagick_rasterizer.rb +27 -0
  42. data/lib/scruffy/renderers.rb +24 -0
  43. data/lib/scruffy/renderers/base.rb +95 -0
  44. data/lib/scruffy/renderers/cubed.rb +44 -0
  45. data/lib/scruffy/renderers/cubed3d.rb +53 -0
  46. data/lib/scruffy/renderers/empty.rb +22 -0
  47. data/lib/scruffy/renderers/pie.rb +20 -0
  48. data/lib/scruffy/renderers/reversed.rb +17 -0
  49. data/lib/scruffy/renderers/sparkline.rb +10 -0
  50. data/lib/scruffy/renderers/split.rb +48 -0
  51. data/lib/scruffy/renderers/standard.rb +37 -0
  52. data/lib/scruffy/themes.rb +175 -0
  53. data/lib/scruffy/version.rb +9 -0
  54. data/test/graph_creation_test.rb +286 -0
  55. data/test/test_helper.rb +2 -0
  56. metadata +132 -0
@@ -0,0 +1,46 @@
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
+ #
12
+ # Options:
13
+ # See initialize()
14
+ def draw(svg, coords, options={})
15
+
16
+ # Include options provided when the object was created
17
+ options.merge!(@options)
18
+
19
+ stroke_width = (options[:relativestroke]) ? relative(options[:stroke_width]) : options[:stroke_width]
20
+ style = (options[:style]) ? options[:style] : ''
21
+
22
+ if options[:shadow]
23
+ svg.g(:class => 'shadow', :transform => "translate(#{relative(0.5)}, #{relative(0.5)})") {
24
+ svg.polyline( :points => stringify_coords(coords).join(' '), :fill => 'transparent',
25
+ :stroke => 'black', 'stroke-width' => stroke_width,
26
+ :style => 'fill-opacity: 0; stroke-opacity: 0.35' )
27
+
28
+ if options[:dots]
29
+ coords.each { |coord| svg.circle( :cx => coord.first, :cy => coord.last + relative(0.9), :r => stroke_width,
30
+ :style => "stroke-width: #{stroke_width}; stroke: black; opacity: 0.35;" ) }
31
+ end
32
+ }
33
+ end
34
+
35
+
36
+ svg.polyline( :points => stringify_coords(coords).join(' '), :fill => 'none', :stroke => @color.to_s,
37
+ 'stroke-width' => stroke_width, :style => style )
38
+
39
+ if options[:dots]
40
+ coords.each { |coord| svg.circle( :cx => coord.first, :cy => coord.last, :r => stroke_width,
41
+ :style => "stroke-width: #{stroke_width}; stroke: #{color.to_s}; fill: #{color.to_s}" ) }
42
+ end
43
+
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,123 @@
1
+ module Scruffy::Layers
2
+ # ==Scruffy::Layers::Pie
3
+ #
4
+ # Author:: A.J. Ostman
5
+ # Date:: August 15, 2006
6
+ #
7
+ # Provides a container for pie slice.
8
+ class Pie < Base
9
+ include Scruffy::Helpers::LayerContainer
10
+
11
+ # Basic Example:
12
+ #
13
+ # graph = Scruffy::Graph.new
14
+ # graph.title = "Snack Preference"
15
+ # graph.renderer = Scruffy::Renderers::Pie.new
16
+ # graph.add :pie, 'Example', {'Apples' => 90, 'Orange' => 60, 'Taco' => 30}
17
+ #
18
+ # Or, using a block to add slices:
19
+ #
20
+ # graph = Scruffy::Graph.new
21
+ # graph.title = "Snack Preference"
22
+ # graph.renderer = Scruffy::Renderers::Pie.new
23
+ # graph.add :pie do |pie|
24
+ # pie.add :pie_slice, 'Apple', [90]
25
+ # pie.add :pie_slice, 'Orange', [60]
26
+ # pie.add :pie_slice, 'Taco', [30]
27
+ # end
28
+ #
29
+ # Another Example:
30
+ # graph.title = "Scruff-Pac!"
31
+ # graph.renderer = Scruffy::Renderers::Pie.new
32
+ # graph.add :pie, :diameter => 40, :degree_offset => 30 do |pie|
33
+ # pie.add :pie_slice, '', [160], :preferred_color => "yellow", :shadow => true,
34
+ # :shadow_x => -1, :shadow_y => 1, :shadow_color=>"black", :shadow_opacity => 0.4
35
+ # pie.add :pie_slice, '', [50], :preferred_color => "green", :explode => 5, :diameter => 20,
36
+ # :shadow => true, :shadow_x => -1, :shadow_y => 1, :shadow_color => "black", :shadow_opacity => 0.4
37
+ # end
38
+ #
39
+ # graph.add :pie, :diameter => 3, :center_x => 48, :center_y=> 37, :degree_offset => 20 do |pie|
40
+ # pie.add :pie_slice, '', [160], :preferred_color => "blue", :stroke => "black"
41
+ # end
42
+
43
+
44
+ # Setup Constants
45
+ RADIANS = Math::PI/180
46
+
47
+ attr_accessor :diameter
48
+ attr_accessor :percent_used
49
+ attr_accessor :degree_offset
50
+ attr_accessor :scaler
51
+ attr_accessor :center_x, :center_y
52
+
53
+
54
+ # The initialize method passes itself to the block, and since Pie is a
55
+ # LayerContainer, layers (pie slice) can be added just as if they were being
56
+ # added to Graph.
57
+ def initialize(options = {}, &block)
58
+ super(options)
59
+
60
+ # Allow for population of data with a block during initialization.
61
+ if block
62
+ block.call(self)
63
+ else
64
+ # Otherwise, just iterate over the points, adding the slices
65
+ if @points.class == Hash
66
+ @points.keys.each {|k|
67
+ self.add :pie_slice, k.to_s, [@points[k]]}
68
+ end
69
+ if @points.class == Array
70
+ @points.each {|v|
71
+ self.add :pie_slice, '', [v]}
72
+ end
73
+ end
74
+ end
75
+
76
+
77
+ # Overrides Base#render to fiddle with layers' points to achieve a stacked
78
+ # effect.
79
+ def render(svg, options = {})
80
+ # #current_points = points.dup
81
+
82
+ @scaler = 1
83
+ total = 0
84
+
85
+ layers.each do |layer|
86
+ total += layer.sum_values
87
+ end
88
+
89
+ @scaler = 100.0 / total
90
+
91
+ @percent_used = 30
92
+
93
+ layers.each do |layer|
94
+ layer_options = options.dup
95
+ layer_options = layer_options.merge(@options)
96
+ layer_options = layer_options.merge(layer.options)
97
+ layer_options[:scaler] = @scaler
98
+ layer_options[:percent_used] = @percent_used
99
+ @percent_used += @scaler * layer.sum_values
100
+ layer_options[:color] = layer.preferred_color || layer.color || options[:theme].next_color
101
+
102
+ layer.render(svg, layer_options)
103
+ end
104
+ end
105
+
106
+ # A stacked graph has many data sets. Return legend information for all of them.
107
+ def legend_data
108
+ if relevant_data?
109
+ retval = []
110
+ layers.each do |layer|
111
+ retval << layer.legend_data
112
+ end
113
+ retval
114
+ else
115
+ nil
116
+ end
117
+ end
118
+
119
+ def points=(val)
120
+ throw ArgumentsError, "Pie layers cannot accept points, only pie slices."
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,119 @@
1
+ module Scruffy::Layers
2
+ # ==Scruffy::Layers::PieSlice
3
+ #
4
+ # Author:: A.J. Ostman
5
+ # Date:: August 14th, 2006
6
+ #
7
+ # Basic Pie Chart Slice..
8
+
9
+ class PieSlice < Base
10
+
11
+ # Setup Constants
12
+ RADIANS = Math::PI / 180
13
+ MARKER_OFFSET_RATIO = 1.2
14
+ MARKER_FONT_SIZE = 6
15
+
16
+ attr_accessor :diameter
17
+ attr_accessor :percent_used
18
+ attr_accessor :degree_offset
19
+ attr_accessor :scaler
20
+ attr_accessor :center_x, :center_y
21
+
22
+ def draw(svg, coords, options = {})
23
+ # Scaler is the multiplier to normalize the values to a percentage across
24
+ # the Pie Chart
25
+ @scaler = options[:scaler] || 1
26
+
27
+ # Degree Offset is degrees by which the pie chart is twisted when it
28
+ # begins
29
+ @degree_offset = options[:degree_offset] || @options[:degree_offset] || 0
30
+
31
+ # Percent Used keeps track of where in the pie chart we are
32
+ @percent_used = options[:percent_used] || @options[:percent_used] || 0
33
+
34
+ # Diameter of the pie chart defaults to 80% of the height
35
+ @diameter = relative(options[:diameter]) || relative(@options[:diameter]) || relative(80.0)
36
+
37
+ # Stroke
38
+ stroke = options[:stroke] || @options[:stroke] || "none"
39
+
40
+ # Shadow
41
+ shadow = options[:shadow] || @options[:shadow_] || false
42
+ shadow_x = relative(options[:shadow_x]) || relative(@options[:shadow_x]) || relative(-0.5)
43
+ shadow_y = relative(options[:shadow_y]) || relative(@options[:shadow_y]) || relative(0.5)
44
+ shadow_color = options[:shadow_color] || @options[:shadow_color] || "white"
45
+ shadow_opacity = options[:shadow_opacity] || @options[:shadow_opacity] || 0.06
46
+
47
+ # Coordinates for the center of the pie chart.
48
+ @center_x = relative_width(options[:center_x]) || relative_width(@options[:center_x]) || relative_width(50)
49
+ @center_y = relative_height(options[:center_y]) || relative_height(@options[:center_y]) || relative_height(50)
50
+ radius = @diameter / 2.0
51
+
52
+ # Graphing calculated using percent of graph. We later multiply by 3.6 to
53
+ # convert to 360 degree system.
54
+ percent = @scaler * sum_values
55
+
56
+
57
+ # Calculate the Radian Start Point
58
+ radians_start = ((@percent_used * 3.6) + @degree_offset) * RADIANS
59
+ # Calculate the Radian End Point
60
+ radians_end = ((@percent_used + percent) * 3.6 + @degree_offset) * RADIANS
61
+
62
+ radians_mid_point = radians_start + ((radians_end - radians_start) / 2)
63
+
64
+ if options[:explode]
65
+ @center_x = @center_x + (Math.sin(radians_mid_point) * relative(options[:explode]))
66
+ @center_y = @center_y - (Math.cos(radians_mid_point) * relative(options[:explode]))
67
+ end
68
+
69
+
70
+ # Calculate the beginning coordinates
71
+ x_start = @center_x + (Math.sin(radians_start) * radius)
72
+ y_start = @center_y - (Math.cos(radians_start) * radius)
73
+
74
+ # Calculate the End Coords
75
+ x_end = @center_x + (Math.sin(radians_end) * radius)
76
+ y_end = @center_y - (Math.cos(radians_end) * radius)
77
+
78
+
79
+
80
+ # If percentage is really really close to 100% then draw a circle instead!
81
+ if percent >= 99.9999
82
+
83
+ if shadow
84
+ svg.circle(:cx => "#{@center_x + shadow_x}", :cy => "#{@center_y + shadow_y}", :r=>"#{radius}",:stroke => "none",
85
+ :fill => shadow_color.to_s, :style => "fill-opacity: #{shadow_opacity.to_s};")
86
+ end
87
+
88
+ svg.circle(:cx => "#{@center_x}", :cy => "#{@center_y}", :r=>"#{radius}",:stroke => stroke, :fill => color.to_s)
89
+
90
+ else
91
+ if shadow
92
+ svg.path(:d => "M#{@center_x + shadow_x},#{@center_y + shadow_y} L#{x_start + shadow_x},#{y_start + shadow_y} A#{radius},#{radius} 0, #{percent >= 50 ? '1' : '0'}, 1, #{x_end + shadow_x} #{y_end + shadow_y} Z",
93
+ :fill => shadow_color.to_s, :style => "fill-opacity: #{shadow_opacity.to_s};")
94
+ end
95
+
96
+ svg.path(:d => "M#{@center_x},#{@center_y} L#{x_start},#{y_start} A#{radius},#{radius} 0, #{percent >= 50 ? '1' : '0'}, 1, #{x_end} #{y_end} Z",
97
+ :stroke => stroke, :fill => color.to_s)
98
+ end
99
+
100
+ text_x = @center_x + (Math.sin(radians_mid_point) * radius * MARKER_OFFSET_RATIO)
101
+ text_y = @center_y - (Math.cos(radians_mid_point) * radius * MARKER_OFFSET_RATIO)
102
+
103
+ svg.text("#{sprintf('%d', percent)}%",
104
+ :x => text_x,
105
+ :y => text_y + relative(MARKER_FONT_SIZE / 2),
106
+ 'font-size' => relative(MARKER_FONT_SIZE),
107
+ 'font-family' => options[:theme].font_family,
108
+ :fill => (options[:theme].marker || 'black').to_s,
109
+ 'text-anchor' => 'middle')
110
+ end
111
+
112
+ protected
113
+ def generate_coordinates(options = {})
114
+ # Coordinate Generation didn't make much sense here. Overridden just
115
+ # because Brasten said this would be overridden.
116
+ end
117
+ end
118
+
119
+ end
@@ -0,0 +1,29 @@
1
+ module Scruffy::Layers
2
+ # ==Scruffy::Layers::Line
3
+ #
4
+ # Author:: Mat Schaffer
5
+ # Date:: March 20th, 2007
6
+ #
7
+ # Simple scatter graph
8
+ class Scatter < Base
9
+
10
+ include Scruffy::Helpers::Marker
11
+
12
+ # Renders scatter graph.
13
+ def draw(svg, coords, options={})
14
+
15
+ options.merge!(@options)
16
+
17
+ if options[:shadow]
18
+ svg.g(:class => 'shadow', :transform => "translate(#{relative(0.5)}, #{relative(0.5)})") {
19
+ coords.each { |coord| svg.circle( :cx => coord.first, :cy => coord.last + relative(0.9), :r => relative(2),
20
+ :style => "stroke-width: #{relative(2)}; stroke: black; opacity: 0.35;" ) }
21
+ }
22
+ end
23
+
24
+ coords.each { |coord| svg.circle( :cx => coord.first, :cy => coord.last, :r => relative(2),
25
+ :style => "stroke-width: #{relative(2)}; stroke: #{color.to_s}; fill: #{color.to_s}" ) }
26
+ end
27
+ end
28
+ end
29
+
@@ -0,0 +1,39 @@
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
+ #TODO iteration by index on points. Needs to change
28
+ coords = (0...points.size).map do |idx|
29
+ x_coord = (options[:point_distance] * idx) + (width / points.size * 0.5)
30
+
31
+ relative_percent = ((points[idx] == min_value) ? 0 : ((points[idx] - min_value) / (max_value - min_value).to_f))
32
+ y_coord = (height - (height * relative_percent))
33
+ [x_coord, y_coord]
34
+ end
35
+ coords
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,87 @@
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)
27
+
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
+ #TODO ensure this works with new points
34
+ current_points = points
35
+
36
+ layers.each do |layer|
37
+ real_points = layer.points
38
+ layer.points = current_points
39
+ layer_options = options.dup
40
+ layer_options[:color] = layer.preferred_color || layer.color || options[:theme].next_color
41
+ layer.render(svg, layer_options)
42
+ options.merge(layer_options)
43
+ layer.points = real_points
44
+
45
+ layer.points.each_with_index { |val, idx| current_points[idx] -= val }
46
+ end
47
+ end
48
+
49
+ # A stacked graph has many data sets. Return legend information for all of them.
50
+ def legend_data
51
+ if relevant_data?
52
+ retval = []
53
+ layers.each do |layer|
54
+ retval << layer.legend_data
55
+ end
56
+ retval
57
+ else
58
+ nil
59
+ end
60
+ end
61
+
62
+ # TODO, special points accessor
63
+ def points
64
+ longest_arr = layers.inject(nil) do |longest, layer|
65
+ longest = layer.points if (longest.nil? || longest.size < layer.points.size)
66
+ end
67
+
68
+ summed_points = (0...longest_arr.size).map do |idx|
69
+ layers.inject(nil) do |sum, layer|
70
+ if sum.nil? && !layer.points[idx].nil?
71
+ sum = layer.points[idx]
72
+ elsif !layer.points[idx].nil?
73
+ sum += layer.points[idx]
74
+ end
75
+
76
+ sum
77
+ end
78
+ end
79
+
80
+ summed_points
81
+ end
82
+
83
+ def points=(val)
84
+ throw ArgumentsError, "Stacked layers cannot accept points, only other layers."
85
+ end
86
+ end
87
+ end