ramhoj-scruffy 0.2.6

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 (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