ramhoj-scruffy 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/README.txt +66 -0
  2. data/lib/scruffy.rb +25 -0
  3. data/lib/scruffy/components.rb +21 -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/formatters.rb +213 -0
  16. data/lib/scruffy/graph.rb +190 -0
  17. data/lib/scruffy/graph_state.rb +24 -0
  18. data/lib/scruffy/helpers.rb +12 -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/layers.rb +24 -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/rasterizers.rb +14 -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/renderers.rb +22 -0
  39. data/lib/scruffy/renderers/base.rb +93 -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/themes.rb +156 -0
  49. data/lib/scruffy/version.rb +3 -0
  50. data/spec/output/array.svg +47 -0
  51. data/spec/output/hash.svg +47 -0
  52. data/spec/scruffy/graph_spec.rb +175 -0
  53. data/spec/scruffy/layers/base_spec.rb +30 -0
  54. data/spec/spec_helper.rb +8 -0
  55. metadata +155 -0
@@ -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,21 @@
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
+ # Renders scatter graph.
11
+ def draw(svg, coords, options={})
12
+ svg.g(:class => 'shadow', :transform => "translate(#{relative(0.5)}, #{relative(0.5)})") {
13
+ coords.each { |coord| svg.circle( :cx => coord.first, :cy => coord.last + relative(0.9), :r => relative(2),
14
+ :style => "stroke-width: #{relative(2)}; stroke: black; opacity: 0.35;" ) }
15
+ }
16
+
17
+ coords.each { |coord| svg.circle( :cx => coord.first, :cy => coord.last, :r => relative(2),
18
+ :style => "stroke-width: #{relative(2)}; stroke: #{color.to_s}; fill: #{color.to_s}" ) }
19
+ end
20
+ end
21
+ end
@@ -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.dup
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
@@ -0,0 +1,14 @@
1
+ # ===Scruffy Rasterizers
2
+ #
3
+ # Author:: Brasten Sager
4
+ # Date:: August 10th, 2006
5
+ #
6
+ # These handle the job of rasterizing SVG images to other image formats.
7
+ # At the moment, only RMagickRasterizer exists, but others may soon follow.
8
+ #
9
+ # I'm somewhat interesting in finding a way to integrate Apache Batik, as it's
10
+ # SVG rendering seems to be superior to ImageMagick's.
11
+ module Scruffy::Rasterizers; end
12
+
13
+ require 'scruffy/rasterizers/rmagick_rasterizer.rb'
14
+ require 'scruffy/rasterizers/batik_rasterizer.rb'
@@ -0,0 +1,39 @@
1
+ module Scruffy::Rasterizers
2
+ # == Scruffy::Rasterizers::BatikRasterizer
3
+ #
4
+ # Author:: Brasten Sager
5
+ # Date:: August 14th, 2006
6
+ #
7
+ # Purely experimental. Can be used to rasterize SVG graphs with
8
+ # Apache Batik.
9
+ class BatikRasterizer
10
+ # Returns new BatikRasterizer.
11
+ #
12
+ # Options:
13
+ # command:: Command needed to execute Batik. (ie: 'java -classpath {...}')
14
+ # temp_folder:: Folder for storing temporary files being passed between Scruffy and Batik.
15
+ def initialize(options={})
16
+ @command = options[:command]
17
+ @temp_folder = options[:temp_folder]
18
+ end
19
+
20
+ # Rasterize graph.
21
+ #
22
+ # Options:
23
+ # as:: Image format to generate (PNG, JPG, et al.)
24
+ def rasterize(svg, options={})
25
+ File.open(@temp_folder + '/temp_svg.svg', 'w') { |file|
26
+ file.write(svg)
27
+ }
28
+
29
+ `#{@command} -d #{@temp_folder} -m image/#{options[:as].downcase} #{@temp_folder}/temp_svg.svg`
30
+
31
+ image = ""
32
+ File.open(@temp_folder + '/temp_svg.' + options[:as].downcase, 'r') { |file|
33
+ image = file.read
34
+ }
35
+
36
+ image
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,27 @@
1
+ module Scruffy::Rasterizers
2
+
3
+ # == RMagickRasterizer
4
+ #
5
+ # Author:: Brasten Sager
6
+ # Date:: August 14th, 2006
7
+ #
8
+ # The RMagickRasterizer converts SVG graphs to images using ImageMagick.
9
+ class RMagickRasterizer
10
+ def rasterize(svg, options={})
11
+ # I know this seems weird, I'm open to suggestions.
12
+ # I didn't want RMagick required unless absolutely necessary.
13
+ require 'rmagick'
14
+
15
+ image = Magick::Image::from_blob(svg)[0]
16
+
17
+ # Removed for now
18
+ # image.resize!(options[:size][0], options[:size][1], Magick::BoxFilter, 1.25) if options[:actual_size]
19
+
20
+ if options[:to]
21
+ image.write(options[:to]) { self.format = options[:as] }
22
+ end
23
+
24
+ image.to_blob { self.format = options[:as] }
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,22 @@
1
+ # ===Scruffy Renderers
2
+ #
3
+ # Author:: Brasten Sager
4
+ # Date:: August 14th, 2006
5
+ #
6
+ # Renderers piece the entire graph together from a collection
7
+ # of components. Creating new renderers allows you to create
8
+ # entirely new layouts for your graphs.
9
+ #
10
+ # Scruffy::Renderers::Base contains the basic functionality needed
11
+ # by a layout. The easiest way to create a new layout is by subclassing
12
+ # Base.
13
+ module Scruffy::Renderers; end
14
+
15
+ require 'scruffy/renderers/base'
16
+ require 'scruffy/renderers/empty'
17
+ require 'scruffy/renderers/standard'
18
+ require 'scruffy/renderers/reversed'
19
+ require 'scruffy/renderers/cubed'
20
+ require 'scruffy/renderers/split'
21
+ require 'scruffy/renderers/cubed3d'
22
+ require 'scruffy/renderers/pie'
@@ -0,0 +1,93 @@
1
+ require 'builder'
2
+ module Scruffy::Renderers
3
+ # ===Scruffy::Renderers::Base
4
+ #
5
+ # Author:: Brasten Sager
6
+ # Date:: August 14th, 2006
7
+ #
8
+ # Provides all the base functionality needed to render a graph, but
9
+ # does not provide a default layout.
10
+ #
11
+ # For a basic layout, see Scruffy::Renderers::Standard.
12
+ class Base
13
+ include Scruffy::Helpers::Canvas
14
+
15
+ attr_accessor :options
16
+
17
+ def initialize(options = {})
18
+ self.components = []
19
+ self.options = options
20
+ define_layout
21
+ end
22
+
23
+ # Renders the graph and all components.
24
+ def render(options = {})
25
+ options[:graph_id] ||= 'scruffy_graph'
26
+ options[:complexity] ||= (global_complexity || :normal)
27
+
28
+ # Allow subclasses to muck with components prior to renders.
29
+ rendertime_renderer = self.clone
30
+ rendertime_renderer.instance_eval { before_render if respond_to?(:before_render) }
31
+
32
+ svg = Builder::XmlMarkup.new(:indent => 2)
33
+ svg.instruct!
34
+ svg.instruct! 'DOCTYPE', 'svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd" type'
35
+ svg.svg(:xmlns => "http://www.w3.org/2000/svg", 'xmlns:xlink' => "http://www.w3.org/1999/xlink", :width => options[:size].first, :height => options[:size].last) {
36
+ svg.g(:id => options[:graph_id]) {
37
+ rendertime_renderer.components.each do |component|
38
+ component.render(svg,
39
+ bounds_for( options[:size], component.position, component.size ),
40
+ options)
41
+ end
42
+ }
43
+ }
44
+ svg.target!
45
+ end
46
+
47
+ def before_render
48
+ if self.options
49
+ set_values(self.options[:values]) if (self.options[:values] && self.options[:values] != :hide)
50
+ hide_grid if (self.options[:grid] == :hide)
51
+ hide_values if (self.options[:values] == :hide)
52
+ hide_labels if (self.options[:labels] == :hide)
53
+ end
54
+ end
55
+
56
+ def method_missing(sym, *args)
57
+ self.options = {} if self.options.nil?
58
+
59
+ if args.size > 0
60
+ self.options[sym] = args[0]
61
+ else
62
+ return self.options[sym]
63
+ end
64
+ end
65
+
66
+ protected
67
+ def hide_grid
68
+ grids.each { |grid| grid.visible = false }
69
+ end
70
+
71
+ def set_values(val)
72
+ values.each { |value| value.markers = val }
73
+ grids.each { |grid| grid.markers = val }
74
+ end
75
+
76
+ def hide_values
77
+ values.each { |value| value.visible = false }
78
+ end
79
+
80
+ def hide_labels
81
+ labels.each { |label| label.visible = false }
82
+ end
83
+
84
+ private
85
+ def global_complexity
86
+ if Kernel.const_defined? "SCRUFFY_COMPLEXITY"
87
+ SCRUFFY_COMPLEXITY
88
+ else
89
+ nil
90
+ end
91
+ end
92
+ end # base
93
+ end