tlconnor-scruffy 0.2.17
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.txt +115 -0
- data/LICENCE.txt +20 -0
- data/Manifest.txt +74 -0
- data/README.txt +66 -0
- data/lib/scruffy.rb +30 -0
- data/lib/scruffy/components.rb +22 -0
- data/lib/scruffy/components/axes.rb +23 -0
- data/lib/scruffy/components/background.rb +24 -0
- data/lib/scruffy/components/base.rb +57 -0
- data/lib/scruffy/components/data_markers.rb +41 -0
- data/lib/scruffy/components/graphs.rb +52 -0
- data/lib/scruffy/components/grid.rb +57 -0
- data/lib/scruffy/components/label.rb +17 -0
- data/lib/scruffy/components/legend.rb +147 -0
- data/lib/scruffy/components/style_info.rb +22 -0
- data/lib/scruffy/components/title.rb +19 -0
- data/lib/scruffy/components/value_markers.rb +25 -0
- data/lib/scruffy/components/viewport.rb +37 -0
- data/lib/scruffy/formatters.rb +233 -0
- data/lib/scruffy/graph.rb +205 -0
- data/lib/scruffy/graph_state.rb +29 -0
- data/lib/scruffy/helpers.rb +13 -0
- data/lib/scruffy/helpers/canvas.rb +41 -0
- data/lib/scruffy/helpers/layer_container.rb +119 -0
- data/lib/scruffy/helpers/marker_helper.rb +25 -0
- data/lib/scruffy/helpers/meta.rb +5 -0
- data/lib/scruffy/helpers/point_container.rb +99 -0
- data/lib/scruffy/layers.rb +28 -0
- data/lib/scruffy/layers/all_smiles.rb +137 -0
- data/lib/scruffy/layers/area.rb +46 -0
- data/lib/scruffy/layers/average.rb +67 -0
- data/lib/scruffy/layers/bar.rb +73 -0
- data/lib/scruffy/layers/base.rb +211 -0
- data/lib/scruffy/layers/box.rb +114 -0
- data/lib/scruffy/layers/line.rb +46 -0
- data/lib/scruffy/layers/multi.rb +74 -0
- data/lib/scruffy/layers/multi_bar.rb +51 -0
- data/lib/scruffy/layers/pie.rb +123 -0
- data/lib/scruffy/layers/pie_slice.rb +119 -0
- data/lib/scruffy/layers/scatter.rb +29 -0
- data/lib/scruffy/layers/sparkline_bar.rb +39 -0
- data/lib/scruffy/layers/stacked.rb +87 -0
- data/lib/scruffy/rasterizers.rb +14 -0
- data/lib/scruffy/rasterizers/batik_rasterizer.rb +39 -0
- data/lib/scruffy/rasterizers/mini_magick_rasterizer.rb +24 -0
- data/lib/scruffy/rasterizers/rmagick_rasterizer.rb +27 -0
- data/lib/scruffy/renderers.rb +23 -0
- data/lib/scruffy/renderers/axis_legend.rb +41 -0
- data/lib/scruffy/renderers/base.rb +95 -0
- data/lib/scruffy/renderers/cubed.rb +44 -0
- data/lib/scruffy/renderers/cubed3d.rb +53 -0
- data/lib/scruffy/renderers/empty.rb +22 -0
- data/lib/scruffy/renderers/pie.rb +20 -0
- data/lib/scruffy/renderers/reversed.rb +17 -0
- data/lib/scruffy/renderers/sparkline.rb +10 -0
- data/lib/scruffy/renderers/split.rb +48 -0
- data/lib/scruffy/renderers/standard.rb +37 -0
- data/lib/scruffy/themes.rb +177 -0
- data/lib/scruffy/version.rb +9 -0
- data/test/graph_creation_test.rb +286 -0
- data/test/test_helper.rb +2 -0
- metadata +150 -0
@@ -0,0 +1,114 @@
|
|
1
|
+
module Scruffy::Layers
|
2
|
+
# ==Scruffy::Layers::Box
|
3
|
+
#
|
4
|
+
# Author:: Brasten Sager
|
5
|
+
# Date:: August 6th, 2006
|
6
|
+
#
|
7
|
+
# Standard bar graph.
|
8
|
+
class Box < Base
|
9
|
+
|
10
|
+
# Draw box plot.
|
11
|
+
def draw(svg, coords, options = {})
|
12
|
+
coords.each_with_index do |coord,idx|
|
13
|
+
x, y, bar_height = (coord.first), coord.last, 1#(height - coord.last)
|
14
|
+
|
15
|
+
valh = max_value + min_value * -1 #value_height
|
16
|
+
maxh = max_value * height / valh #positive area height
|
17
|
+
minh = min_value * height / valh #negative area height
|
18
|
+
#puts "height = #{height} and max_value = #{max_value} and min_value = #{min_value} and y = #{y} and point = #{points[idx]}"
|
19
|
+
|
20
|
+
#if points[idx] > 0
|
21
|
+
# bar_height = points[idx]*maxh/max_value
|
22
|
+
#else
|
23
|
+
# bar_height = points[idx]*minh/min_value
|
24
|
+
#end
|
25
|
+
|
26
|
+
#puts " y = #{y} and point = #{points[idx]}"
|
27
|
+
#svg.g(:transform => "translate(-#{relative(0.5)}, -#{relative(0.5)})") {
|
28
|
+
# svg.rect( :x => x, :y => y, :width => @bar_width + relative(1), :height => bar_height + relative(1),
|
29
|
+
# :style => "fill: black; fill-opacity: 0.15; stroke: none;" )
|
30
|
+
# svg.rect( :x => x+relative(0.5), :y => y+relative(2), :width => @bar_width + relative(1), :height => bar_height - relative(0.5),
|
31
|
+
# :style => "fill: black; fill-opacity: 0.15; stroke: none;" )
|
32
|
+
#
|
33
|
+
#}
|
34
|
+
|
35
|
+
svg.line(:x1=>x+@bar_width/2,:x2=>x+@bar_width/2,:y1=>y[0],:y2=>y[4], :style => "stroke:#{(outline.to_s || options[:theme].marker || 'white').to_s}; stroke-width:1")
|
36
|
+
svg.line(:x1=>x+@bar_width/4,:x2=>x+@bar_width/4*3,:y1=>y[0],:y2=>y[0], :style => "stroke:#{(outline.to_s || options[:theme].marker || 'white').to_s}; stroke-width:1")
|
37
|
+
svg.line(:x1=>x+@bar_width/4,:x2=>x+@bar_width/4*3,:y1=>y[4],:y2=>y[4], :style => "stroke:#{(outline.to_s || options[:theme].marker || 'white').to_s}; stroke-width:1")
|
38
|
+
svg.rect( :x => x, :y => y[1], :width => @bar_width, :height => (y[1]-y[3])*-1,
|
39
|
+
:fill => color.to_s, 'style' => "opacity: #{opacity}; stroke:#{(outline.to_s || options[:theme].marker || 'white').to_s}; stroke-width:1;" )
|
40
|
+
svg.line(:x1=>x,:x2=>x+@bar_width,:y1=>y[2],:y2=>y[2], :style => "stroke:#{(outline.to_s || options[:theme].marker || 'white').to_s}; stroke-width:1")
|
41
|
+
#svg.rect( :x => x, :y => y, :width => @bar_width, :height => bar_height,
|
42
|
+
# :fill => color.to_s, 'style' => "opacity: #{opacity}; stroke: none;" )
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
# Returns the highest value in any of this container's layers.
|
48
|
+
#
|
49
|
+
# If padding is set to :padded, a 15% padding is added to the highest value.
|
50
|
+
def top_value(padding=nil) # :nodoc:
|
51
|
+
topval = points[0].max
|
52
|
+
points.each do |point_set|
|
53
|
+
topval = ( (topval < point_set.max) ? point_set.max : topval )
|
54
|
+
end
|
55
|
+
#topval = layers.inject(0) { |max, layer| (max = ((max < layer.top_value) ? layer.top_value : max)) unless layer.top_value.nil?; max }
|
56
|
+
below_zero = (topval <= 0)
|
57
|
+
topval = padding == :padded ? (topval + ((topval - bottom_value(nil)) * 0.15)) : topval
|
58
|
+
(below_zero && topval > 0) ? 0 : topval
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the lowest value in any of this container's layers.
|
62
|
+
#
|
63
|
+
# If padding is set to :padded, a 15% padding is added below the lowest value.
|
64
|
+
# If the lowest value is greater than zero, then the padding will not cross the zero line, preventing
|
65
|
+
# negative values from being introduced into the graph purely due to padding.
|
66
|
+
def bottom_value(padding=nil) # :nodoc:
|
67
|
+
botval = points[0].min
|
68
|
+
points.each do |point_set|
|
69
|
+
botval = ( (botval>point_set.min) ? point_set.min : botval )
|
70
|
+
end
|
71
|
+
#botval = layers.inject(0) do |min, layer|
|
72
|
+
# (min = ((min > layer.bottom_value) ? layer.bottom_value : min)) unless layer.bottom_value.nil?
|
73
|
+
# min
|
74
|
+
#end
|
75
|
+
above_zero = (botval >= 0)
|
76
|
+
botval = (botval - ((top_value(nil) - botval) * 0.15)) if padding == :padded
|
77
|
+
|
78
|
+
# Don't introduce negative values solely due to padding.
|
79
|
+
# A user-provided value must be negative before padding will extend into negative values.
|
80
|
+
(above_zero && botval < 0) ? 0 : botval
|
81
|
+
end
|
82
|
+
|
83
|
+
protected
|
84
|
+
|
85
|
+
# Due to the size of the bar graph, X-axis coords must
|
86
|
+
# be squeezed so that the bars do not hang off the ends
|
87
|
+
# of the graph.
|
88
|
+
#
|
89
|
+
# Unfortunately this just mean that bar-graphs and most other graphs
|
90
|
+
# end up on different points. Maybe adding a padding to the coordinates
|
91
|
+
# should be a graph-wide thing?
|
92
|
+
#
|
93
|
+
# Update : x-axis coords for lines and area charts should now line
|
94
|
+
# up with the center of bar charts.
|
95
|
+
|
96
|
+
def generate_coordinates(options = {})
|
97
|
+
@bar_width = (width / points.size) * 0.9
|
98
|
+
options[:point_distance] = (width - (width / points.size)) / (points.size - 1).to_f
|
99
|
+
|
100
|
+
#TODO more array work with index, try to rework to be accepting of hashes
|
101
|
+
coords = (0...points.size).map do |idx|
|
102
|
+
x_coord = (options[:point_distance] * idx) + (width / points.size * 0.5) - (@bar_width * 0.5)
|
103
|
+
y_coords = []
|
104
|
+
points[idx].each do |point|
|
105
|
+
relative_percent = ((point == min_value) ? 0 : ((point - min_value) / (max_value - min_value).to_f))
|
106
|
+
y_coord = (height - (height * relative_percent))
|
107
|
+
y_coords << y_coord
|
108
|
+
end
|
109
|
+
[x_coord, y_coords]
|
110
|
+
end
|
111
|
+
coords
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Scruffy::Layers
|
2
|
+
# ==Scruffy::Layers::Line
|
3
|
+
#
|
4
|
+
# Author:: Brasten Sager
|
5
|
+
# Date:: August 7th, 2006
|
6
|
+
#
|
7
|
+
# Line graph.
|
8
|
+
class Line < Base
|
9
|
+
|
10
|
+
# Renders line graph.
|
11
|
+
#
|
12
|
+
# Options:
|
13
|
+
# See initialize()
|
14
|
+
def draw(svg, coords, options={})
|
15
|
+
|
16
|
+
# Include options provided when the object was created
|
17
|
+
options.merge!(@options)
|
18
|
+
|
19
|
+
stroke_width = (options[:relativestroke]) ? relative(options[:stroke_width]) : options[:stroke_width]
|
20
|
+
style = (options[:style]) ? options[:style] : ''
|
21
|
+
|
22
|
+
if options[:shadow]
|
23
|
+
svg.g(:class => 'shadow', :transform => "translate(#{relative(0.5)}, #{relative(0.5)})") {
|
24
|
+
svg.polyline( :points => stringify_coords(coords).join(' '), :fill => 'transparent',
|
25
|
+
:stroke => 'black', 'stroke-width' => stroke_width,
|
26
|
+
:style => 'fill-opacity: 0; stroke-opacity: 0.35' )
|
27
|
+
|
28
|
+
if options[:dots]
|
29
|
+
coords.each { |coord| svg.circle( :cx => coord.first, :cy => coord.last + relative(0.9), :r => stroke_width,
|
30
|
+
:style => "stroke-width: #{stroke_width}; stroke: black; opacity: 0.35;" ) }
|
31
|
+
end
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
svg.polyline( :points => stringify_coords(coords).join(' '), :fill => 'none', :stroke => @color.to_s,
|
37
|
+
'stroke-width' => stroke_width, :style => style )
|
38
|
+
|
39
|
+
if options[:dots]
|
40
|
+
coords.each { |coord| svg.circle( :cx => coord.first, :cy => coord.last, :r => stroke_width,
|
41
|
+
:style => "stroke-width: #{stroke_width}; stroke: #{color.to_s}; fill: #{color.to_s}" ) }
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Scruffy::Layers
|
2
|
+
# ==Scruffy::Layers::Multi
|
3
|
+
#
|
4
|
+
# Author:: Jeremy Green
|
5
|
+
# Date:: July 29th, 2009
|
6
|
+
#
|
7
|
+
# Based on:: Scruffy::Layers::Stacked by
|
8
|
+
# Author:: Brasten Sager
|
9
|
+
# Date:: August 12th, 2006
|
10
|
+
#
|
11
|
+
# Provides a generic way for displaying multiple bar graphs side by side.
|
12
|
+
class Multi < Base
|
13
|
+
include Scruffy::Helpers::LayerContainer
|
14
|
+
|
15
|
+
# Returns new Multi graph.
|
16
|
+
#
|
17
|
+
# You can provide a block for easily adding layers during (just after) initialization.
|
18
|
+
# Example:
|
19
|
+
# Multi.new do |multi|
|
20
|
+
# multi << Scruffy::Layers::Line.new( ... )
|
21
|
+
# multi.add(:multi_bar, 'My Bar', [...])
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# The initialize method passes itself to the block, and since multi is a LayerContainer,
|
25
|
+
# layers can be added just as if they were being added to Graph.
|
26
|
+
def initialize(options={}, &block)
|
27
|
+
super(options)
|
28
|
+
|
29
|
+
block.call(self) # Allow for population of data with a block during initialization.
|
30
|
+
end
|
31
|
+
|
32
|
+
# Overrides Base#render to fiddle with layers' points to achieve a multi effect.
|
33
|
+
def render(svg, options = {})
|
34
|
+
#TODO ensure this works with new points
|
35
|
+
#current_points = points
|
36
|
+
layers.each_with_index do |layer,i|
|
37
|
+
|
38
|
+
#real_points = layer.points
|
39
|
+
#layer.points = current_points
|
40
|
+
layer_options = options.dup
|
41
|
+
|
42
|
+
layer_options[:num_bars] = layers.size
|
43
|
+
layer_options[:position] = i
|
44
|
+
layer_options[:color] = layer.preferred_color || layer.color || options[:theme].next_color
|
45
|
+
layer.render(svg, layer_options)
|
46
|
+
|
47
|
+
options.merge(layer_options)
|
48
|
+
|
49
|
+
#layer.points = real_points
|
50
|
+
#layer.points.each_with_index { |val, idx| current_points[idx] -= val }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# A multi graph has many data sets. Return legend information for all of them.
|
55
|
+
def legend_data
|
56
|
+
if relevant_data?
|
57
|
+
retval = []
|
58
|
+
layers.each do |layer|
|
59
|
+
retval << layer.legend_data
|
60
|
+
end
|
61
|
+
retval
|
62
|
+
else
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# TODO, special points accessor
|
68
|
+
|
69
|
+
|
70
|
+
def points=(val)
|
71
|
+
throw ArgumentsError, "Multi layers cannot accept points, only other layers."
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Scruffy::Layers
|
2
|
+
# ==Scruffy::Layers::MultiBar
|
3
|
+
#
|
4
|
+
# Author:: Jeremy Green
|
5
|
+
# Date:: July 29th, 2009
|
6
|
+
#
|
7
|
+
# Based on:: Scruffy::Layers::Bar by
|
8
|
+
# Author:: Brasten Sager
|
9
|
+
# Date:: August 6th, 2006
|
10
|
+
#
|
11
|
+
# Standard multi_bar graph.
|
12
|
+
class MultiBar < Bar
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
# Due to the size of the bar graph, X-axis coords must
|
19
|
+
# be squeezed so that the bars do not hang off the ends
|
20
|
+
# of the graph.
|
21
|
+
#
|
22
|
+
# Unfortunately this just mean that bar-graphs and most other graphs
|
23
|
+
# end up on different points. Maybe adding a padding to the coordinates
|
24
|
+
# should be a graph-wide thing?
|
25
|
+
#
|
26
|
+
# Update : x-axis coords for lines and area charts should now line
|
27
|
+
# up with the center of bar charts.
|
28
|
+
|
29
|
+
def generate_coordinates(options = {})
|
30
|
+
@point_width = (width / points.size)
|
31
|
+
@point_space = @point_width * 0.1
|
32
|
+
@point_width = @point_width - @point_space
|
33
|
+
@bar_width = @point_width/options[:num_bars]
|
34
|
+
@bar_space = @bar_width * 0.1
|
35
|
+
@bar_width = @bar_width * 0.9
|
36
|
+
|
37
|
+
#options[:point_distance] = (width - (width / points.size)) / (points.size - 1).to_f
|
38
|
+
|
39
|
+
#TODO more array work with index, try to rework to be accepting of hashes
|
40
|
+
coords = (0...points.size).map do |idx|
|
41
|
+
|
42
|
+
x_coord = (@point_width * idx) + @point_space/2 + @point_space*idx + @bar_width * options[:position] + @bar_space*(options[:position].to_f - 0.5)
|
43
|
+
|
44
|
+
relative_percent = ((points[idx] == min_value) ? 0 : ((points[idx] - min_value) / (max_value - min_value).to_f))
|
45
|
+
y_coord = (height - (height * relative_percent))
|
46
|
+
[x_coord, y_coord]
|
47
|
+
end
|
48
|
+
coords
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module Scruffy::Layers
|
2
|
+
# ==Scruffy::Layers::Pie
|
3
|
+
#
|
4
|
+
# Author:: A.J. Ostman
|
5
|
+
# Date:: August 15, 2006
|
6
|
+
#
|
7
|
+
# Provides a container for pie slice.
|
8
|
+
class Pie < Base
|
9
|
+
include Scruffy::Helpers::LayerContainer
|
10
|
+
|
11
|
+
# Basic Example:
|
12
|
+
#
|
13
|
+
# graph = Scruffy::Graph.new
|
14
|
+
# graph.title = "Snack Preference"
|
15
|
+
# graph.renderer = Scruffy::Renderers::Pie.new
|
16
|
+
# graph.add :pie, 'Example', {'Apples' => 90, 'Orange' => 60, 'Taco' => 30}
|
17
|
+
#
|
18
|
+
# Or, using a block to add slices:
|
19
|
+
#
|
20
|
+
# graph = Scruffy::Graph.new
|
21
|
+
# graph.title = "Snack Preference"
|
22
|
+
# graph.renderer = Scruffy::Renderers::Pie.new
|
23
|
+
# graph.add :pie do |pie|
|
24
|
+
# pie.add :pie_slice, 'Apple', [90]
|
25
|
+
# pie.add :pie_slice, 'Orange', [60]
|
26
|
+
# pie.add :pie_slice, 'Taco', [30]
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# Another Example:
|
30
|
+
# graph.title = "Scruff-Pac!"
|
31
|
+
# graph.renderer = Scruffy::Renderers::Pie.new
|
32
|
+
# graph.add :pie, :diameter => 40, :degree_offset => 30 do |pie|
|
33
|
+
# pie.add :pie_slice, '', [160], :preferred_color => "yellow", :shadow => true,
|
34
|
+
# :shadow_x => -1, :shadow_y => 1, :shadow_color=>"black", :shadow_opacity => 0.4
|
35
|
+
# pie.add :pie_slice, '', [50], :preferred_color => "green", :explode => 5, :diameter => 20,
|
36
|
+
# :shadow => true, :shadow_x => -1, :shadow_y => 1, :shadow_color => "black", :shadow_opacity => 0.4
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# graph.add :pie, :diameter => 3, :center_x => 48, :center_y=> 37, :degree_offset => 20 do |pie|
|
40
|
+
# pie.add :pie_slice, '', [160], :preferred_color => "blue", :stroke => "black"
|
41
|
+
# end
|
42
|
+
|
43
|
+
|
44
|
+
# Setup Constants
|
45
|
+
RADIANS = Math::PI/180
|
46
|
+
|
47
|
+
attr_accessor :diameter
|
48
|
+
attr_accessor :percent_used
|
49
|
+
attr_accessor :degree_offset
|
50
|
+
attr_accessor :scaler
|
51
|
+
attr_accessor :center_x, :center_y
|
52
|
+
|
53
|
+
|
54
|
+
# The initialize method passes itself to the block, and since Pie is a
|
55
|
+
# LayerContainer, layers (pie slice) can be added just as if they were being
|
56
|
+
# added to Graph.
|
57
|
+
def initialize(options = {}, &block)
|
58
|
+
super(options)
|
59
|
+
|
60
|
+
# Allow for population of data with a block during initialization.
|
61
|
+
if block
|
62
|
+
block.call(self)
|
63
|
+
else
|
64
|
+
# Otherwise, just iterate over the points, adding the slices
|
65
|
+
if @points.class == Hash
|
66
|
+
@points.keys.each {|k|
|
67
|
+
self.add :pie_slice, k.to_s, [@points[k]]}
|
68
|
+
end
|
69
|
+
if @points.class == Array
|
70
|
+
@points.each {|v|
|
71
|
+
self.add :pie_slice, '', [v]}
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
# Overrides Base#render to fiddle with layers' points to achieve a stacked
|
78
|
+
# effect.
|
79
|
+
def render(svg, options = {})
|
80
|
+
# #current_points = points.dup
|
81
|
+
|
82
|
+
@scaler = 1
|
83
|
+
total = 0
|
84
|
+
|
85
|
+
layers.each do |layer|
|
86
|
+
total += layer.sum_values
|
87
|
+
end
|
88
|
+
|
89
|
+
@scaler = 100.0 / total
|
90
|
+
|
91
|
+
@percent_used = 30
|
92
|
+
|
93
|
+
layers.each do |layer|
|
94
|
+
layer_options = options.dup
|
95
|
+
layer_options = layer_options.merge(@options)
|
96
|
+
layer_options = layer_options.merge(layer.options)
|
97
|
+
layer_options[:scaler] = @scaler
|
98
|
+
layer_options[:percent_used] = @percent_used
|
99
|
+
@percent_used += @scaler * layer.sum_values
|
100
|
+
layer_options[:color] = layer.preferred_color || layer.color || options[:theme].next_color
|
101
|
+
|
102
|
+
layer.render(svg, layer_options)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# A stacked graph has many data sets. Return legend information for all of them.
|
107
|
+
def legend_data
|
108
|
+
if relevant_data?
|
109
|
+
retval = []
|
110
|
+
layers.each do |layer|
|
111
|
+
retval << layer.legend_data
|
112
|
+
end
|
113
|
+
retval
|
114
|
+
else
|
115
|
+
nil
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def points=(val)
|
120
|
+
throw ArgumentsError, "Pie layers cannot accept points, only pie slices."
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module Scruffy::Layers
|
2
|
+
# ==Scruffy::Layers::PieSlice
|
3
|
+
#
|
4
|
+
# Author:: A.J. Ostman
|
5
|
+
# Date:: August 14th, 2006
|
6
|
+
#
|
7
|
+
# Basic Pie Chart Slice..
|
8
|
+
|
9
|
+
class PieSlice < Base
|
10
|
+
|
11
|
+
# Setup Constants
|
12
|
+
RADIANS = Math::PI / 180
|
13
|
+
MARKER_OFFSET_RATIO = 1.2
|
14
|
+
MARKER_FONT_SIZE = 6
|
15
|
+
|
16
|
+
attr_accessor :diameter
|
17
|
+
attr_accessor :percent_used
|
18
|
+
attr_accessor :degree_offset
|
19
|
+
attr_accessor :scaler
|
20
|
+
attr_accessor :center_x, :center_y
|
21
|
+
|
22
|
+
def draw(svg, coords, options = {})
|
23
|
+
# Scaler is the multiplier to normalize the values to a percentage across
|
24
|
+
# the Pie Chart
|
25
|
+
@scaler = options[:scaler] || 1
|
26
|
+
|
27
|
+
# Degree Offset is degrees by which the pie chart is twisted when it
|
28
|
+
# begins
|
29
|
+
@degree_offset = options[:degree_offset] || @options[:degree_offset] || 0
|
30
|
+
|
31
|
+
# Percent Used keeps track of where in the pie chart we are
|
32
|
+
@percent_used = options[:percent_used] || @options[:percent_used] || 0
|
33
|
+
|
34
|
+
# Diameter of the pie chart defaults to 80% of the height
|
35
|
+
@diameter = relative(options[:diameter]) || relative(@options[:diameter]) || relative(80.0)
|
36
|
+
|
37
|
+
# Stroke
|
38
|
+
stroke = options[:stroke] || @options[:stroke] || "none"
|
39
|
+
|
40
|
+
# Shadow
|
41
|
+
shadow = options[:shadow] || @options[:shadow_] || false
|
42
|
+
shadow_x = relative(options[:shadow_x]) || relative(@options[:shadow_x]) || relative(-0.5)
|
43
|
+
shadow_y = relative(options[:shadow_y]) || relative(@options[:shadow_y]) || relative(0.5)
|
44
|
+
shadow_color = options[:shadow_color] || @options[:shadow_color] || "white"
|
45
|
+
shadow_opacity = options[:shadow_opacity] || @options[:shadow_opacity] || 0.06
|
46
|
+
|
47
|
+
# Coordinates for the center of the pie chart.
|
48
|
+
@center_x = relative_width(options[:center_x]) || relative_width(@options[:center_x]) || relative_width(50)
|
49
|
+
@center_y = relative_height(options[:center_y]) || relative_height(@options[:center_y]) || relative_height(50)
|
50
|
+
radius = @diameter / 2.0
|
51
|
+
|
52
|
+
# Graphing calculated using percent of graph. We later multiply by 3.6 to
|
53
|
+
# convert to 360 degree system.
|
54
|
+
percent = @scaler * sum_values
|
55
|
+
|
56
|
+
|
57
|
+
# Calculate the Radian Start Point
|
58
|
+
radians_start = ((@percent_used * 3.6) + @degree_offset) * RADIANS
|
59
|
+
# Calculate the Radian End Point
|
60
|
+
radians_end = ((@percent_used + percent) * 3.6 + @degree_offset) * RADIANS
|
61
|
+
|
62
|
+
radians_mid_point = radians_start + ((radians_end - radians_start) / 2)
|
63
|
+
|
64
|
+
if options[:explode]
|
65
|
+
@center_x = @center_x + (Math.sin(radians_mid_point) * relative(options[:explode]))
|
66
|
+
@center_y = @center_y - (Math.cos(radians_mid_point) * relative(options[:explode]))
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
# Calculate the beginning coordinates
|
71
|
+
x_start = @center_x + (Math.sin(radians_start) * radius)
|
72
|
+
y_start = @center_y - (Math.cos(radians_start) * radius)
|
73
|
+
|
74
|
+
# Calculate the End Coords
|
75
|
+
x_end = @center_x + (Math.sin(radians_end) * radius)
|
76
|
+
y_end = @center_y - (Math.cos(radians_end) * radius)
|
77
|
+
|
78
|
+
|
79
|
+
|
80
|
+
# If percentage is really really close to 100% then draw a circle instead!
|
81
|
+
if percent >= 99.9999
|
82
|
+
|
83
|
+
if shadow
|
84
|
+
svg.circle(:cx => "#{@center_x + shadow_x}", :cy => "#{@center_y + shadow_y}", :r=>"#{radius}",:stroke => "none",
|
85
|
+
:fill => shadow_color.to_s, :style => "fill-opacity: #{shadow_opacity.to_s};")
|
86
|
+
end
|
87
|
+
|
88
|
+
svg.circle(:cx => "#{@center_x}", :cy => "#{@center_y}", :r=>"#{radius}",:stroke => stroke, :fill => color.to_s)
|
89
|
+
|
90
|
+
else
|
91
|
+
if shadow
|
92
|
+
svg.path(:d => "M#{@center_x + shadow_x},#{@center_y + shadow_y} L#{x_start + shadow_x},#{y_start + shadow_y} A#{radius},#{radius} 0, #{percent >= 50 ? '1' : '0'}, 1, #{x_end + shadow_x} #{y_end + shadow_y} Z",
|
93
|
+
:fill => shadow_color.to_s, :style => "fill-opacity: #{shadow_opacity.to_s};")
|
94
|
+
end
|
95
|
+
|
96
|
+
svg.path(:d => "M#{@center_x},#{@center_y} L#{x_start},#{y_start} A#{radius},#{radius} 0, #{percent >= 50 ? '1' : '0'}, 1, #{x_end} #{y_end} Z",
|
97
|
+
:stroke => stroke, :fill => color.to_s)
|
98
|
+
end
|
99
|
+
|
100
|
+
text_x = @center_x + (Math.sin(radians_mid_point) * radius * MARKER_OFFSET_RATIO)
|
101
|
+
text_y = @center_y - (Math.cos(radians_mid_point) * radius * MARKER_OFFSET_RATIO)
|
102
|
+
|
103
|
+
svg.text("#{sprintf('%d', percent)}%",
|
104
|
+
:x => text_x,
|
105
|
+
:y => text_y + relative(MARKER_FONT_SIZE / 2),
|
106
|
+
'font-size' => relative(MARKER_FONT_SIZE),
|
107
|
+
'font-family' => options[:theme].font_family,
|
108
|
+
:fill => (options[:theme].marker || 'black').to_s,
|
109
|
+
'text-anchor' => 'middle')
|
110
|
+
end
|
111
|
+
|
112
|
+
protected
|
113
|
+
def generate_coordinates(options = {})
|
114
|
+
# Coordinate Generation didn't make much sense here. Overridden just
|
115
|
+
# because Brasten said this would be overridden.
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|