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 +6 -0
- data/lib/scruffy/graph.rb +14 -14
- data/lib/scruffy/layers/all_smiles.rb +109 -0
- data/lib/scruffy/layers/area.rb +34 -0
- data/lib/scruffy/layers/average.rb +54 -0
- data/lib/scruffy/layers/bar.rb +30 -0
- data/lib/scruffy/layers/base.rb +122 -0
- data/lib/scruffy/layers/line.rb +13 -0
- data/lib/scruffy/layers.rb +7 -0
- data/lib/scruffy/themes.rb +1 -1
- data/lib/scruffy/version.rb +1 -1
- data/lib/scruffy.rb +1 -9
- data/spec/graph_spec.rb +5 -5
- metadata +8 -8
- data/lib/scruffy/graph_layers/all_smiles.rb +0 -107
- data/lib/scruffy/graph_layers/area.rb +0 -32
- data/lib/scruffy/graph_layers/average.rb +0 -52
- data/lib/scruffy/graph_layers/bar.rb +0 -28
- data/lib/scruffy/graph_layers/base.rb +0 -120
- data/lib/scruffy/graph_layers/blue_screen.rb +0 -7
- data/lib/scruffy/graph_layers/line.rb +0 -11
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::
|
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::
|
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::
|
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::
|
49
|
-
# graph << Scruffy::
|
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
|
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
|
96
|
-
# default_type:: A symbol indicating the default type of
|
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::
|
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
|
137
|
-
# a
|
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 <<
|
147
|
+
# graph << Scruffy::Layers::Bar.new({...}) # Adds Bar layer to graph
|
148
148
|
#
|
149
149
|
def <<(*args)
|
150
|
-
if args[0].kind_of?(Scruffy::
|
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)}
|
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
|
data/lib/scruffy/themes.rb
CHANGED
data/lib/scruffy/version.rb
CHANGED
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::
|
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::
|
56
|
-
@graph.theme.should_equal Scruffy::
|
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::
|
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::
|
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.
|
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/
|
43
|
-
- lib/scruffy/
|
44
|
-
- lib/scruffy/
|
45
|
-
- lib/scruffy/
|
46
|
-
- lib/scruffy/
|
47
|
-
- lib/scruffy/
|
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,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
|