scruffy 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +8 -0
- data/Rakefile +9 -2
- data/lib/scruffy.rb +13 -0
- data/lib/scruffy/components.rb +10 -0
- data/lib/scruffy/components/viewport.rb +29 -17
- data/lib/scruffy/formatters.rb +156 -91
- data/lib/scruffy/graph.rb +22 -24
- data/lib/scruffy/helpers.rb +8 -0
- data/lib/scruffy/helpers/canvas.rb +25 -22
- data/lib/scruffy/helpers/layer_container.rb +20 -2
- data/lib/scruffy/layers.rb +15 -1
- data/lib/scruffy/layers/all_smiles.rb +121 -97
- data/lib/scruffy/layers/area.rb +38 -30
- data/lib/scruffy/layers/average.rb +52 -35
- data/lib/scruffy/layers/bar.rb +35 -22
- data/lib/scruffy/layers/base.rb +138 -126
- data/lib/scruffy/layers/line.rb +15 -9
- data/lib/scruffy/layers/sparkline_bar.rb +38 -0
- data/lib/scruffy/layers/stacked.rb +74 -59
- data/lib/scruffy/rasterizers.rb +13 -1
- data/lib/scruffy/rasterizers/batik_rasterizer.rb +35 -21
- data/lib/scruffy/rasterizers/rmagick_rasterizer.rb +19 -22
- data/lib/scruffy/renderers.rb +17 -1
- data/lib/scruffy/renderers/base.rb +39 -31
- data/lib/scruffy/renderers/cubed.rb +39 -31
- data/lib/scruffy/renderers/cubed3d.rb +53 -0
- data/lib/scruffy/renderers/empty.rb +23 -0
- data/lib/scruffy/renderers/sparkline.rb +11 -0
- data/lib/scruffy/themes.rb +81 -61
- data/lib/scruffy/version.rb +1 -1
- metadata +16 -4
data/lib/scruffy/helpers.rb
CHANGED
@@ -1,2 +1,10 @@
|
|
1
|
+
# ==Scruffy Helpers
|
2
|
+
#
|
3
|
+
# Author:: Brasten Sager
|
4
|
+
# Date:: August 16th, 2006
|
5
|
+
#
|
6
|
+
# Modules which provide helper methods for various situations.
|
7
|
+
module Scruffy::Helpers; end
|
8
|
+
|
1
9
|
require 'scruffy/helpers/canvas'
|
2
10
|
require 'scruffy/helpers/layer_container'
|
@@ -1,22 +1,25 @@
|
|
1
|
-
module Scruffy
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
1
|
+
module Scruffy::Helpers
|
2
|
+
# ==Scruffy::Helpers::Canvas
|
3
|
+
#
|
4
|
+
# Author:: Brasten Sager
|
5
|
+
# Date:: August 16th, 2006
|
6
|
+
#
|
7
|
+
# Provides common methods for canvas objects. Primarily used for providing spacial-type calculations
|
8
|
+
# where necessary.
|
9
|
+
module Canvas
|
10
|
+
protected
|
11
|
+
# Converts percentage values into actual pixe values based on the known render size.
|
12
|
+
#
|
13
|
+
# Returns a hash consisting of :x, :y, :width, and :height elements.
|
14
|
+
def bounds_for(canvas_size, position, size)
|
15
|
+
return nil if (position.nil? || size.nil?)
|
16
|
+
bounds = {}
|
17
|
+
bounds[:x] = canvas_size.first * (position.first / 100.to_f)
|
18
|
+
bounds[:y] = canvas_size.last * (position.last / 100.to_f)
|
19
|
+
bounds[:width] = canvas_size.first * (size.first / 100.to_f)
|
20
|
+
bounds[:height] = canvas_size.last * (size.last / 100.to_f)
|
21
|
+
bounds
|
22
|
+
end
|
23
|
+
end # canvas
|
24
|
+
|
25
|
+
end # scruffy::helpers
|
@@ -1,4 +1,12 @@
|
|
1
1
|
module Scruffy::Helpers
|
2
|
+
# ==Scruffy::Helpers::LayerContainer
|
3
|
+
#
|
4
|
+
# Author:: Brasten Sager
|
5
|
+
# Date:: August 16th, 2006
|
6
|
+
#
|
7
|
+
# Adds some common functionality to any object which needs to act as a
|
8
|
+
# container for graph layers. The best example of this is the Scruffy::Graph
|
9
|
+
# object itself, but this module is also used by Scruffy::Layer::Stacked.
|
2
10
|
module LayerContainer
|
3
11
|
# Adds a Layer to the Graph/Container. Accepts either a list of arguments used to build a new layer, or
|
4
12
|
# a Scruffy::Layers::Base-derived object. When passing a list of arguments, all arguments are optional, but the arguments
|
@@ -32,23 +40,33 @@ module Scruffy::Helpers
|
|
32
40
|
end
|
33
41
|
layer
|
34
42
|
end
|
35
|
-
|
43
|
+
|
36
44
|
alias :add :<<
|
37
45
|
|
38
46
|
|
39
|
-
#
|
47
|
+
# Layer Writer
|
40
48
|
def layers=(val)
|
41
49
|
@layers = val
|
42
50
|
end
|
51
|
+
|
52
|
+
# Layer Reader
|
43
53
|
def layers
|
44
54
|
@layers ||= []
|
45
55
|
end
|
46
56
|
|
57
|
+
# Returns the highest value in any of this container's layers.
|
58
|
+
#
|
59
|
+
# If padding is set to :padded, a 15% padding is added to the highest value.
|
47
60
|
def top_value(padding=nil) # :nodoc:
|
48
61
|
topval = layers.inject(0) { |max, layer| (max = ((max < layer.top_value) ? layer.top_value : max)) unless layer.top_value.nil?; max }
|
49
62
|
padding == :padded ? (topval - ((topval - bottom_value) * 0.15)) : topval
|
50
63
|
end
|
51
64
|
|
65
|
+
# Returns the lowest value in any of this container's layers.
|
66
|
+
#
|
67
|
+
# If padding is set to :padded, a 15% padding is added below the lowest value.
|
68
|
+
# If the lowest value is greater than zero, then the padding will not cross the zero line, preventing
|
69
|
+
# negative values from being introduced into the graph purely due to padding.
|
52
70
|
def bottom_value(padding=nil) # :nodoc:
|
53
71
|
botval = layers.inject(top_value) { |min, layer| (min = ((min > layer.bottom_value) ? layer.bottom_value : min)) unless layer.bottom_value.nil?; min }
|
54
72
|
above_zero = (botval > 0)
|
data/lib/scruffy/layers.rb
CHANGED
@@ -1,4 +1,18 @@
|
|
1
|
-
#
|
1
|
+
# ==Scruffy::Layers
|
2
|
+
#
|
3
|
+
# Author:: Brasten Sager
|
4
|
+
# Date:: August 10th, 2006
|
5
|
+
#
|
6
|
+
# See documentation in Scruffy::Layers::Base
|
7
|
+
#
|
8
|
+
module Scruffy::Layers
|
9
|
+
|
10
|
+
# Should be raised whenever a predictable error during rendering occurs,
|
11
|
+
# particularly if you do not want to terminate the graph rendering process.
|
12
|
+
class RenderError < StandardError; end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
2
16
|
require 'scruffy/layers/base'
|
3
17
|
require 'scruffy/layers/area'
|
4
18
|
require 'scruffy/layers/all_smiles'
|
@@ -1,113 +1,137 @@
|
|
1
|
-
module Scruffy
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
module Scruffy::Layers
|
2
|
+
# ==Scruffy::Layers::AllSmiles
|
3
|
+
#
|
4
|
+
# Author:: Brasten Sager
|
5
|
+
# Date:: August 8th, 2006
|
6
|
+
#
|
7
|
+
# The AllSmiles graph consists of smiley faces for data points, with smiles or frowns depending upon
|
8
|
+
# their relative location on the graph. The highest point is crowned with a wizard hat. The Wizard
|
9
|
+
# Smiley eventually become 'Scruffy', our mascot.
|
10
|
+
#
|
11
|
+
# I don't know why.
|
12
|
+
#
|
13
|
+
# This graph only looks decent in SVG mode. If you're rasterizing the graph with ImageMagick, you
|
14
|
+
# must use the :complexity => :minimal option on Graph#render. This will make the graph look really
|
15
|
+
# nasty, but still better than if you try to rasterize with all the gradients in place.
|
16
|
+
class AllSmiles < Base
|
17
|
+
attr_accessor :standalone
|
18
|
+
|
19
|
+
# Returns a new AllSmiles graph.
|
20
|
+
#
|
21
|
+
# Options:
|
22
|
+
# standalone:: If set to true, dashed lines under smilies run vertically, like bar graphs.
|
23
|
+
# If false (default), dashed lines run from smiley to smiley, like a line-graph.
|
24
|
+
def initialize(options = {})
|
25
|
+
super
|
26
|
+
@standalone = options[:standalone] || false
|
27
|
+
end
|
28
|
+
|
29
|
+
# Renders graph.
|
30
|
+
def draw(svg, coords, options={})
|
5
31
|
|
6
|
-
|
7
|
-
|
8
|
-
@standalone = options[:standalone] || false
|
9
|
-
end
|
32
|
+
hero_smiley = nil
|
33
|
+
coords.each { |c| hero_smiley = c.last if (hero_smiley.nil? || c.last < hero_smiley) }
|
10
34
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
}
|
35
|
+
svg.defs {
|
36
|
+
svg.radialGradient(:id => 'SmileyGradient', :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' => '#FFC')
|
41
|
+
svg.stop(:offset => '45%', 'stop-color' => '#FF3')
|
42
|
+
svg.stop(:offset => '60%', 'stop-color' => '#FF0')
|
43
|
+
svg.stop(:offset => '90%', 'stop-color' => '#990')
|
44
|
+
svg.stop(:offset => '100%', 'stop-color' => '#220')
|
45
|
+
}
|
46
|
+
svg.radialGradient(:id => 'HeroGradient', :cx => '50%',
|
47
|
+
:cy => '50%', :r => '50%', :fx => '30%', :fy => '30%') {
|
48
|
+
|
49
|
+
svg.stop(:offset => '0%', 'stop-color' => '#FEE')
|
50
|
+
svg.stop(:offset => '20%', 'stop-color' => '#F0E0C0')
|
51
|
+
svg.stop(:offset => '45%', 'stop-color' => '#8A2A1A')
|
52
|
+
svg.stop(:offset => '60%', 'stop-color' => '#821')
|
53
|
+
svg.stop(:offset => '90%', 'stop-color' => '#210')
|
45
54
|
}
|
46
|
-
|
47
|
-
|
48
|
-
|
55
|
+
svg.radialGradient(:id => 'StarGradient', :cx => '50%',
|
56
|
+
:cy => '50%', :r => '50%', :fx => '30%', :fy => '30%') {
|
57
|
+
|
58
|
+
svg.stop(:offset => '0%', 'stop-color' => '#FFF')
|
59
|
+
svg.stop(:offset => '20%', 'stop-color' => '#EFEFEF')
|
60
|
+
svg.stop(:offset => '45%', 'stop-color' => '#DDD')
|
61
|
+
svg.stop(:offset => '60%', 'stop-color' => '#BBB')
|
62
|
+
svg.stop(:offset => '90%', 'stop-color' => '#888')
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
unless standalone
|
67
|
+
svg.polyline( :points => stringify_coords(coords).join(' '), :fill => 'none',
|
68
|
+
:stroke => '#660', 'stroke-width' => scaled(10), 'stroke-dasharray' => "#{scaled(10)}, #{scaled(10)}" )
|
69
|
+
end
|
70
|
+
|
71
|
+
# Draw smilies.
|
72
|
+
coords.each do |coord|
|
73
|
+
if standalone
|
74
|
+
svg.line( :x1 => coord.first, :y1 => coord.last, :x2 => coord.first, :y2 => height, :fill => 'none',
|
49
75
|
:stroke => '#660', 'stroke-width' => scaled(10), 'stroke-dasharray' => "#{scaled(10)}, #{scaled(10)}" )
|
50
76
|
end
|
77
|
+
svg.circle( :cx => coord.first + scaled(2), :cy => coord.last + scaled(2), :r => scaled(15),
|
78
|
+
:fill => 'black', :stroke => 'none', :opacity => 0.4)
|
79
|
+
svg.circle( :cx => coord.first, :cy => coord.last, :r => scaled(15),
|
80
|
+
:fill => (complexity == :minimal ? 'yellow' : 'url(#SmileyGradient)'), :stroke => 'black', 'stroke-width' => scaled(1) )
|
81
|
+
svg.line( :x1 => (coord.first - scaled(3)),
|
82
|
+
:x2 => (coord.first - scaled(3)),
|
83
|
+
:y1 => (coord.last),
|
84
|
+
:y2 => (coord.last - scaled(7)), :stroke => 'black', 'stroke-width' => scaled(1.4) )
|
85
|
+
svg.line( :x1 => (coord.first + scaled(3)),
|
86
|
+
:x2 => (coord.first + scaled(3)),
|
87
|
+
:y1 => (coord.last),
|
88
|
+
:y2 => (coord.last - scaled(7)), :stroke => 'black', 'stroke-width' => scaled(1.4) )
|
89
|
+
|
51
90
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
91
|
+
# Some minor mathematics for the smile/frown
|
92
|
+
percent = 1.0 - (coord.last.to_f / height.to_f)
|
93
|
+
corners = scaled(8 - (5 * percent))
|
94
|
+
anchor = scaled((20 * percent) - 5)
|
70
95
|
|
71
|
-
|
96
|
+
# Draw the mouth
|
97
|
+
svg.path( :d => "M#{coord.first - scaled(9)} #{coord.last + corners} Q#{coord.first} #{coord.last + anchor} #{coord.first + scaled(9)} #{coord.last + corners}",
|
98
|
+
:stroke => 'black', 'stroke-width' => scaled(1.4), :fill => 'none' )
|
99
|
+
|
100
|
+
|
101
|
+
# Wizard hat for hero smiley.
|
102
|
+
if coord.last == hero_smiley
|
103
|
+
svg.ellipse(:cx => coord.first, :cy => (coord.last - scaled(13)),
|
104
|
+
:rx => scaled(17), :ry => scaled(6.5), :fill => (complexity == :minimal ? 'purple' : 'url(#HeroGradient)'), :stroke => 'black', 'stroke-width' => scaled(1.4) )
|
72
105
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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)') )
|
106
|
+
svg.path(:d => "M#{coord.first} #{coord.last - scaled(60)} " +
|
107
|
+
"L#{coord.first + scaled(10)} #{coord.last - scaled(14)} " +
|
108
|
+
"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)}" +
|
109
|
+
"L#{coord.first} #{coord.last - scaled(60)}",
|
110
|
+
:stroke => 'black', 'stroke-width' => scaled(1.4), :fill => (complexity == :minimal ? 'purple' : 'url(#HeroGradient)'))
|
102
111
|
|
103
|
-
|
112
|
+
svg.path(:d => "M#{coord.first - scaled(4)} #{coord.last - scaled(23)}" +
|
113
|
+
"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)') )
|
114
|
+
svg.path(:d => "M#{coord.first + scaled(2)} #{coord.last - scaled(30)}" +
|
115
|
+
"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)') )
|
116
|
+
svg.path(:d => "M#{coord.first - scaled(2)} #{coord.last - scaled(33)}" +
|
117
|
+
"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' )
|
118
|
+
svg.path(:d => "M#{coord.first - scaled(2.2)} #{coord.last - scaled(32.7)}" +
|
119
|
+
"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)') )
|
120
|
+
svg.path(:d => "M#{coord.first + scaled(4.5)} #{coord.last - scaled(20)}" +
|
121
|
+
"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)') )
|
122
|
+
svg.path(:d => "M#{coord.first} #{coord.last - scaled(40)}" +
|
123
|
+
"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)') )
|
104
124
|
|
105
125
|
end
|
106
|
-
end
|
107
126
|
|
108
|
-
def scaled(pt)
|
109
|
-
relative(pt) / 2
|
110
127
|
end
|
111
128
|
end
|
129
|
+
|
130
|
+
# Legacy (4 days old). Removed scaled from layout engine,
|
131
|
+
# changed to #relative, with different math involved.
|
132
|
+
# Translate here so I don't have to entirely redo this graph.
|
133
|
+
def scaled(pt)
|
134
|
+
relative(pt) / 2
|
135
|
+
end
|
112
136
|
end
|
113
137
|
end
|
data/lib/scruffy/layers/area.rb
CHANGED
@@ -1,35 +1,43 @@
|
|
1
|
-
module Scruffy
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
module Scruffy::Layers
|
2
|
+
# ==Scruffy::Layers::Area
|
3
|
+
#
|
4
|
+
# Author:: Brasten Sager
|
5
|
+
# Date:: August 6th, 2006
|
6
|
+
#
|
7
|
+
# Standard area graph.
|
8
|
+
class Area < Base
|
9
|
+
|
10
|
+
# Render area graph.
|
11
|
+
def draw(svg, coords, options={})
|
12
|
+
# svg.polygon wants a long string of coords.
|
13
|
+
points_value = "0,#{height} #{stringify_coords(coords).join(' ')} #{width},#{height}"
|
6
14
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
15
|
+
# Experimental, for later user.
|
16
|
+
# This was supposed to add some fun filters, 3d effects and whatnot.
|
17
|
+
# Neither ImageMagick nor Mozilla SVG render this well (at all). Maybe a future thing.
|
18
|
+
#
|
19
|
+
# svg.defs {
|
20
|
+
# svg.filter(:id => 'MyFilter', :filterUnits => 'userSpaceOnUse', :x => 0, :y => 0, :width => 200, :height => '120') {
|
21
|
+
# svg.feGaussianBlur(:in => 'SourceAlpha', :stdDeviation => 4, :result => 'blur')
|
22
|
+
# svg.feOffset(:in => 'blur', :dx => 4, :dy => 4, :result => 'offsetBlur')
|
23
|
+
# svg.feSpecularLighting( :in => 'blur', :surfaceScale => 5, :specularConstant => '.75',
|
24
|
+
# :specularExponent => 20, 'lighting-color' => '#bbbbbb',
|
25
|
+
# :result => 'specOut') {
|
26
|
+
# svg.fePointLight(:x => '-5000', :y => '-10000', :z => '20000')
|
27
|
+
# }
|
28
|
+
#
|
29
|
+
# svg.feComposite(:in => 'specOut', :in2 => 'SourceAlpha', :operator => 'in', :result => 'specOut')
|
30
|
+
# svg.feComposite(:in => 'sourceGraphic', :in2 => 'specOut', :operator => 'arithmetic',
|
31
|
+
# :k1 => 0, :k2 => 1, :k3 => 1, :k4 => 0, :result => 'litPaint')
|
32
|
+
#
|
33
|
+
# svg.feMerge {
|
34
|
+
# svg.feMergeNode(:in => 'offsetBlur')
|
35
|
+
# svg.feMergeNode(:in => 'litPaint')
|
36
|
+
# }
|
37
|
+
# }
|
38
|
+
# }
|
30
39
|
|
31
|
-
|
32
|
-
end
|
40
|
+
svg.polygon(:points => points_value, :fill => color.to_s, :stroke => color.to_s, 'style' => "opacity: #{opacity}")
|
33
41
|
end
|
34
42
|
end
|
35
43
|
end
|
@@ -1,45 +1,62 @@
|
|
1
|
-
module Scruffy
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
module Scruffy::Layers
|
2
|
+
# ==Scruffy::Layers::Average
|
3
|
+
#
|
4
|
+
# Author:: Brasten Sager
|
5
|
+
# Date:: August 7th, 2006
|
6
|
+
#
|
7
|
+
# An 'average' graph. This graph iterates through all the layers and averages
|
8
|
+
# all the data at each point, then draws a thick, translucent, shadowy line graph
|
9
|
+
# indicating the average values.
|
10
|
+
#
|
11
|
+
# This only looks decent in SVG mode. ImageMagick doesn't retain the transparency
|
12
|
+
# for some reason, creating a massive black line. Any help resolving this would
|
13
|
+
# be useful.
|
14
|
+
class Average < Base
|
15
|
+
|
16
|
+
# Returns new Average graph.
|
17
|
+
def initialize(options = {})
|
18
|
+
# Set self's relevant_data to false. Otherwise we get stuck in a
|
19
|
+
# recursive loop.
|
20
|
+
super(options.merge({:relevant_data => false}))
|
21
|
+
end
|
22
|
+
|
23
|
+
# Render average graph.
|
24
|
+
def draw(svg, coords, options = {})
|
25
|
+
svg.polyline( :points => coords.join(' '), :fill => 'none', :stroke => 'black',
|
26
|
+
'stroke-width' => relative(5), 'opacity' => '0.4')
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
# Override default generate_coordinates method to iterate through the layers and
|
31
|
+
# generate coordinates based on the average data points.
|
32
|
+
def generate_coordinates(options = {})
|
33
|
+
key_layer = points.find { |layer| layer.relevant_data? }
|
7
34
|
|
8
|
-
|
9
|
-
svg.polyline( :points => coords.join(' '), :fill => 'none', :stroke => 'black',
|
10
|
-
'stroke-width' => relative(5), 'opacity' => '0.4')
|
11
|
-
end
|
35
|
+
options[:point_distance] = width / (key_layer.points.size - 1).to_f
|
12
36
|
|
13
|
-
|
14
|
-
def generate_coordinates(options = {})
|
15
|
-
key_layer = points.find { |layer| layer.relevant_data? }
|
16
|
-
|
17
|
-
options[:point_distance] = width / (key_layer.points.size - 1).to_f
|
18
|
-
|
19
|
-
coords = []
|
20
|
-
|
21
|
-
key_layer.points.each_with_index do |layer, idx|
|
22
|
-
sum, objects = points.inject([0, 0]) do |arr, elem|
|
23
|
-
if elem.relevant_data?
|
24
|
-
arr[0] += elem.points[idx]
|
25
|
-
arr[1] += 1
|
26
|
-
end
|
27
|
-
arr
|
28
|
-
end
|
37
|
+
coords = []
|
29
38
|
|
30
|
-
|
39
|
+
key_layer.points.each_with_index do |layer, idx|
|
40
|
+
sum, objects = points.inject([0, 0]) do |arr, elem|
|
41
|
+
if elem.relevant_data?
|
42
|
+
arr[0] += elem.points[idx]
|
43
|
+
arr[1] += 1
|
44
|
+
end
|
45
|
+
arr
|
46
|
+
end
|
31
47
|
|
32
|
-
|
48
|
+
average = sum / objects.to_f
|
33
49
|
|
34
|
-
|
35
|
-
y_coord = (height - (height * relative_percent))
|
50
|
+
x_coord = options[:point_distance] * idx
|
36
51
|
|
37
|
-
|
38
|
-
|
52
|
+
relative_percent = ((average == min_value) ? 0 : ((average - min_value) / (max_value - min_value).to_f))
|
53
|
+
y_coord = (height - (height * relative_percent))
|
39
54
|
|
40
|
-
|
55
|
+
coords << [x_coord, y_coord].join(',')
|
41
56
|
end
|
42
|
-
|
43
|
-
|
57
|
+
|
58
|
+
return coords
|
59
|
+
end
|
60
|
+
|
44
61
|
end
|
45
62
|
end
|