jagthedrummer-scruffy 0.2.9 → 0.2.10
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/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"
|