jagthedrummer-scruffy 0.2.9 → 0.2.10
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +5 -0
- data/lib/scruffy/components/data_markers.rb +11 -6
- data/lib/scruffy/components/graphs.rb +1 -0
- data/lib/scruffy/components/grid.rb +23 -5
- data/lib/scruffy/components/legend.rb +63 -21
- data/lib/scruffy/components/title.rb +1 -1
- data/lib/scruffy/components/value_markers.rb +2 -1
- data/lib/scruffy/graph.rb +17 -7
- data/lib/scruffy/graph_state.rb +4 -0
- data/lib/scruffy/helpers/layer_container.rb +4 -2
- data/lib/scruffy/helpers/marker_helper.rb +3 -1
- data/lib/scruffy/layers/bar.rb +20 -4
- data/lib/scruffy/layers/base.rb +12 -3
- data/lib/scruffy/layers.rb +3 -0
- data/lib/scruffy/renderers/standard.rb +6 -6
- data/lib/scruffy/renderers.rb +1 -0
- data/lib/scruffy/themes.rb +19 -0
- data/test/graph_creation_test.rb +152 -0
- metadata +1 -1
data/Manifest.txt
CHANGED
@@ -24,6 +24,7 @@ lib/scruffy/graph_state.rb
|
|
24
24
|
lib/scruffy/helpers.rb
|
25
25
|
lib/scruffy/helpers/canvas.rb
|
26
26
|
lib/scruffy/helpers/layer_container.rb
|
27
|
+
lib/scruffy/helpers/marker_helper.rb
|
27
28
|
lib/scruffy/helpers/meta.rb
|
28
29
|
lib/scruffy/helpers/point_container.rb
|
29
30
|
lib/scruffy/layers.rb
|
@@ -31,8 +32,11 @@ lib/scruffy/layers/all_smiles.rb
|
|
31
32
|
lib/scruffy/layers/area.rb
|
32
33
|
lib/scruffy/layers/average.rb
|
33
34
|
lib/scruffy/layers/bar.rb
|
35
|
+
lib/scruffy/layers/box.rb
|
34
36
|
lib/scruffy/layers/base.rb
|
35
37
|
lib/scruffy/layers/line.rb
|
38
|
+
lib/scruffy/layers/multi.rb
|
39
|
+
lib/scruffy/layers/multi_bar.rb
|
36
40
|
lib/scruffy/layers/pie.rb
|
37
41
|
lib/scruffy/layers/pie_slice.rb
|
38
42
|
lib/scruffy/layers/scatter.rb
|
@@ -51,6 +55,7 @@ lib/scruffy/renderers/reversed.rb
|
|
51
55
|
lib/scruffy/renderers/sparkline.rb
|
52
56
|
lib/scruffy/renderers/split.rb
|
53
57
|
lib/scruffy/renderers/standard.rb
|
58
|
+
lib/scruffy/renderers/axis_legend.rb
|
54
59
|
lib/scruffy/themes.rb
|
55
60
|
lib/scruffy/version.rb
|
56
61
|
script/console
|
@@ -16,15 +16,20 @@ module Scruffy
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
unless options[:point_markers].nil?
|
19
|
-
|
20
|
-
|
19
|
+
# Updated to set the label in line with the point.
|
20
|
+
point_distance = bounds[:width] / (options[:point_markers].size).to_f
|
21
21
|
(0...options[:point_markers].size).map do |idx|
|
22
|
-
x_coord = point_distance * idx
|
22
|
+
x_coord = point_distance * idx + point_distance/2
|
23
|
+
if options[:point_markers_ticks]
|
24
|
+
svg.line(:x1 => x_coord, :y1 => 0, :x2 => x_coord, :y2 => -2, :style => "stroke:#{(options[:theme].marker || 'white').to_s}, stroke-width:1")
|
25
|
+
end
|
26
|
+
|
23
27
|
svg.text(options[:point_markers][idx],
|
24
|
-
:x =>
|
25
|
-
:y =>
|
26
|
-
'font-size' => relative(90),
|
28
|
+
:x => 0,
|
29
|
+
:y => 0,
|
30
|
+
'font-size' => options[:theme].marker_font_size || relative(90),
|
27
31
|
'font-family' => options[:theme].font_family,
|
32
|
+
:transform => "translate(#{x_coord},#{bounds[:height]}) rotate(#{options[:point_markers_rotation] || 0})",
|
28
33
|
:fill => (options[:theme].marker || 'white').to_s,
|
29
34
|
'text-anchor' => 'middle') unless options[:point_markers][idx].nil?
|
30
35
|
end
|
@@ -31,6 +31,7 @@ module Scruffy
|
|
31
31
|
layer_options[:complexity] = options[:complexity]
|
32
32
|
layer_options[:size] = [bounds[:width], bounds[:height]]
|
33
33
|
layer_options[:color] = layer.preferred_color || layer.color || options[:theme].next_color
|
34
|
+
layer_options[:outline] = layer.preferred_outline || layer.outline || options[:theme].next_outline
|
34
35
|
layer_options[:opacity] = opacity_for(idx)
|
35
36
|
layer_options[:theme] = options[:theme]
|
36
37
|
|
@@ -13,6 +13,11 @@ module Scruffy
|
|
13
13
|
each_marker(markers, options[:min_value], options[:max_value], bounds[:height], options, :value_formatter) do |label, y|
|
14
14
|
svg.line(:x1 => 0, :y1 => y, :x2 => bounds[:width], :y2 => y, :style => "stroke: #{options[:theme].marker.to_s}; stroke-width: #{stroke_width};")
|
15
15
|
end
|
16
|
+
|
17
|
+
#add a 0 line
|
18
|
+
y = (options[:max_value] * bounds[:height])/(options[:max_value] - options[:min_value])
|
19
|
+
svg.line(:x1 => 0, :y1 => y, :x2 => bounds[:width], :y2 => y, :style => "stroke: #{options[:theme].marker.to_s}; stroke-width: #{stroke_width};")
|
20
|
+
|
16
21
|
end
|
17
22
|
end
|
18
23
|
|
@@ -22,12 +27,25 @@ module Scruffy
|
|
22
27
|
include Scruffy::Helpers::Marker
|
23
28
|
|
24
29
|
def draw(svg, bounds, options={})
|
25
|
-
markers = (options[:key_markers] || self.markers) || 5
|
26
|
-
|
27
|
-
stroke_width = options[:stroke_width]
|
28
30
|
|
29
|
-
|
30
|
-
|
31
|
+
if options[:graph].point_markers #get vertical grid lines up with points if there are labels for them
|
32
|
+
point_distance = bounds[:width] / (options[:graph].point_markers.size).to_f
|
33
|
+
stroke_width = options[:stroke_width]
|
34
|
+
(0...options[:graph].point_markers.size).map do |idx|
|
35
|
+
x = point_distance * idx + point_distance/2
|
36
|
+
svg.line(:x1 => x, :y1 => 0, :x2 => x, :y2 => bounds[:height], :style => "stroke: #{options[:theme].marker.to_s}; stroke-width: #{stroke_width};")
|
37
|
+
end
|
38
|
+
#add the far right and far left lines
|
39
|
+
svg.line(:x1 => 0, :y1 => 0, :x2 => 0, :y2 => bounds[:height], :style => "stroke: #{options[:theme].marker.to_s}; stroke-width: #{stroke_width};")
|
40
|
+
svg.line(:x1 => bounds[:width], :y1 => 0, :x2 => bounds[:width], :y2 => bounds[:height], :style => "stroke: #{options[:theme].marker.to_s}; stroke-width: #{stroke_width};")
|
41
|
+
else
|
42
|
+
|
43
|
+
markers = (options[:key_markers] || self.markers) || 5 #options[:point_markers].size#
|
44
|
+
stroke_width = options[:stroke_width]
|
45
|
+
each_marker(markers, options[:min_key], options[:max_key], bounds[:width], options, :key_formatter) do |label, x|
|
46
|
+
svg.line(:x1 => x, :y1 => 0, :x2 => x, :y2 => bounds[:height], :style => "stroke: #{options[:theme].marker.to_s}; stroke-width: #{stroke_width};")
|
47
|
+
end
|
48
|
+
|
31
49
|
end
|
32
50
|
end
|
33
51
|
end
|
@@ -1,5 +1,47 @@
|
|
1
1
|
module Scruffy::Components
|
2
|
-
|
2
|
+
|
3
|
+
class XLegend < Base
|
4
|
+
def draw(svg, bounds, options={})
|
5
|
+
if options[:title]
|
6
|
+
svg.text(options[:x_legend],
|
7
|
+
:class => 'title',
|
8
|
+
:x => (bounds[:width] / 2),
|
9
|
+
:y => bounds[:height],
|
10
|
+
'font-size' => options[:theme].legend_font_size || relative(100),
|
11
|
+
'font-family' => options[:theme].font_family,
|
12
|
+
:fill => options[:theme].marker,
|
13
|
+
:stroke => 'none', 'stroke-width' => '0',
|
14
|
+
'text-anchor' => (@options[:text_anchor] || 'middle'))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end #XLegend
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
class YLegend < Base
|
22
|
+
def draw(svg, bounds, options={})
|
23
|
+
if options[:title]
|
24
|
+
svg.text(options[:y_legend],
|
25
|
+
:class => 'title',
|
26
|
+
:x => (0),
|
27
|
+
:y => 0,
|
28
|
+
'font-size' => options[:theme].legend_font_size || relative(100),
|
29
|
+
'font-family' => options[:theme].font_family,
|
30
|
+
:transform => "translate(#{bounds[:width] / 2},#{bounds[:height]/2}) rotate(#{-90})",
|
31
|
+
:fill => options[:theme].marker,
|
32
|
+
:stroke => 'none', 'stroke-width' => '0',
|
33
|
+
'text-anchor' => (@options[:text_anchor] || 'middle'))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end #YLegend
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
|
41
|
+
|
42
|
+
|
43
|
+
|
44
|
+
|
3
45
|
class Legend < Base
|
4
46
|
FONT_SIZE = 80
|
5
47
|
|
@@ -11,18 +53,18 @@ module Scruffy::Components
|
|
11
53
|
set_line_height = 0.08 * bounds[:height]
|
12
54
|
@line_height = bounds[:height] / legend_info.length
|
13
55
|
@line_height = set_line_height if @line_height >
|
14
|
-
|
56
|
+
set_line_height
|
15
57
|
else
|
16
58
|
set_line_height = 0.90 * bounds[:height]
|
17
59
|
@line_height = set_line_height
|
18
60
|
end
|
19
|
-
|
61
|
+
|
20
62
|
text_height = @line_height * FONT_SIZE / 100
|
21
63
|
# #TODO how does this related to @points?
|
22
64
|
active_width, points = layout(legend_info, vertical)
|
23
|
-
|
65
|
+
|
24
66
|
offset = (bounds[:width] - active_width) / 2 # Nudge over a bit for true centering
|
25
|
-
|
67
|
+
|
26
68
|
# Render Legend
|
27
69
|
points.each_with_index do |point, idx|
|
28
70
|
if vertical
|
@@ -38,18 +80,18 @@ module Scruffy::Components
|
|
38
80
|
# "#{x} #{y} #{@line_height} #{size}"
|
39
81
|
|
40
82
|
svg.rect(:x => x,
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
83
|
+
:y => y,
|
84
|
+
:width => size,
|
85
|
+
:height => size,
|
86
|
+
:fill => legend_info[idx][:color])
|
87
|
+
|
46
88
|
svg.text(legend_info[idx][:title],
|
47
|
-
|
48
|
-
|
89
|
+
:x => x + @line_height,
|
90
|
+
:y => y + text_height * 0.75,
|
49
91
|
'font-size' => text_height,
|
50
92
|
'font-family' => options[:theme].font_family,
|
51
|
-
|
52
|
-
|
93
|
+
:style => "color: #{options[:theme].marker || 'white'}",
|
94
|
+
:fill => (options[:theme].marker || 'white'))
|
53
95
|
end
|
54
96
|
end # draw
|
55
97
|
|
@@ -60,9 +102,9 @@ module Scruffy::Components
|
|
60
102
|
def relevant_legend_info(layers, categories=(@options[:category] ? [@options[:category]] : @options[:categories]))
|
61
103
|
legend_info = layers.inject([]) do |arr, layer|
|
62
104
|
if categories.nil? ||
|
63
|
-
|
64
|
-
|
65
|
-
|
105
|
+
(categories.include?(layer.options[:category]) ||
|
106
|
+
(layer.options[:categories] && (categories & layer.options[:categories]).size > 0) )
|
107
|
+
|
66
108
|
data = layer.legend_data
|
67
109
|
arr << data if data.is_a?(Hash)
|
68
110
|
arr = arr + data if data.is_a?(Array)
|
@@ -70,7 +112,7 @@ module Scruffy::Components
|
|
70
112
|
arr
|
71
113
|
end
|
72
114
|
end # relevant_legend_info
|
73
|
-
|
115
|
+
|
74
116
|
# Returns an array consisting of the total width needed by the legend
|
75
117
|
# information, as well as an array of @x-coords for each element. If
|
76
118
|
# vertical, then these are @y-coords, and @x is 0
|
@@ -84,7 +126,7 @@ module Scruffy::Components
|
|
84
126
|
longest = longest < cur_length ? cur_length : longest
|
85
127
|
}
|
86
128
|
y_positions = []
|
87
|
-
|
129
|
+
(0..legend_info_array.length - 1).each {|y|
|
88
130
|
y_positions << y * @line_height
|
89
131
|
}
|
90
132
|
[longest, y_positions]
|
@@ -94,12 +136,12 @@ module Scruffy::Components
|
|
94
136
|
enum[1] << enum.first # Add location to points
|
95
137
|
enum[0] += relative(50) # Add room for color box
|
96
138
|
enum[0] += (relative(50) * elem[:title].length) # Add room for text
|
97
|
-
|
139
|
+
|
98
140
|
[enum.first, enum.last]
|
99
141
|
end
|
100
142
|
end
|
101
143
|
end
|
102
|
-
|
144
|
+
|
103
145
|
end # class Legend
|
104
146
|
|
105
147
|
end # Scruffy::Components
|
@@ -7,7 +7,7 @@ module Scruffy
|
|
7
7
|
:class => 'title',
|
8
8
|
:x => (bounds[:width] / 2),
|
9
9
|
:y => bounds[:height],
|
10
|
-
'font-size' => relative(100),
|
10
|
+
'font-size' => options[:theme].title_font_size || relative(100),
|
11
11
|
'font-family' => options[:theme].font_family,
|
12
12
|
:fill => options[:theme].marker,
|
13
13
|
:stroke => 'none', 'stroke-width' => '0',
|
@@ -10,10 +10,11 @@ module Scruffy
|
|
10
10
|
markers = (options[:markers] || self.markers) || 5
|
11
11
|
|
12
12
|
each_marker(markers, options[:min_value], options[:max_value], bounds[:height], options, :value_formatter) do |label, y|
|
13
|
+
|
13
14
|
svg.text( label,
|
14
15
|
:x => bounds[:width],
|
15
16
|
:y => (bounds[:height] - y),
|
16
|
-
'font-size' => relative(8),
|
17
|
+
'font-size' => options[:theme].marker_font_size || relative(8),
|
17
18
|
'font-family' => options[:theme].font_family,
|
18
19
|
:fill => ((options.delete(:marker_color_override) || options[:theme].marker) || 'white').to_s,
|
19
20
|
'text-anchor' => 'end')
|
data/lib/scruffy/graph.rb
CHANGED
@@ -79,12 +79,12 @@ module Scruffy
|
|
79
79
|
include Scruffy::Helpers::LayerContainer
|
80
80
|
|
81
81
|
# Delegating these getters to the internal state object.
|
82
|
-
def_delegators :internal_state, :title, :theme, :default_type,
|
83
|
-
:point_markers, :value_formatter, :rasterizer,
|
82
|
+
def_delegators :internal_state, :title,:x_legend,:y_legend, :theme, :default_type,
|
83
|
+
:point_markers,:point_markers_rotation,:point_markers_ticks, :value_formatter, :rasterizer,
|
84
84
|
:key_formatter
|
85
85
|
|
86
|
-
def_delegators :internal_state, :title=, :theme=, :default_type=,
|
87
|
-
:point_markers=, :value_formatter=, :rasterizer=,
|
86
|
+
def_delegators :internal_state, :title=, :theme=,:x_legend=,:y_legend=, :default_type=,
|
87
|
+
:point_markers=,:point_markers_rotation=,:point_markers_ticks=, :value_formatter=, :rasterizer=,
|
88
88
|
:key_formatter=
|
89
89
|
|
90
90
|
attr_reader :renderer # Writer defined below
|
@@ -98,11 +98,15 @@ module Scruffy
|
|
98
98
|
# Options:
|
99
99
|
#
|
100
100
|
# title:: Graph's title
|
101
|
+
# x_legend :: Title for X Axis
|
102
|
+
# y_legend :: Title for Y Axis
|
101
103
|
# theme:: A theme object to use when rendering graph
|
102
104
|
# layers:: An array of Layers for this graph to use
|
103
105
|
# default_type:: A symbol indicating the default type of Layer for this graph
|
104
106
|
# value_formatter:: Sets a formatter used to modify marker values prior to rendering
|
105
107
|
# point_markers:: Sets the x-axis marker values
|
108
|
+
# point_markers_rotation:: Sets the angle of rotation for x-axis marker values
|
109
|
+
# point_markers_ticks:: Sets a small tick mark above each marker value. Helful when used with rotation.
|
106
110
|
# rasterizer:: Sets the rasterizer to use when rendering to an image format. Defaults to RMagick.
|
107
111
|
def initialize(*args)
|
108
112
|
self.default_type = args.shift if args.first.is_a?(Symbol)
|
@@ -110,13 +114,15 @@ module Scruffy
|
|
110
114
|
raise ArgumentError, "The arguments provided are not supported." if args.size > 0
|
111
115
|
|
112
116
|
options ||= {}
|
117
|
+
|
118
|
+
|
113
119
|
self.theme = Scruffy::Themes::Standard.new
|
114
120
|
self.renderer = Scruffy::Renderers::Standard.new
|
115
121
|
self.rasterizer = Scruffy::Rasterizers::RMagickRasterizer.new
|
116
122
|
self.value_formatter = Scruffy::Formatters::Number.new
|
117
123
|
self.key_formatter = Scruffy::Formatters::Number.new
|
118
124
|
|
119
|
-
%w(title theme layers default_type value_formatter point_markers rasterizer key_formatter).each do |arg|
|
125
|
+
%w(title x_legend y_legend theme layers default_type value_formatter point_markers point_markers_rotation point_markers_ticks rasterizer key_formatter).each do |arg|
|
120
126
|
self.send("#{arg}=".to_sym, options.delete(arg.to_sym)) unless options[arg.to_sym].nil?
|
121
127
|
end
|
122
128
|
|
@@ -141,11 +147,15 @@ module Scruffy
|
|
141
147
|
options[:value_formatter] ||= value_formatter
|
142
148
|
options[:key_formatter] ||= key_formatter
|
143
149
|
options[:point_markers] ||= point_markers
|
150
|
+
options[:point_markers_rotation] ||= point_markers_rotation
|
151
|
+
options[:point_markers_ticks] ||= point_markers_ticks
|
144
152
|
options[:size] ||= (options[:width] ? [options[:width], (options.delete(:width) * 0.6).to_i] : [600, 360])
|
145
153
|
options[:title] ||= title
|
154
|
+
options[:x_legend] ||= x_legend
|
155
|
+
options[:y_legend] ||= y_legend
|
146
156
|
options[:layers] ||= layers
|
147
|
-
options[:min_value] ||= bottom_value
|
148
|
-
options[:max_value] ||= top_value
|
157
|
+
options[:min_value] ||= bottom_value(options[:padding] ? options[:padding] : nil)
|
158
|
+
options[:max_value] ||= top_value(options[:padding] ? options[:padding] : nil)
|
149
159
|
options[:min_key] ||= bottom_key
|
150
160
|
options[:max_key] ||= top_key
|
151
161
|
options[:graph] ||= self
|
data/lib/scruffy/graph_state.rb
CHANGED
@@ -11,9 +11,13 @@ module Scruffy
|
|
11
11
|
class GraphState
|
12
12
|
|
13
13
|
attr_accessor :title
|
14
|
+
attr_accessor :x_legend
|
15
|
+
attr_accessor :y_legend
|
14
16
|
attr_accessor :theme
|
15
17
|
attr_accessor :default_type
|
16
18
|
attr_accessor :point_markers
|
19
|
+
attr_accessor :point_markers_rotation
|
20
|
+
attr_accessor :point_markers_ticks
|
17
21
|
attr_accessor :value_formatter
|
18
22
|
attr_accessor :key_formatter
|
19
23
|
attr_accessor :rasterizer
|
@@ -67,7 +67,9 @@ module Scruffy::Helpers
|
|
67
67
|
# If padding is set to :padded, a 15% padding is added to the highest value.
|
68
68
|
def top_value(padding=nil) # :nodoc:
|
69
69
|
topval = layers.inject(0) { |max, layer| (max = ((max < layer.top_value) ? layer.top_value : max)) unless layer.top_value.nil?; max }
|
70
|
-
|
70
|
+
below_zero = (topval <= 0)
|
71
|
+
topval = padding == :padded ? (topval + ((topval - bottom_value) * 0.15)) : topval
|
72
|
+
(below_zero && topval > 0) ? 0 : topval
|
71
73
|
end
|
72
74
|
|
73
75
|
# Returns the lowest value in any of this container's layers.
|
@@ -80,7 +82,7 @@ module Scruffy::Helpers
|
|
80
82
|
(min = ((min > layer.bottom_value) ? layer.bottom_value : min)) unless layer.bottom_value.nil?
|
81
83
|
min
|
82
84
|
end
|
83
|
-
above_zero = (botval
|
85
|
+
above_zero = (botval >= 0)
|
84
86
|
botval = (botval - ((top_value - botval) * 0.15)) if padding == :padded
|
85
87
|
|
86
88
|
# Don't introduce negative values solely due to padding.
|
@@ -11,7 +11,9 @@ module Scruffy::Helpers
|
|
11
11
|
end
|
12
12
|
|
13
13
|
all_values.size.times do |idx|
|
14
|
-
|
14
|
+
dx = width/(markers - 1)
|
15
|
+
|
16
|
+
location = idx.to_f * dx #+ dx/2
|
15
17
|
marker_value = all_values[idx]
|
16
18
|
marker_value = options[format_key].route_format(marker_value, idx, options.merge({:all_values => all_values})) if options[format_key]
|
17
19
|
|
data/lib/scruffy/layers/bar.rb
CHANGED
@@ -8,10 +8,22 @@ module Scruffy::Layers
|
|
8
8
|
class Bar < Base
|
9
9
|
|
10
10
|
# Draw bar graph.
|
11
|
+
# Now handles positive and negative values gracefully.
|
11
12
|
def draw(svg, coords, options = {})
|
12
|
-
coords.
|
13
|
-
x, y, bar_height = (coord.first
|
14
|
-
|
13
|
+
coords.each_with_index do |coord,idx|
|
14
|
+
x, y, bar_height = (coord.first), coord.last, 1#(height - coord.last)
|
15
|
+
|
16
|
+
valh = max_value + min_value * -1 #value_height
|
17
|
+
maxh = max_value * height / valh #positive area height
|
18
|
+
minh = min_value * height / valh #negative area height
|
19
|
+
#puts "height = #{height} and max_value = #{max_value} and min_value = #{min_value} and y = #{y} and point = #{points[idx]}"
|
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]}"
|
15
27
|
svg.g(:transform => "translate(-#{relative(0.5)}, -#{relative(0.5)})") {
|
16
28
|
svg.rect( :x => x, :y => y, :width => @bar_width + relative(1), :height => bar_height + relative(1),
|
17
29
|
:style => "fill: black; fill-opacity: 0.15; stroke: none;" )
|
@@ -34,13 +46,17 @@ module Scruffy::Layers
|
|
34
46
|
# Unfortunately this just mean that bar-graphs and most other graphs
|
35
47
|
# end up on different points. Maybe adding a padding to the coordinates
|
36
48
|
# should be a graph-wide thing?
|
49
|
+
#
|
50
|
+
# Update : x-axis coords for lines and area charts should now line
|
51
|
+
# up with the center of bar charts.
|
52
|
+
|
37
53
|
def generate_coordinates(options = {})
|
38
54
|
@bar_width = (width / points.size) * 0.9
|
39
55
|
options[:point_distance] = (width - (width / points.size)) / (points.size - 1).to_f
|
40
56
|
|
41
57
|
#TODO more array work with index, try to rework to be accepting of hashes
|
42
58
|
coords = (0...points.size).map do |idx|
|
43
|
-
x_coord = (options[:point_distance] * idx) + (width / points.size * 0.5)
|
59
|
+
x_coord = (options[:point_distance] * idx) + (width / points.size * 0.5) - (@bar_width * 0.5)
|
44
60
|
|
45
61
|
relative_percent = ((points[idx] == min_value) ? 0 : ((points[idx] - min_value) / (max_value - min_value).to_f))
|
46
62
|
y_coord = (height - (height * relative_percent))
|
data/lib/scruffy/layers/base.rb
CHANGED
@@ -26,6 +26,7 @@ module Scruffy::Layers
|
|
26
26
|
attr_accessor :points
|
27
27
|
attr_accessor :relevant_data
|
28
28
|
attr_accessor :preferred_color
|
29
|
+
attr_accessor :preferred_outline
|
29
30
|
attr_accessor :options # On-the-fly values for easy customization / acts as attributes.
|
30
31
|
|
31
32
|
# The following attributes are set during the layer's render process,
|
@@ -34,6 +35,7 @@ module Scruffy::Layers
|
|
34
35
|
attr_reader :height, :width
|
35
36
|
attr_reader :min_value, :max_value
|
36
37
|
attr_reader :color
|
38
|
+
attr_reader :outline
|
37
39
|
attr_reader :opacity
|
38
40
|
attr_reader :complexity
|
39
41
|
|
@@ -47,6 +49,7 @@ module Scruffy::Layers
|
|
47
49
|
# title:: Name/title of data group
|
48
50
|
# points:: Array of data points
|
49
51
|
# preferred_color:: Color used to render this graph, overrides theme color.
|
52
|
+
# preferred_outline:: Color used to render this graph outline, overrides theme outline.
|
50
53
|
# relevant_data:: Rarely used - indicates the data on this graph should not
|
51
54
|
# included in any graph data aggregations, such as averaging data points.
|
52
55
|
# style:: SVG polyline style. (default: 'fill-opacity: 0; stroke-opacity: 0.35')
|
@@ -57,6 +60,7 @@ module Scruffy::Layers
|
|
57
60
|
def initialize(options = {})
|
58
61
|
@title = options.delete(:title) || ''
|
59
62
|
@preferred_color = options.delete(:color)
|
63
|
+
@preferred_outline = options.delete(:outline)
|
60
64
|
@relevant_data = options.delete(:relevant_data) || true
|
61
65
|
@points = options.delete(:points) || []
|
62
66
|
@points.extend Scruffy::Helpers::PointContainer unless @points.kind_of? Scruffy::Helpers::PointContainer
|
@@ -145,6 +149,7 @@ module Scruffy::Layers
|
|
145
149
|
# itself.
|
146
150
|
def setup_variables(options = {})
|
147
151
|
@color = (preferred_color || options.delete(:color))
|
152
|
+
@outline = (preferred_outline || options.delete(:outline))
|
148
153
|
@width, @height = options.delete(:size)
|
149
154
|
@min_value, @max_value = options[:min_value], options[:max_value]
|
150
155
|
@opacity = options[:opacity] || 1.0
|
@@ -154,15 +159,19 @@ module Scruffy::Layers
|
|
154
159
|
# Optimistic generation of coordinates for layer to use. These coordinates are
|
155
160
|
# just a best guess, and can be overridden or thrown away (for example, this is overridden
|
156
161
|
# in pie charting and bar charts).
|
162
|
+
|
163
|
+
# Updated : Assuming n number of points, the graph is divided into n rectangles
|
164
|
+
# and the points are plotted in the middle of each rectangle. This allows bars to
|
165
|
+
# play nice with lines.
|
157
166
|
def generate_coordinates(options = {})
|
158
167
|
|
159
|
-
dy = height.to_f / (options[:max_value] - options[:min_value])
|
160
|
-
dx = width.to_f / (options[:max_key] - options[:min_key])
|
168
|
+
dy = height.to_f / (options[:max_value] - options[:min_value])
|
169
|
+
dx = width.to_f / (options[:max_key] - options[:min_key] + 1)
|
161
170
|
|
162
171
|
ret = []
|
163
172
|
points.each_point do |x, y|
|
164
173
|
if y
|
165
|
-
x_coord = dx * (x - options[:min_key])
|
174
|
+
x_coord = dx * (x - options[:min_key]) + dx/2
|
166
175
|
y_coord = dy * (y - options[:min_value])
|
167
176
|
|
168
177
|
ret << [x_coord, height - y_coord]
|
data/lib/scruffy/layers.rb
CHANGED
@@ -17,9 +17,12 @@ require 'scruffy/layers/base'
|
|
17
17
|
require 'scruffy/layers/area'
|
18
18
|
require 'scruffy/layers/all_smiles'
|
19
19
|
require 'scruffy/layers/bar'
|
20
|
+
require 'scruffy/layers/box'
|
20
21
|
require 'scruffy/layers/line'
|
21
22
|
require 'scruffy/layers/average'
|
22
23
|
require 'scruffy/layers/stacked'
|
24
|
+
require 'scruffy/layers/multi'
|
25
|
+
require 'scruffy/layers/multi_bar'
|
23
26
|
require 'scruffy/layers/pie'
|
24
27
|
require 'scruffy/layers/pie_slice'
|
25
28
|
require 'scruffy/layers/scatter'
|
@@ -4,12 +4,12 @@ module Scruffy::Renderers
|
|
4
4
|
def define_layout
|
5
5
|
super do |components|
|
6
6
|
components << Scruffy::Components::Title.new(:title, :position => [5, 2], :size => [90, 7])
|
7
|
-
components << Scruffy::Components::Viewport.new(:view, :position => [2, 26], :size => [
|
8
|
-
graph << Scruffy::Components::ValueMarkers.new(:values, :position => [0, 2], :size => [
|
9
|
-
graph << Scruffy::Components::Grid.new(:grid, :position => [
|
10
|
-
graph << Scruffy::Components::VGrid.new(:vgrid, :position => [
|
11
|
-
graph << Scruffy::Components::DataMarkers.new(:labels, :position => [
|
12
|
-
graph << Scruffy::Components::Graphs.new(:graphs, :position => [
|
7
|
+
components << Scruffy::Components::Viewport.new(:view, :position => [2, 26], :size => [90, 66]) do |graph|
|
8
|
+
graph << Scruffy::Components::ValueMarkers.new(:values, :position => [0, 2], :size => [8, 89])
|
9
|
+
graph << Scruffy::Components::Grid.new(:grid, :position => [10, 0], :size => [90, 89], :stroke_width => 1)
|
10
|
+
graph << Scruffy::Components::VGrid.new(:vgrid, :position => [10, 0], :size => [90, 89], :stroke_width => 1)
|
11
|
+
graph << Scruffy::Components::DataMarkers.new(:labels, :position => [10, 92], :size => [90, 8])
|
12
|
+
graph << Scruffy::Components::Graphs.new(:graphs, :position => [10, 0], :size => [90, 89])
|
13
13
|
end
|
14
14
|
components << Scruffy::Components::Legend.new(:legend, :position => [5, 13], :size => [90, 6])
|
15
15
|
end
|
data/lib/scruffy/renderers.rb
CHANGED
@@ -15,6 +15,7 @@ module Scruffy::Renderers; end
|
|
15
15
|
require 'scruffy/renderers/base'
|
16
16
|
require 'scruffy/renderers/empty'
|
17
17
|
require 'scruffy/renderers/standard'
|
18
|
+
require 'scruffy/renderers/axis_legend'
|
18
19
|
require 'scruffy/renderers/reversed'
|
19
20
|
require 'scruffy/renderers/cubed'
|
20
21
|
require 'scruffy/renderers/split'
|
data/lib/scruffy/themes.rb
CHANGED
@@ -19,8 +19,12 @@ module Scruffy::Themes
|
|
19
19
|
class Base
|
20
20
|
attr_accessor :background # Background color or array of two colors
|
21
21
|
attr_accessor :colors # Array of colors for data graphs
|
22
|
+
attr_accessor :outlines # Array of colors for outlines of elements for data graphs
|
22
23
|
attr_accessor :marker # Marker color for grid lines, values, etc.
|
23
24
|
attr_accessor :font_family # Font family: Not really supported. Maybe in the future.
|
25
|
+
attr_accessor :marker_font_size # Marker Font Size:
|
26
|
+
attr_accessor :title_font_size # Title Font Size:
|
27
|
+
attr_accessor :legend_font_size # Legend Font Size:
|
24
28
|
|
25
29
|
# Returns a new Scruffy::Themes::Base object.
|
26
30
|
#
|
@@ -34,8 +38,12 @@ module Scruffy::Themes
|
|
34
38
|
def initialize(descriptor)
|
35
39
|
self.background = descriptor[:background]
|
36
40
|
self.colors = descriptor[:colors]
|
41
|
+
self.outlines = descriptor[:outlines]
|
37
42
|
self.marker = descriptor[:marker]
|
38
43
|
self.font_family = descriptor[:font_family]
|
44
|
+
self.marker_font_size = descriptor[:marker_font_size]
|
45
|
+
self.title_font_size = descriptor[:title_font_size]
|
46
|
+
self.legend_font_size = descriptor[:legend_font_size]
|
39
47
|
end
|
40
48
|
|
41
49
|
# Returns the next available color in the color array.
|
@@ -45,6 +53,17 @@ module Scruffy::Themes
|
|
45
53
|
|
46
54
|
self.colors[(@previous_color-1) % self.colors.size]
|
47
55
|
end
|
56
|
+
|
57
|
+
|
58
|
+
# Returns the next available outline in the outline array.
|
59
|
+
def next_outline
|
60
|
+
@previous_outline = 0 if @previous_outline.nil?
|
61
|
+
@previous_outline += 1
|
62
|
+
if self.outlines.nil?
|
63
|
+
return "#000000"
|
64
|
+
end
|
65
|
+
self.outlines[(@previous_outline-1) % self.outlines.size]
|
66
|
+
end
|
48
67
|
|
49
68
|
# todo: Implement darken function.
|
50
69
|
def darken(color, shift=20); end
|
data/test/graph_creation_test.rb
CHANGED
@@ -49,6 +49,43 @@ class GraphCreationTest < Test::Unit::TestCase
|
|
49
49
|
graph.render :to => "#{WEBSITE_DIR}/line_test.svg"
|
50
50
|
graph.render :width => 400, :to => "#{WEBSITE_DIR}/line_test.png", :as => 'png' if $make_png
|
51
51
|
end
|
52
|
+
|
53
|
+
|
54
|
+
def test_create_line_with_negatives
|
55
|
+
graph = Scruffy::Graph.new
|
56
|
+
graph.title = "Sample Line Graph"
|
57
|
+
graph.renderer = Scruffy::Renderers::Standard.new
|
58
|
+
|
59
|
+
graph.add :line, 'Example', [-20, 100, -70, -30, 106]
|
60
|
+
theme = Scruffy::Themes::Base.new :background=>"#ffffff", :marker=>"#444444",
|
61
|
+
:colors=>["#4f83bf","#be514e","#a1ba5e","#82649a"],
|
62
|
+
:title_font_size => 30, :marker_font_size=>10
|
63
|
+
graph.render :to => "#{WEBSITE_DIR}/line_test_with_negatives.svg",:theme=>theme
|
64
|
+
graph.render :width => 400, :to => "#{WEBSITE_DIR}/line_test_with_negatives.png",:theme=>theme, :as => 'png' if $make_png
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
def test_create_negative_line
|
69
|
+
graph = Scruffy::Graph.new
|
70
|
+
graph.title = "Sample Line Graph"
|
71
|
+
graph.renderer = Scruffy::Renderers::Standard.new
|
72
|
+
|
73
|
+
graph.add :line, 'Example', [-20, -100, -70, -30, -106]
|
74
|
+
theme = Scruffy::Themes::Apples.new
|
75
|
+
graph.render :to => "#{WEBSITE_DIR}/negative_line_test.svg",:theme=>theme
|
76
|
+
graph.render :width => 600,:theme=>theme, :to => "#{WEBSITE_DIR}/negative_line_test.png", :as => 'png' if $make_png
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_create_small_value_line
|
80
|
+
graph = Scruffy::Graph.new
|
81
|
+
graph.title = "Sample Line Graph"
|
82
|
+
graph.renderer = Scruffy::Renderers::Standard.new
|
83
|
+
graph.value_formatter = Scruffy::Formatters::Number.new(:precision => 1)
|
84
|
+
graph.add :line, 'Example', [0.2,0.5,0.1,0.9,0.8,1.2,0.05,1]
|
85
|
+
|
86
|
+
graph.render :to => "#{WEBSITE_DIR}/small_value_line_test.svg"
|
87
|
+
graph.render :width => 400, :to => "#{WEBSITE_DIR}/small_value_line_test.png", :as => 'png' if $make_png
|
88
|
+
end
|
52
89
|
|
53
90
|
|
54
91
|
def test_create_bar
|
@@ -60,6 +97,34 @@ class GraphCreationTest < Test::Unit::TestCase
|
|
60
97
|
graph.render :width => 400, :to => "#{WEBSITE_DIR}/bar_test.png", :as => 'png' if $make_png
|
61
98
|
end
|
62
99
|
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
def test_create_bar_with_negatives
|
104
|
+
graph = Scruffy::Graph.new
|
105
|
+
graph.title = "Sample Bar Graph"
|
106
|
+
graph.renderer = Scruffy::Renderers::Standard.new
|
107
|
+
graph.add :bar, 'Example', [20, 100,-10, 70, 30, -40, 106]
|
108
|
+
graph.render :to => "#{WEBSITE_DIR}/negative_bar_test.svg"
|
109
|
+
graph.render :width => 400, :to => "#{WEBSITE_DIR}/negative_bar_test.png", :as => 'png' if $make_png
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
def test_create_bar_with_all_negatives
|
114
|
+
graph = Scruffy::Graph.new
|
115
|
+
graph.title = "Sample Bar Graph"
|
116
|
+
graph.renderer = Scruffy::Renderers::Standard.new
|
117
|
+
graph.add :bar, 'Example', [-20, -100,-10, -70, -30, -40, -106]
|
118
|
+
|
119
|
+
theme = Scruffy::Themes::Base.new :background=>"#ffffff", :marker=>"#444444",
|
120
|
+
:colors=>["#ff0000","#00ff00","#0000ff","#cccccc"],
|
121
|
+
:title_font_size => 30, :marker_font_size=>10
|
122
|
+
|
123
|
+
graph.render :to => "#{WEBSITE_DIR}/all_negative_bar_test.svg",:theme=>theme
|
124
|
+
graph.render :width => 400,:theme=>theme, :to => "#{WEBSITE_DIR}/all_negative_bar_test.png", :as => 'png' if $make_png
|
125
|
+
end
|
126
|
+
|
127
|
+
|
63
128
|
def test_split
|
64
129
|
graph = Scruffy::Graph.new
|
65
130
|
graph.title = "Long-term Comparisons"
|
@@ -91,6 +156,93 @@ class GraphCreationTest < Test::Unit::TestCase
|
|
91
156
|
graph.render :width => 500, :to => "#{WEBSITE_DIR}/stacking_test.png", :as => 'png' if $make_png
|
92
157
|
end
|
93
158
|
|
159
|
+
|
160
|
+
def test_reg_multi_bar
|
161
|
+
graph = Scruffy::Graph.new
|
162
|
+
graph.title = "Comparative Agent Performance"
|
163
|
+
graph.value_formatter = Scruffy::Formatters::Percentage.new(:precision => 0)
|
164
|
+
#graph.add :multi do |multi|
|
165
|
+
graph.add :bar, 'Jack', [30, 60, 49, 29, 100, 120]
|
166
|
+
graph.add :bar, 'Jill', [120, 240, 0, 100, 140, 20]
|
167
|
+
graph.add :bar, 'Hill', [10, 10, 90, 20, 40, 10]
|
168
|
+
#end
|
169
|
+
graph.point_markers = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']
|
170
|
+
graph.render :to => "#{WEBSITE_DIR}/reg_multi_bar_test.svg"
|
171
|
+
graph.render :width => 500, :to => "#{WEBSITE_DIR}/reg_multi_bar_test.png", :as => 'png' if $make_png
|
172
|
+
end
|
173
|
+
|
174
|
+
def test_multi_bar
|
175
|
+
graph = Scruffy::Graph.new
|
176
|
+
graph.title = "Comparative Agent Performance"
|
177
|
+
graph.value_formatter = Scruffy::Formatters::Percentage.new(:precision => 0)
|
178
|
+
graph.add :multi do |multi|
|
179
|
+
multi.add :multi_bar, 'Jack', [30, 60, 49, 29, 100, 120]
|
180
|
+
multi.add :multi_bar, 'Jill', [120, 240, 0, 100, 140, 20]
|
181
|
+
multi.add :multi_bar, 'Hill', [10, 10, 90, 20, 40, 10]
|
182
|
+
multi.add :multi_bar, 'Bob', [-10, -20, -30, -40, -50, -60]
|
183
|
+
end
|
184
|
+
graph.point_markers = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']
|
185
|
+
graph.point_markers_ticks = true
|
186
|
+
theme = Scruffy::Themes::Base.new :background=>"#ffffff", :marker=>"#444444",
|
187
|
+
:colors=>["#cccccc","#ff0000","#00ff00","#0000ff"],
|
188
|
+
:title_font_size => 30, :marker_font_size=>10
|
189
|
+
|
190
|
+
graph.render :to => "#{WEBSITE_DIR}/multi_bar_test.svg",:theme=>theme
|
191
|
+
|
192
|
+
graph.render :width => 900,:theme=>theme, :to => "#{WEBSITE_DIR}/multi_bar_test.png", :as => 'png' if $make_png
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
def test_box_plot
|
197
|
+
graph = Scruffy::Graph.new()
|
198
|
+
graph.title = "Box Plot Test"
|
199
|
+
graph.x_legend = "Time in Seconds"
|
200
|
+
graph.y_legend = "Inces of Rain"
|
201
|
+
graph.value_formatter = Scruffy::Formatters::Percentage.new(:precision => 0)
|
202
|
+
graph.add :box, "Test Data", [
|
203
|
+
[10,8,6.2,4,2],
|
204
|
+
[12,9,8.2,4.2,3.5],
|
205
|
+
[10,8,5.3,4,2],
|
206
|
+
[12,9,8.2,4.2,3.5],
|
207
|
+
[10,8,6.6,4,2],
|
208
|
+
[12,9,8.2,4.2,3.5]
|
209
|
+
]
|
210
|
+
|
211
|
+
graph.point_markers = ['Jan', 'Feb','Jan', 'Feb','Jan', 'Feb']
|
212
|
+
graph.point_markers_ticks = true
|
213
|
+
graph.renderer = Scruffy::Renderers::AxisLegend.new
|
214
|
+
|
215
|
+
theme = Scruffy::Themes::Base.new :background=>"#ffffff", :marker=>"#aaaaaa",
|
216
|
+
:colors=>["#4f83bf","#be514e","#a1ba5e","#82649a"],
|
217
|
+
:legend_font_size=>30,
|
218
|
+
:title_font_size=>40,
|
219
|
+
:marker_font_size=>20,
|
220
|
+
:outlines=>["#be514e","#a1ba5e","#82649a","#4f83bf"]
|
221
|
+
graph.render :to => "#{WEBSITE_DIR}/box_plot_test.svg",:padding=>:padded,:theme=>theme,:key_markers=>8
|
222
|
+
graph.render :size => [600,540],:theme=>theme,:key_markers=>7, :to => "#{WEBSITE_DIR}/box_plot_test.png", :as => 'png',:padding=>:padded if $make_png
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
|
227
|
+
def test_rotated_point_markers
|
228
|
+
graph = Scruffy::Graph.new({:point_markers_rotation=>30}) #
|
229
|
+
graph.title = "Comparative Agent Performance"
|
230
|
+
graph.value_formatter = Scruffy::Formatters::Percentage.new(:precision => 0)
|
231
|
+
graph.add :stacked do |stacked|
|
232
|
+
stacked.add :bar, 'Jack', [30, 60, 49, 29, 100, 120]
|
233
|
+
stacked.add :bar, 'Jill', [120, 240, 0, 100, 140, 20]
|
234
|
+
stacked.add :bar, 'Hill', [10, 10, 90, 20, 40, 10]
|
235
|
+
end
|
236
|
+
graph.point_markers = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']
|
237
|
+
graph.point_markers_ticks = true
|
238
|
+
#Rotation was set when the graph was created
|
239
|
+
#You can also do something like this
|
240
|
+
#graph.point_markers_rotation = 90
|
241
|
+
graph.render :to => "#{WEBSITE_DIR}/rotated_point_markers_test.svg"
|
242
|
+
graph.render :width => 500, :to => "#{WEBSITE_DIR}/rotated_point_markers_test.png", :as => 'png' if $make_png
|
243
|
+
end
|
244
|
+
|
245
|
+
|
94
246
|
def test_multi_layered
|
95
247
|
graph = Scruffy::Graph.new
|
96
248
|
graph.title = "Some Kind of Information"
|