scruffy 0.0.11 → 0.0.12

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.
data/CHANGES CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  Version 0.1.0 will be the first release for general use.
4
4
 
5
+ == Version 0.0.12
6
+ (August 10th, 2006)
7
+ This is not a public release.
8
+
9
+ * Rearranged Layers into a better class/module arrangement.
10
+
5
11
  == Version 0.0.11
6
12
  (August 10th, 2006)
7
13
  This is not a public release.
data/lib/scruffy/graph.rb CHANGED
@@ -7,7 +7,7 @@
7
7
  # documentation in Scruffy::Graph.
8
8
  #
9
9
  # For information on creating your own graph types, see the
10
- # documentation in Scruffy::GraphLayer.
10
+ # documentation in Scruffy::Layers::Base.
11
11
  module Scruffy
12
12
 
13
13
  # ==Scruffy Graphs
@@ -20,7 +20,7 @@ module Scruffy
20
20
  #
21
21
  # Scruffy::Graph is the primary class you will use to generate your graphs. A Graph does not
22
22
  # define a graph type nor does it directly hold any data. Instead, a Graph object can be thought
23
- # of as a canvas on which other graphs are draw. (The actual graphs themselves are subclasses of Scruffy::GraphLayer)
23
+ # of as a canvas on which other graphs are draw. (The actual graphs themselves are subclasses of Scruffy::Layers::Base)
24
24
  # Despite the technical distinction, we will refer to Scruffy::Graph objects as 'graphs' and Scruffy::GraphLayers as
25
25
  # 'layers' or 'graph types.'
26
26
  #
@@ -34,7 +34,7 @@ module Scruffy
34
34
  #
35
35
  # OR
36
36
  #
37
- # graph = Scruffy::Graph.new(:title => "Monthly Profits", :theme => Scruffy::Theme::RUBY_BLOG)
37
+ # graph = Scruffy::Graph.new(:title => "Monthly Profits", :theme => Scruffy::Themes::RUBY_BLOG)
38
38
  #
39
39
  # Once you have a Graph object, you can set any Graph-level properties (title, theme, etc), or begin adding
40
40
  # graph layers. You can add a graph layer to a graph by using the Graph#add or Graph#<< methods. The two
@@ -45,8 +45,8 @@ module Scruffy
45
45
  #
46
46
  # OR
47
47
  #
48
- # graph << Scruffy::LineLayer.new(:title => 'John', :points => [100, -20, 30, 60])
49
- # graph << Scruffy::LineLayer.new(:title => 'Sara', :points => [120, 50, -80, 20])
48
+ # graph << Scruffy::Layers::Line.new(:title => 'John', :points => [100, -20, 30, 60])
49
+ # graph << Scruffy::Layers::Line.new(:title => 'Sara', :points => [120, 50, -80, 20])
50
50
  #
51
51
  # Now that we've created our graph and added a layer to it, we're ready to render! You can render the graph
52
52
  # directly to SVG with the Graph#render method:
@@ -85,15 +85,15 @@ module Scruffy
85
85
  # Returns a new Graph. You can optionally pass in a default graph type and an options hash.
86
86
  #
87
87
  # Graph.new # New graph
88
- # Graph.new(:line) # New graph with default graph type of LineLayer
88
+ # Graph.new(:line) # New graph with default graph type of Line
89
89
  # Graph.new({...}) # New graph with options.
90
90
  #
91
91
  # Options:
92
92
  #
93
93
  # title:: Graph's title
94
94
  # theme:: A theme hash to use when rendering graph
95
- # layers:: An array of GraphLayers for this graph to use
96
- # default_type:: A symbol indicating the default type of GraphLayer for this graph
95
+ # layers:: An array of Layers for this graph to use
96
+ # default_type:: A symbol indicating the default type of Layer for this graph
97
97
  # renderer:: Sets the renderer to use when rendering graph
98
98
  # marker_transformer:: Sets a transformer used to modify marker values prior to rendering
99
99
  # point_markers:: Sets the x-axis marker values
@@ -103,7 +103,7 @@ module Scruffy
103
103
  raise ArgumentError, "The arguments provided are not supported." if args.size > 0
104
104
 
105
105
  options ||= {}
106
- self.theme = Scruffy::Theme::KEYNOTE
106
+ self.theme = Scruffy::Themes::KEYNOTE
107
107
  self.layers = []
108
108
  self.renderer = Scruffy::StandardRenderer.new
109
109
 
@@ -133,8 +133,8 @@ module Scruffy
133
133
  :layers => layers, :min_value => (options[:min_value] || bottom_value), :max_value => (options[:max_value] || top_value) } ) )
134
134
  end
135
135
 
136
- # Adds a GraphLayer to the Graph. Accepts either a list of arguments used to build a new layer, or
137
- # a GraphLayer object. When passing a list of arguments, all arguments are optional, but the arguments
136
+ # Adds a Layer to the Graph. Accepts either a list of arguments used to build a new layer, or
137
+ # a Scruffy::Layers::Base-derived object. When passing a list of arguments, all arguments are optional, but the arguments
138
138
  # specified must be provided in a particular order: type (Symbol), title (String), points (Array),
139
139
  # options (Hash).
140
140
  #
@@ -144,10 +144,10 @@ module Scruffy
144
144
  #
145
145
  # graph << (:line, "John's Sales", [150, 100]) # Create and add a titled line graph
146
146
  #
147
- # graph << BarLayer.new({...}) # Adds BarLayer object to graph
147
+ # graph << Scruffy::Layers::Bar.new({...}) # Adds Bar layer to graph
148
148
  #
149
149
  def <<(*args)
150
- if args[0].kind_of?(Scruffy::GraphLayer)
150
+ if args[0].kind_of?(Scruffy::Layers::Base)
151
151
  layers << args[0]
152
152
  else
153
153
  type = args.first.is_a?(Symbol) ? args.shift : default_type
@@ -160,7 +160,7 @@ module Scruffy
160
160
  raise ArgumentError,
