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