scruffy 0.0.11 → 0.0.12

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