161
161
  'You must specify a graph type (:area, :bar, :line, etc) if you do not have a default type specified.' if type.nil?
162
162
 
163
- layer = Kernel::module_eval("Scruffy::#{to_camelcase(type.to_s)}Layer").new(options.merge({:points => points, :title => title}))
163
+ layer = Kernel::module_eval("Scruffy::Layers::#{to_camelcase(type.to_s)}").new(options.merge({:points => points, :title => title}))
164
164
  layers << layer
165
165
  end
166
166
  layer
@@ -0,0 +1,109 @@
1
+ module Scruffy
2
+ module Layers
3
+ class AllSmiles < Base
4
+ attr_accessor :standalone
5
+
6
+ def initialize(options = {})
7
+ super
8
+ @standalone = options[:standalone] || false
9
+ end
10
+
11
+ def draw(svg, coords, options={})
12
+
13
+ hero_smiley = nil
14
+ coords.each { |c| hero_smiley = c.last if (hero_smiley.nil? || c.last < hero_smiley) }
15
+
16
+ svg.defs {
17
+ svg.radialGradient(:id => 'SmileyGradient', :cx => '50%',
18
+ :cy => '50%', :r => '50%', :fx => '30%', :fy => '30%') {
19
+
20
+ svg.stop(:offset => '0%', 'stop-color' => '#FFF')
21
+ svg.stop(:offset => '20%', 'stop-color' => '#FFC')
22
+ svg.stop(:offset => '45%', 'stop-color' => '#FF3')
23
+ svg.stop(:offset => '60%', 'stop-color' => '#FF0')
24
+ svg.stop(:offset => '90%', 'stop-color' => '#990')
25
+ svg.stop(:offset => '100%', 'stop-color' => '#220')
26
+ }
27
+ svg.radialGradient(:id => 'HeroGradient', :cx => '50%',
28
+ :cy => '50%', :r => '50%', :fx => '30%', :fy => '30%') {
29
+
30
+ svg.stop(:offset => '0%', 'stop-color' => '#FEE')
31
+ svg.stop(:offset => '20%', 'stop-color' => '#F0E0C0')
32
+ svg.stop(:offset => '45%', 'stop-color' => '#8A2A1A')
33
+ svg.stop(:offset => '60%', 'stop-color' => '#821')
34
+ svg.stop(:offset => '90%', 'stop-color' => '#210')
35
+ }
36
+ svg.radialGradient(:id => 'StarGradient', :cx => '50%',
37
+ :cy => '50%', :r => '50%', :fx => '30%', :fy => '30%') {
38
+
39
+ svg.stop(:offset => '0%', 'stop-color' => '#FFF')
40
+ svg.stop(:offset => '20%', 'stop-color' => '#EFEFEF')
41
+ svg.stop(:offset => '45%', 'stop-color' => '#DDD')
42
+ svg.stop(:offset => '60%', 'stop-color' => '#BBB')
43
+ svg.stop(:offset => '90%', 'stop-color' => '#888')
44
+ }
45
+ }
46
+
47
+ unless standalone
48
+ svg.polyline( :points => stringify_coords(coords).join(' '), :fill => 'none',
49
+ :stroke => '#660', 'stroke-width' => scaled(10), 'stroke-dasharray' => "#{scaled(10)}, #{scaled(10)}" )
50
+ end
51
+
52
+ coords.each do |coord|
53
+ if standalone
54
+ svg.line( :x1 => coord.first, :y1 => coord.last, :x2 => coord.first, :y2 => height, :fill => 'none',
55
+ :stroke => '#660', 'stroke-width' => scaled(10), 'stroke-dasharray' => "#{scaled(10)}, #{scaled(10)}" )
56
+ end
57
+ svg.circle( :cx => coord.first + scaled(2), :cy => coord.last + scaled(2), :r => scaled(15),
58
+ :fill => 'black', :stroke => 'none', :opacity => 0.4)
59
+ svg.circle( :cx => coord.first, :cy => coord.last, :r => scaled(15),
60
+ :fill => (complexity == :minimal ? 'yellow' : 'url(#SmileyGradient)'), :stroke => 'black', 'stroke-width' => scaled(1) )
61
+ svg.line( :x1 => (coord.first - scaled(3)),
62
+ :x2 => (coord.first - scaled(3)),
63
+ :y1 => (coord.last),
64
+ :y2 => (coord.last - scaled(7)), :stroke => 'black', 'stroke-width' => scaled(1.4) )
65
+ svg.line( :x1 => (coord.first + scaled(3)),
66
+ :x2 => (coord.first + scaled(3)),
67
+ :y1 => (coord.last),
68
+ :y2 => (coord.last - scaled(7)), :stroke => 'black', 'stroke-width' => scaled(1.4) )
69
+
70
+
71
+ percent = 1.0 - (coord.last.to_f / height.to_f)
72
+
73
+ corners = scaled(8 - (5 * percent))
74
+ anchor = scaled((20 * percent) - 5)
75
+ svg.path( :d => "M#{coord.first - scaled(9)} #{coord.last + corners} Q#{coord.first} #{coord.last + anchor} #{coord.first + scaled(9)} #{coord.last + corners}",
76
+ :stroke => 'black', 'stroke-width' => scaled(1.4), :fill => 'none' )
77
+
78
+
79
+ # top hat
80
+ if coord.last == hero_smiley
81
+ svg.ellipse(:cx => coord.first, :cy => (coord.last - scaled(13)),
82
+ :rx => scaled(17), :ry => scaled(6.5), :fill => (complexity == :minimal ? 'purple' : 'url(#HeroGradient)'), :stroke => 'black', 'stroke-width' => scaled(1.4) )
83
+
84
+ svg.path(:d => "M#{coord.first} #{coord.last - scaled(60)} " +
85
+ "L#{coord.first + scaled(10)} #{coord.last - scaled(14)} " +
86
+ "C#{coord.first + scaled(10)},#{coord.last - scaled(9)} #{coord.first - scaled(10)},#{coord.last - scaled(9)} #{coord.first - scaled(10)},#{coord.last - scaled(14)}" +
87
+ "L#{coord.first} #{coord.last - scaled(60)}",
88
+ :stroke => 'black', 'stroke-width' => scaled(1.4), :fill => (complexity == :minimal ? 'purple' : 'url(#HeroGradient)'))
89
+
90
+ svg.path(:d => "M#{coord.first - scaled(4)} #{coord.last - scaled(23)}" +
91
+ "l-#{scaled(2.5)} #{scaled(10)} l#{scaled(7.5)} -#{scaled(5)} l-#{scaled(10)} 0 l#{scaled(7.5)} #{scaled(5)} l-#{scaled(2.5)} -#{scaled(10)}", :stroke => 'none', :fill => (complexity == :minimal ? 'white': 'url(#StarGradient)') )
92
+ svg.path(:d => "M#{coord.first + scaled(2)} #{coord.last - scaled(30)}" +
93
+ "l-#{scaled(2.5)} #{scaled(10)} l#{scaled(7.5)} -#{scaled(5)} l-#{scaled(10)} 0 l#{scaled(7.5)} #{scaled(5)} l-#{scaled(2.5)} -#{scaled(10)}", :stroke => 'none', :fill => (complexity == :minimal ? 'white': 'url(#StarGradient)') )
94
+ svg.path(:d => "M#{coord.first - scaled(2)} #{coord.last - scaled(33)}" +
95
+ "l-#{scaled(1.25)} #{scaled(5)} l#{scaled(3.75)} -#{scaled(2.5)} l-#{scaled(5)} 0 l#{scaled(3.75)} #{scaled(2.5)} l-#{scaled(1.25)} -#{scaled(5)}", :stroke => 'none', :fill => 'white' )
96
+ svg.path(:d => "M#{coord.first - scaled(2.2)} #{coord.last - scaled(32.7)}" +
97
+ "l-#{scaled(1.25)} #{scaled(5)} l#{scaled(3.75)} -#{scaled(2.5)} l-#{scaled(5)} 0 l#{scaled(3.75)} #{scaled(2.5)} l-#{scaled(1.25)} -#{scaled(5)}", :stroke => 'none', :fill => (complexity == :minimal ? 'white': 'url(#StarGradient)') )
98
+ svg.path(:d => "M#{coord.first + scaled(4.5)} #{coord.last - scaled(20)}" +
99
+ "l-#{scaled(1.25)} #{scaled(5)} l#{scaled(3.75)} -#{scaled(2.5)} l-#{scaled(5)} 0 l#{scaled(3.75)} #{scaled(2.5)} l-#{scaled(1.25)} -#{scaled(5)}", :stroke => 'none', :fill => (complexity == :minimal ? 'white': 'url(#StarGradient)') )
100
+ svg.path(:d => "M#{coord.first} #{coord.last - scaled(40)}" +
101
+ "l-#{scaled(1.25)} #{scaled(5)} l#{scaled(3.75)} -#{scaled(2.5)} l-#{scaled(5)} 0 l#{scaled(3.75)} #{scaled(2.5)} l-#{scaled(1.25)} -#{scaled(5)}", :stroke => 'none', :fill => (complexity == :minimal ? 'white': 'url(#StarGradient)') )
102
+
103
+ end
104
+
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,34 @@
1
+ module Scruffy
2
+ module Layers
3
+ class Area < Base
4
+ def draw(svg, coords, options={})
5
+ points_value = "0,#{height} #{stringify_coords(coords).join(' ')} #{width},#{height}"
6
+
7
+ # Experimental, for later user.
8
+ #
9
+ # svg.defs {
10
+ # svg.filter(:id => 'MyFilter', :filterUnits => 'userSpaceOnUse', :x => 0, :y => 0, :width => 200, :height => '120') {
11
+ # svg.feGaussianBlur(:in => 'SourceAlpha', :stdDeviation => 4, :result => 'blur')
12
+ # svg.feOffset(:in => 'blur', :dx => 4, :dy => 4, :result => 'offsetBlur')
13
+ # svg.feSpecularLighting( :in => 'blur', :surfaceScale => 5, :specularConstant => '.75',
14
+ # :specularExponent => 20, 'lighting-color' => '#bbbbbb',
15
+ # :result => 'specOut') {
16
+ # svg.fePointLight(:x => '-5000', :y => '-10000', :z => '20000')
17
+ # }
18
+ #
19
+ # svg.feComposite(:in => 'specOut', :in2 => 'SourceAlpha', :operator => 'in', :result => 'specOut')
20
+ # svg.feComposite(:in => 'sourceGraphic', :in2 => 'specOut', :operator => 'arithmetic',
21
+ # :k1 => 0, :k2 => 1, :k3 => 1, :k4 => 0, :result => 'litPaint')
22
+ #
23
+ # svg.feMerge {
24
+ # svg.feMergeNode(:in => 'offsetBlur')
25
+ # svg.feMergeNode(:in => 'litPaint')
26
+ # }
27
+ # }
28
+ # }
29
+
30
+ svg.polygon(:points => points_value, :fill => color.to_s, :stroke => color.to_s, :opacity => options[:opacity])
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,54 @@
1
+ module Scruffy
2
+ module Layers
3
+ class Average < Base
4
+ def initialize(options = {})
5
+ super
6
+
7
+ @relevant_data = false
8
+ end
9
+ def draw(svg, coords, options = {})
10
+ svg.polyline( :points => coords.join(' '), :fill => 'none', :stroke => 'black',
11
+ 'stroke-width' => scaled(25), :opacity => '0.4')
12
+ end
13
+
14
+ def highest_point
15
+ nil
16
+ end
17
+
18
+ def lowest_point
19
+ nil
20
+ end
21
+
22
+ protected
23
+ def generate_coordinates(options = {})
24
+ key_layer = points.find { |layer| layer.relevant_data? }
25
+
26
+ options[:point_distance] = width / (key_layer.points.size - 1).to_f
27
+
28
+ coords = []
29
+
30
+ key_layer.points.each_with_index do |layer, idx|
31
+ sum, objects = points.inject([0, 0]) do |arr, elem|
32
+ if elem.relevant_data?
33
+ arr[0] += elem.points[idx]
34
+ arr[1] += 1
35
+ end
36
+ arr
37
+ end
38
+
39
+ average = sum / objects.to_f
40
+
41
+ x_coord = options[:point_distance] * idx
42
+
43
+ relative_percent = ((average == min_value) ? 0 : ((average - min_value) / (max_value - min_value).to_f))
44
+ y_coord = (height - (height * relative_percent))
45
+
46
+ coords << [x_coord, y_coord].join(',')
47
+ end
48
+
49
+ return coords
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,30 @@
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)
8
+
9
+ svg.rect( :x => x, :y => y, :width => @bar_width, :height => bar_height,
10
+ :fill => color.to_s, :stroke => color.to_s, :opacity => opacity )
11
+ end
12
+ end
13
+
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
18
+
19
+ coords = (0...points.size).map do |idx|
20
+ x_coord = (options[:point_distance] * idx) + (width / points.size * 0.5)
21
+
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
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,122 @@
1
+ module Scruffy
2
+ module Layers
3
+ # ==Base
4
+ #
5
+ # Author:: Brasten Sager
6
+ # Date:: August 5th, 2006
7
+ #
8
+ # Scruffy::Layers::Base contains the basic functionality needed by the various types of graphs. The Base
9
+ # class is responsible holding layer information such as the title and data points.
10
+ #
11
+ # When the graph is rendered, the graph renderer calls Base#render. Base#render sets up
12
+ # some standard information, and calculates the x,y coordinates of each data point. The draw() method,
13
+ # which should have been overridden by the current instance, is then called. The actual rendering of
14
+ # the graph takes place there.
15
+ #
16
+ # ====Create New Graph Types
17
+ #
18
+ # Assuming the information generated by Scruffy::Layers::Base is sufficient, you can create a new graph type
19
+ # simply by overriding the draw() method. See Base#draw for arguments.
20
+ #
21
+ class Base
22
+ attr_accessor :title
23
+ attr_accessor :points
24
+ attr_accessor :height, :width
25
+ attr_accessor :min_value, :max_value
26
+ attr_accessor :preferred_color, :color
27
+ attr_accessor :opacity
28
+ attr_accessor :resolver
29
+ attr_accessor :complexity
30
+ attr_accessor :relevant_data
31
+
32
+ # Returns a new Base object.
33
+ #
34
+ # Options:
35
+ # title::
36
+ # points:: Array of data points
37
+ # color:: Color used to render this graph, overrides theme color.
38
+ # relevant_data:: Rarely used - indicates the data on this graph should not
39
+ # included in any graph data aggregations, such as averaging data points.
40
+ def initialize(options = {})
41
+ @title = options[:title] || ''
42
+ @points = options[:points] || []
43
+ @preferred_color = options[:color]
44
+ @relevant_data = options[:relevant_data] || true
45
+ end
46
+
47
+ # Builds SVG code for this graph using the provided Builder object.
48
+ # This method actually generates data needed by this graph, then passes the
49
+ # rendering responsibilities to Base#draw.
50
+ #
51
+ # svg:: a Builder object used to create SVG code.
52
+ def render(svg, options = {})
53
+ setup_variables(options)
54
+ coords = generate_coordinates(options)
55
+
56
+ draw(svg, coords, options)
57
+ end
58
+
59
+ # The method called by Base#draw to render the graph.
60
+ #
61
+ # svg:: a Builder object to use for creating SVG code.
62
+ # coords:: An array of coordinates relating to the graph's data points. ie: [[100, 120], [200, 140], [300, 40]]
63
+ # options:: Optional arguments.
64
+ def draw(svg, coords, options={})
65
+ # This is what the various graphs need to implement.
66
+ end
67
+
68
+ # Returns the value of relevant_data
69
+ def relevant_data?
70
+ @relevant_data
71
+ end
72
+
73
+ # The highest data point on this layer
74
+ def highest_point
75
+ points.sort.last
76
+ end
77
+
78
+ # The lowest data point on this layer
79
+ def lowest_point
80
+ points.sort.first
81
+ end
82
+
83
+ protected
84
+ def setup_variables(options = {}) # :nodoc:
85
+ @color = (preferred_color || options.delete(:color))
86
+ @resolver = options.delete(:resolver)
87
+ @width, @height = options.delete(:size)
88
+ @min_value, @max_value = options[:min_value], options[:max_value]
89
+ @opacity = options[:opacity]
90
+ @complexity = options[:complexity]
91
+ end
92
+
93
+ def generate_coordinates(options = {}) # :nodoc:
94
+ options[:point_distance] = width / (points.size - 1).to_f
95
+
96
+ coords = (0...points.size).map do |idx|
97
+ x_coord = options[:point_distance] * idx
98
+
99
+ relative_percent = ((points[idx] == min_value) ? 0 : ((points[idx] - min_value) / (max_value - min_value).to_f))
100
+ y_coord = (height - (height * relative_percent))
101
+
102
+ [x_coord, y_coord]
103
+ end
104
+
105
+ coords
106
+ end
107
+
108
+ # Returns a 'scaled' value of the given integer.
109
+ #
110
+ # Scaled adjusts the given point depending on the height of the graph, where 400px is considered 1:1.
111
+ #
112
+ # ie: On a graph that is 800px high, scaled(1) => 2. For 200px high, scaled(1) => 0.5, and so on...
113
+ def scaled(point)
114
+ resolver.to_point(point)
115
+ end
116
+
117
+ def stringify_coords(coords) # :nodoc:
118
+ coords.map { |c| c.join(',') }
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,13 @@
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' => scaled(4) )
7
+
8
+ coords.each { |coord| svg.circle( :cx => coord.first, :cy => coord.last, :r => scaled(5),
9
+ :fill => color.to_s ) }
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ # Layer files
2
+ require 'scruffy/layers/base'
3
+ require 'scruffy/layers/area'
4
+ require 'scruffy/layers/all_smiles'
5
+ require 'scruffy/layers/bar'
6
+ require 'scruffy/layers/line'
7
+ require 'scruffy/layers/average'
@@ -1,5 +1,5 @@
1
1
  module Scruffy
2
- module Theme
2
+ module Themes
3
3
  KEYNOTE = {
4
4
  :background => [:black, '#4A465A'],
5
5
  :marker => :white,
@@ -1,3 +1,3 @@
1
1
  module Scruffy
2
- VERSION = '0.0.11'
2
+ VERSION = '0.0.12'
3
3
  end
data/lib/scruffy.rb CHANGED
@@ -10,15 +10,7 @@ require 'scruffy/themes'
10
10
  require 'scruffy/version'
11
11
  require 'scruffy/transformer'
12
12
  require 'scruffy/resolver'
13
-
14
- # Layer files
15
- require 'scruffy/graph_layers/base'
16
- require 'scruffy/graph_layers/area'
17
- require 'scruffy/graph_layers/all_smiles'
18
- require 'scruffy/graph_layers/bar'
19
- require 'scruffy/graph_layers/blue_screen'
20
- require 'scruffy/graph_layers/line'
21
- require 'scruffy/graph_layers/average'
13
+ require 'scruffy/layers'
22
14
 
23
15
  # Renderers
24
16
  require 'scruffy/renderers/standard'
data/spec/graph_spec.rb CHANGED
@@ -23,7 +23,7 @@ context "A new Scruffy::Graph" do
23
23
  end
24
24
 
25
25
  specify "should be set to the keynote theme" do
26
- @graph.theme.should_equal Scruffy::Theme::KEYNOTE
26
+ @graph.theme.should_equal Scruffy::Themes::KEYNOTE
27
27
  end
28
28
 
29
29
  specify "should have zero layers" do
@@ -52,8 +52,8 @@ context "A new Scruffy::Graph" do
52
52
  end
53
53
 
54
54
  specify "should accept a new theme" do
55
- @graph.theme = Scruffy::Theme::MEPHISTO
56
- @graph.theme.should_equal Scruffy::Theme::MEPHISTO
55
+ @graph.theme = Scruffy::Themes::MEPHISTO
56
+ @graph.theme.should_equal Scruffy::Themes::MEPHISTO
57
57
  end
58
58
 
59
59
  specify "should accept a new default type" do
@@ -87,7 +87,7 @@ context "A Scruffy::Graph's initialization block" do
87
87
 
88
88
  specify "should accept just an options hash" do
89
89
  lambda { Scruffy::Graph.new({:title => "My Title"}) }.should_not_raise
90
- lambda { Scruffy::Graph.new(:title => "My Title", :theme => Scruffy::Theme::KEYNOTE) }.should_not_raise
90
+ lambda { Scruffy::Graph.new(:title => "My Title", :theme => Scruffy::Themes::KEYNOTE) }.should_not_raise
91
91
  end
92
92
 
93
93
  specify "should accept both a default_type and options hash" do
@@ -111,7 +111,7 @@ context "A Scruffy::Graph's initialization block" do
111
111
  options = {:title => "My Title",
112
112
  :theme => {:background => [:black],
113
113
  :colors => [:red => 'red', :yellow => 'yellow']},
114
- :layers => [ Scruffy::LineLayer.new(:points => [100, 200, 300]) ],
114
+ :layers => [ Scruffy::Layers::Line.new(:points => [100, 200, 300]) ],
115
115
  :default_type => :average,
116
116
  :renderer => blank_renderer,
117
117
  :marker_transformer => Scruffy::Transformer::Currency.new,
metadata CHANGED
@@ -3,7 +3,7 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: scruffy
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.0.11
6
+ version: 0.0.12
7
7
  date: 2006-08-10 00:00:00 -07:00
8
8
  summary: A powerful, clean graphing library for Ruby.
9
9
  require_paths:
@@ -35,17 +35,17 @@ files:
35
35
  - README
36
36
  - lib/scruffy.rb
37
37
  - lib/scruffy/graph.rb
38
+ - lib/scruffy/layers.rb
38
39
  - lib/scruffy/resolver.rb
39
40
  - lib/scruffy/themes.rb
40
41
  - lib/scruffy/transformer.rb
41
42
  - lib/scruffy/version.rb
42
- - lib/scruffy/graph_layers/all_smiles.rb
43
- - lib/scruffy/graph_layers/area.rb
44
- - lib/scruffy/graph_layers/average.rb
45
- - lib/scruffy/graph_layers/bar.rb
46
- - lib/scruffy/graph_layers/base.rb
47
- - lib/scruffy/graph_layers/blue_screen.rb
48
- - lib/scruffy/graph_layers/line.rb
43
+ - lib/scruffy/layers/all_smiles.rb
44
+ - lib/scruffy/layers/area.rb
45
+ - lib/scruffy/layers/average.rb
46
+ - lib/scruffy/layers/bar.rb
47
+ - lib/scruffy/layers/base.rb
48
+ - lib/scruffy/layers/line.rb
49
49
  - lib/scruffy/renderers/standard.rb
50
50
  - spec/graph_spec.rb
51
51
  test_files: []
@@ -1,107 +0,0 @@
1
- module Scruffy
2
- class AllSmilesLayer < GraphLayer
3
- attr_accessor :standalone
4
-
5
- def initialize(options = {})
6
- super
7
- @standalone = options[:standalone] || false
8
- end
9
-
10
- def draw(svg, coords, options={})
11
-
12
- hero_smiley = nil
13
- coords.each { |c| hero_smiley = c.last if (hero_smiley.nil? || c.last < hero_smiley) }
14
-
15
- svg.defs {
16
- svg.radialGradient(:id => 'SmileyGradient', :cx => '50%',
17
- :cy => '50%', :r => '50%', :fx => '30%', :fy => '30%') {
18
-
19
- svg.stop(:offset => '0%', 'stop-color' => '#FFF')
20
- svg.stop(:offset => '20%', 'stop-color' => '#FFC')
21
- svg.stop(:offset => '45%', 'stop-color' => '#FF3')
22
- svg.stop(:offset => '60%', 'stop-color' => '#FF0')
23
- svg.stop(:offset => '90%', 'stop-color' => '#990')
24
- svg.stop(:offset => '100%', 'stop-color' => '#220')
25
- }
26
- svg.radialGradient(:id => 'HeroGradient', :cx => '50%',
27
- :cy => '50%', :r => '50%', :fx => '30%', :fy => '30%') {
28
-
29
- svg.stop(:offset => '0%', 'stop-color' => '#FEE')
30
- svg.stop(:offset => '20%', 'stop-color' => '#F0E0C0')
31
- svg.stop(:offset => '45%', 'stop-color' => '#8A2A1A')
32
- svg.stop(:offset => '60%', 'stop-color' => '#821')
33
- svg.stop(:offset => '90%', 'stop-color' => '#210')
34
- }
35
- svg.radialGradient(:id => 'StarGradient', :cx => '50%',
36
- :cy => '50%', :r => '50%', :fx => '30%', :fy => '30%') {
37
-
38
- svg.stop(:offset => '0%', 'stop-color' => '#FFF')
39
- svg.stop(:offset => '20%', 'stop-color' => '#EFEFEF')
40
- svg.stop(:offset => '45%', 'stop-color' => '#DDD')
41
- svg.stop(:offset => '60%', 'stop-color' => '#BBB')
42
- svg.stop(:offset => '90%', 'stop-color' => '#888')
43
- }
44
- }
45
-
46
- unless standalone
47
- svg.polyline( :points => stringify_coords(coords).join(' '), :fill => 'none',
48
- :stroke => '#660', 'stroke-width' => scaled(10), 'stroke-dasharray' => "#{scaled(10)}, #{scaled(10)}" )
49
- end
50
-
51
- coords.each do |coord|
52
- if standalone
53
- svg.line( :x1 => coord.first, :y1 => coord.last, :x2 => coord.first, :y2 => height, :fill => 'none',
54
- :stroke => '#660', 'stroke-width' => scaled(10), 'stroke-dasharray' => "#{scaled(10)}, #{scaled(10)}" )
55
- end
56
- svg.circle( :cx => coord.first + scaled(2), :cy => coord.last + scaled(2), :r => scaled(15),
57
- :fill => 'black', :stroke => 'none', :opacity => 0.4)
58
- svg.circle( :cx => coord.first, :cy => coord.last, :r => scaled(15),
59
- :fill => (complexity == :minimal ? 'yellow' : 'url(#SmileyGradient)'), :stroke => 'black', 'stroke-width' => scaled(1) )
60
- svg.line( :x1 => (coord.first - scaled(3)),
61
- :x2 => (coord.first - scaled(3)),
62
- :y1 => (coord.last),
63
- :y2 => (coord.last - scaled(7)), :stroke => 'black', 'stroke-width' => scaled(1.4) )
64
- svg.line( :x1 => (coord.first + scaled(3)),
65
- :x2 => (coord.first + scaled(3)),
66
- :y1 => (coord.last),
67
- :y2 => (coord.last - scaled(7)), :stroke => 'black', 'stroke-width' => scaled(1.4) )
68
-
69
-
70
- percent = 1.0 - (coord.last.to_f / height.to_f)
71
-
72
- corners = scaled(8 - (5 * percent))
73
- anchor = scaled((20 * percent) - 5)
74
- svg.path( :d => "M#{coord.first - scaled(9)} #{coord.last + corners} Q#{coord.first} #{coord.last + anchor} #{coord.first + scaled(9)} #{coord.last + corners}",
75
- :stroke => 'black', 'stroke-width' => scaled(1.4), :fill => 'none' )
76
-
77
-
78
- # top hat
79
- if coord.last == hero_smiley
80
- svg.ellipse(:cx => coord.first, :cy => (coord.last - scaled(13)),
81
- :rx => scaled(17), :ry => scaled(6.5), :fill => (complexity == :minimal ? 'purple' : 'url(#HeroGradient)'), :stroke => 'black', 'stroke-width' => scaled(1.4) )
82
-
83
- svg.path(:d => "M#{coord.first} #{coord.last - scaled(60)} " +
84
- "L#{coord.first + scaled(10)} #{coord.last - scaled(14)} " +
85
- "C#{coord.first + scaled(10)},#{coord.last - scaled(9)} #{coord.first - scaled(10)},#{coord.last - scaled(9)} #{coord.first - scaled(10)},#{coord.last - scaled(14)}" +
86
- "L#{coord.first} #{coord.last - scaled(60)}",
87
- :stroke => 'black', 'stroke-width' => scaled(1.4), :fill => (complexity == :minimal ? 'purple' : 'url(#HeroGradient)'))
88
-
89
- svg.path(:d => "M#{coord.first - scaled(4)} #{coord.last - scaled(23)}" +
90
- "l-#{scaled(2.5)} #{scaled(10)} l#{scaled(7.5)} -#{scaled(5)} l-#{scaled(10)} 0 l#{scaled(7.5)} #{scaled(5)} l-#{scaled(2.5)} -#{scaled(10)}", :stroke => 'none', :fill => (complexity == :minimal ? 'white': 'url(#StarGradient)') )
91
- svg.path(:d => "M#{coord.first + scaled(2)} #{coord.last - scaled(30)}" +
92
- "l-#{scaled(2.5)} #{scaled(10)} l#{scaled(7.5)} -#{scaled(5)} l-#{scaled(10)} 0 l#{scaled(7.5)} #{scaled(5)} l-#{scaled(2.5)} -#{scaled(10)}", :stroke => 'none', :fill => (complexity == :minimal ? 'white': 'url(#StarGradient)') )
93
- svg.path(:d => "M#{coord.first - scaled(2)} #{coord.last - scaled(33)}" +
94
- "l-#{scaled(1.25)} #{scaled(5)} l#{scaled(3.75)} -#{scaled(2.5)} l-#{scaled(5)} 0 l#{scaled(3.75)} #{scaled(2.5)} l-#{scaled(1.25)} -#{scaled(5)}", :stroke => 'none', :fill => 'white' )
95
- svg.path(:d => "M#{coord.first - scaled(2.2)} #{coord.last - scaled(32.7)}" +
96
- "l-#{scaled(1.25)} #{scaled(5)} l#{scaled(3.75)} -#{scaled(2.5)} l-#{scaled(5)} 0 l#{scaled(3.75)} #{scaled(2.5)} l-#{scaled(1.25)} -#{scaled(5)}", :stroke => 'none', :fill => (complexity == :minimal ? 'white': 'url(#StarGradient)') )
97
- svg.path(:d => "M#{coord.first + scaled(4.5)} #{coord.last - scaled(20)}" +
98
- "l-#{scaled(1.25)} #{scaled(5)} l#{scaled(3.75)} -#{scaled(2.5)} l-#{scaled(5)} 0 l#{scaled(3.75)} #{scaled(2.5)} l-#{scaled(1.25)} -#{scaled(5)}", :stroke => 'none', :fill => (complexity == :minimal ? 'white': 'url(#StarGradient)') )
99
- svg.path(:d => "M#{coord.first} #{coord.last - scaled(40)}" +
100
- "l-#{scaled(1.25)} #{scaled(5)} l#{scaled(3.75)} -#{scaled(2.5)} l-#{scaled(5)} 0 l#{scaled(3.75)} #{scaled(2.5)} l-#{scaled(1.25)} -#{scaled(5)}", :stroke => 'none', :fill => (complexity == :minimal ? 'white': 'url(#StarGradient)') )
101
-
102
- end
103
-
104
- end
105
- end
106
- end
107
- end
@@ -1,32 +0,0 @@
1
- module Scruffy
2
- class AreaLayer < GraphLayer
3
- def draw(svg, coords, options={})
4
- points_value = "0,#{height} #{stringify_coords(coords).join(' ')} #{width},#{height}"
5
-
6
- # Experimental, for later user.
7
- #
8
- # svg.defs {
9
- # svg.filter(:id => 'MyFilter', :filterUnits => 'userSpaceOnUse', :x => 0, :y => 0, :width => 200, :height => '120') {
10
- # svg.feGaussianBlur(:in => 'SourceAlpha', :stdDeviation => 4, :result => 'blur')
11
- # svg.feOffset(:in => 'blur', :dx => 4, :dy => 4, :result => 'offsetBlur')
12
- # svg.feSpecularLighting( :in => 'blur', :surfaceScale => 5, :specularConstant => '.75',
13
- # :specularExponent => 20, 'lighting-color' => '#bbbbbb',
14
- # :result => 'specOut') {
15
- # svg.fePointLight(:x => '-5000', :y => '-10000', :z => '20000')
16
- # }
17
- #
18
- # svg.feComposite(:in => 'specOut', :in2 => 'SourceAlpha', :operator => 'in', :result => 'specOut')
19
- # svg.feComposite(:in => 'sourceGraphic', :in2 => 'specOut', :operator => 'arithmetic',
20
- # :k1 => 0, :k2 => 1, :k3 => 1, :k4 => 0, :result => 'litPaint')
21
- #
22
- # svg.feMerge {
23
- # svg.feMergeNode(:in => 'offsetBlur')
24
- # svg.feMergeNode(:in => 'litPaint')
25
- # }
26
- # }
27
- # }
28
-
29
- svg.polygon(:points => points_value, :fill => color.to_s, :stroke => color.to_s, :opacity => options[:opacity])
30
- end
31
- end
32
- end
@@ -1,52 +0,0 @@
1
- module Scruffy
2
- class AverageLayer < GraphLayer
3
- def initialize(options = {})
4
- super
5
-
6
- @relevant_data = false
7
- end
8
- def draw(svg, coords, options = {})
9
- svg.polyline( :points => coords.join(' '), :fill => 'none', :stroke => 'black',
10
- 'stroke-width' => scaled(25), :opacity => '0.4')
11
- end
12
-
13
- def highest_point
14
- nil
15
- end
16
-
17
- def lowest_point
18
- nil
19
- end
20
-
21
- protected
22
- def generate_coordinates(options = {})
23
- key_layer = points.find { |layer| layer.relevant_data? }
24
-
25
- options[:point_distance] = width / (key_layer.points.size - 1).to_f
26
-
27
- coords = []
28
-
29
- key_layer.points.each_with_index do |layer, idx|
30
- sum, objects = points.inject([0, 0]) do |arr, elem|
31
- if elem.relevant_data?
32
- arr[0] += elem.points[idx]
33
- arr[1] += 1
34
- end
35
- arr
36
- end
37
-
38
- average = sum / objects.to_f
39
-
40
- x_coord = options[:point_distance] * idx
41
-
42
- relative_percent = ((average == min_value) ? 0 : ((average - min_value) / (max_value - min_value).to_f))
43
- y_coord = (height - (height * relative_percent))
44
-
45
- coords << [x_coord, y_coord].join(',')
46
- end
47
-
48
- return coords
49
- end
50
-
51
- end
52
- end
@@ -1,28 +0,0 @@
1
- module Scruffy
2
- class BarLayer < GraphLayer
3
-
4
- def draw(svg, coords, options = {})
5
- coords.each do |coord|
6
- x, y, bar_height = (coord.first-(@bar_width * 0.5)), coord.last, (height - coord.last)
7
-
8
- svg.rect( :x => x, :y => y, :width => @bar_width, :height => bar_height,
9
- :fill => color.to_s, :stroke => color.to_s, :opacity => opacity )
10
- end
11
- end
12
-
13
- protected
14
- def generate_coordinates(options = {})
15
- @bar_width = (width / points.size) * 0.9
16
- options[:point_distance] = (width - (width / points.size)) / (points.size - 1).to_f
17
-
18
- coords = (0...points.size).map do |idx|
19
- x_coord = (options[:point_distance] * idx) + (width / points.size * 0.5)
20
-
21
- relative_percent = ((points[idx] == min_value) ? 0 : ((points[idx] - min_value) / (max_value - min_value).to_f))
22
- y_coord = (height - (height * relative_percent))
23
- [x_coord, y_coord]
24
- end
25
- coords
26
- end
27
- end
28
- end
@@ -1,120 +0,0 @@
1
- module Scruffy
2
- # ==GraphLayer
3
- #
4
- # Author:: Brasten Sager
5
- # Date:: August 5th, 2006
6
- #
7
- # GraphLayer contains the basic functionality needed by the various types of graphs. The GraphLayer
8
- # class is responsible holding layer information such as the title and data points.
9
- #
10
- # When the graph is rendered, the graph renderer calls GraphLayer#render. GraphLayer#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 GraphLayer is sufficient, you can create a new graph type
18
- # simply by overriding the draw() method. See GraphLayer#draw for arguments.
19
- #
20
- class GraphLayer
21
- attr_accessor :title
22
- attr_accessor :points
23
- attr_accessor :height, :width
24
- attr_accessor :min_value, :max_value
25
- attr_accessor :preferred_color, :color
26
- attr_accessor :opacity
27
- attr_accessor :resolver
28
- attr_accessor :complexity
29
- attr_accessor :relevant_data
30
-
31
- # Returns a new GraphLayer.
32
- #
33
- # Options:
34
- # title::
35
- # points:: Array of data points
36
- # color:: Color used to render this graph, overrides theme color.
37
- # relevant_data:: Rarely used - indicates the data on this graph should not
38
- # included in any graph data aggregations, such as averaging data points.
39
- def initialize(options = {})
40
- @title = options[:title] || ''
41
- @points = options[:points] || []
42
- @preferred_color = options[:color]
43
- @relevant_data = options[:relevant_data] || true
44
- end
45
-
46
- # Builds SVG code for this graph using the provided Builder object.
47
- # This method actually generates data needed by this graph, then passes the
48
- # rendering responsibilities to GraphLayer#draw.
49
- #
50
- # svg:: a Builder object used to create SVG code.
51
- def render(svg, options = {})
52
- setup_variables(options)
53
- coords = generate_coordinates(options)
54
-
55
- draw(svg, coords, options)
56
- end
57
-
58
- # The method called by GraphLayer#draw to render the graph.
59
- #
60
- # svg:: a Builder object to use for creating SVG code.
61
- # coords:: An array of coordinates relating to the graph's data points. ie: [[100, 120], [200, 140], [300, 40]]
62
- # options:: Optional arguments.
63
- def draw(svg, coords, options={})
64
- # This is what the various graphs need to implement.
65
- end
66
-
67
- # Returns the value of relevant_data
68
- def relevant_data?
69
- @relevant_data
70
- end
71
-
72
- # The highest data point on this GraphLayer
73
- def highest_point
74
- points.sort.last
75
- end
76
-
77
- # The lowest data point on this GraphLayer
78
- def lowest_point
79
- points.sort.first
80
- end
81
-
82
- protected
83
- def setup_variables(options = {}) # :nodoc:
84
- @color = (preferred_color || options.delete(:color))
85
- @resolver = options.delete(:resolver)
86
- @width, @height = options.delete(:size)
87
- @min_value, @max_value = options[:min_value], options[:max_value]
88
- @opacity = options[:opacity]
89
- @complexity = options[:complexity]
90
- end
91
-
92
- def generate_coordinates(options = {}) # :nodoc:
93
- options[:point_distance] = width / (points.size - 1).to_f
94
-
95
- coords = (0...points.size).map do |idx|
96
- x_coord = options[:point_distance] * idx
97
-
98
- relative_percent = ((points[idx] == min_value) ? 0 : ((points[idx] - min_value) / (max_value - min_value).to_f))
99
- y_coord = (height - (height * relative_percent))
100
-
101
- [x_coord, y_coord]
102
- end
103
-
104
- coords
105
- end
106
-
107
- # Returns a 'scaled' value of the given integer.
108
- #
109
- # Scaled adjusts the given point depending on the height of the graph, where 400px is considered 1:1.
110
- #
111
- # ie: On a graph that is 800px high, scaled(1) => 2. For 200px high, scaled(1) => 0.5, and so on...
112
- def scaled(point)
113
- resolver.to_point(point)
114
- end
115
-
116
- def stringify_coords(coords) # :nodoc:
117
- coords.map { |c| c.join(',') }
118
- end
119
- end
120
- end
@@ -1,7 +0,0 @@
1
- module Scruffy
2
- class BlueScreenLayer < GraphLayer
3
- def draw(svg, coords, options={})
4
- svg.image('xlink:href' => 'http://www.nagilum.com/images/logo-large.png', :width => width, :height => height)
5
- end
6
- end
7
- end
@@ -1,11 +0,0 @@
1
- module Scruffy
2
- class LineLayer < GraphLayer
3
- def draw(svg, coords, options={})
4
- svg.polyline( :points => stringify_coords(coords).join(' '), :fill => 'none',
5
- :stroke => color.to_s, 'stroke-width' => scaled(4) )
6
-
7
- coords.each { |coord| svg.circle( :cx => coord.first, :cy => coord.last, :r => scaled(5),
8
- :fill => color.to_s ) }
9
- end
10
- end
11
- end