pfsc-gruff 0.3.6
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/History.txt +117 -0
- data/MIT-LICENSE +21 -0
- data/Manifest.txt +81 -0
- data/README.txt +40 -0
- data/Rakefile +55 -0
- data/assets/bubble.png +0 -0
- data/assets/city_scene/background/0000.png +0 -0
- data/assets/city_scene/background/0600.png +0 -0
- data/assets/city_scene/background/2000.png +0 -0
- data/assets/city_scene/clouds/cloudy.png +0 -0
- data/assets/city_scene/clouds/partly_cloudy.png +0 -0
- data/assets/city_scene/clouds/stormy.png +0 -0
- data/assets/city_scene/grass/default.png +0 -0
- data/assets/city_scene/haze/true.png +0 -0
- data/assets/city_scene/number_sample/1.png +0 -0
- data/assets/city_scene/number_sample/2.png +0 -0
- data/assets/city_scene/number_sample/default.png +0 -0
- data/assets/city_scene/sky/0000.png +0 -0
- data/assets/city_scene/sky/0200.png +0 -0
- data/assets/city_scene/sky/0400.png +0 -0
- data/assets/city_scene/sky/0600.png +0 -0
- data/assets/city_scene/sky/0800.png +0 -0
- data/assets/city_scene/sky/1000.png +0 -0
- data/assets/city_scene/sky/1200.png +0 -0
- data/assets/city_scene/sky/1400.png +0 -0
- data/assets/city_scene/sky/1500.png +0 -0
- data/assets/city_scene/sky/1700.png +0 -0
- data/assets/city_scene/sky/2000.png +0 -0
- data/assets/pc306715.jpg +0 -0
- data/assets/plastik/blue.png +0 -0
- data/assets/plastik/green.png +0 -0
- data/assets/plastik/red.png +0 -0
- data/init.rb +2 -0
- data/lib/gruff.rb +28 -0
- data/lib/gruff/accumulator_bar.rb +27 -0
- data/lib/gruff/area.rb +58 -0
- data/lib/gruff/bar.rb +87 -0
- data/lib/gruff/bar_conversion.rb +46 -0
- data/lib/gruff/base.rb +1123 -0
- data/lib/gruff/bullet.rb +109 -0
- data/lib/gruff/deprecated.rb +39 -0
- data/lib/gruff/dot.rb +113 -0
- data/lib/gruff/line.rb +135 -0
- data/lib/gruff/mini/bar.rb +37 -0
- data/lib/gruff/mini/legend.rb +109 -0
- data/lib/gruff/mini/pie.rb +36 -0
- data/lib/gruff/mini/side_bar.rb +35 -0
- data/lib/gruff/net.rb +140 -0
- data/lib/gruff/photo_bar.rb +100 -0
- data/lib/gruff/pie.rb +126 -0
- data/lib/gruff/scene.rb +209 -0
- data/lib/gruff/side_bar.rb +118 -0
- data/lib/gruff/side_stacked_bar.rb +77 -0
- data/lib/gruff/spider.rb +130 -0
- data/lib/gruff/stacked_area.rb +67 -0
- data/lib/gruff/stacked_bar.rb +57 -0
- data/lib/gruff/stacked_mixin.rb +23 -0
- data/rails_generators/gruff/gruff_generator.rb +63 -0
- data/rails_generators/gruff/templates/controller.rb +32 -0
- data/rails_generators/gruff/templates/functional_test.rb +24 -0
- data/test/gruff_test_case.rb +123 -0
- data/test/test_accumulator_bar.rb +50 -0
- data/test/test_area.rb +134 -0
- data/test/test_bar.rb +321 -0
- data/test/test_base.rb +8 -0
- data/test/test_bullet.rb +26 -0
- data/test/test_dot.rb +273 -0
- data/test/test_legend.rb +68 -0
- data/test/test_line.rb +556 -0
- data/test/test_mini_bar.rb +33 -0
- data/test/test_mini_pie.rb +26 -0
- data/test/test_mini_side_bar.rb +37 -0
- data/test/test_net.rb +230 -0
- data/test/test_photo.rb +41 -0
- data/test/test_pie.rb +154 -0
- data/test/test_scene.rb +100 -0
- data/test/test_side_bar.rb +29 -0
- data/test/test_sidestacked_bar.rb +89 -0
- data/test/test_spider.rb +216 -0
- data/test/test_stacked_area.rb +52 -0
- data/test/test_stacked_bar.rb +52 -0
- metadata +186 -0
data/lib/gruff/bullet.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/base'
|
2
|
+
|
3
|
+
class Gruff::Bullet < Gruff::Base
|
4
|
+
|
5
|
+
def initialize(target_width="400x40")
|
6
|
+
if not Numeric === target_width
|
7
|
+
geometric_width, geometric_height = target_width.split('x')
|
8
|
+
@columns = geometric_width.to_f
|
9
|
+
@rows = geometric_height.to_f
|
10
|
+
else
|
11
|
+
@columns = target_width.to_f
|
12
|
+
@rows = target_width.to_f / 5.0
|
13
|
+
end
|
14
|
+
|
15
|
+
initialize_ivars
|
16
|
+
|
17
|
+
reset_themes
|
18
|
+
theme_greyscale
|
19
|
+
@title_font_size = 20
|
20
|
+
end
|
21
|
+
|
22
|
+
def data(value, maximum_value, options={})
|
23
|
+
@value = value.to_f
|
24
|
+
@maximum_value = maximum_value.to_f
|
25
|
+
@options = options
|
26
|
+
@options.map { |k, v| @options[k] = v.to_f if v === Numeric }
|
27
|
+
end
|
28
|
+
|
29
|
+
# def setup_drawing
|
30
|
+
# # Maybe should be done in one of the following functions for more granularity.
|
31
|
+
# unless @has_data
|
32
|
+
# draw_no_data()
|
33
|
+
# return
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# normalize()
|
37
|
+
# setup_graph_measurements()
|
38
|
+
# sort_norm_data() if @sort # Sort norm_data with avg largest values set first (for display)
|
39
|
+
#
|
40
|
+
# draw_legend()
|
41
|
+
# draw_line_markers()
|
42
|
+
# draw_axis_labels()
|
43
|
+
# draw_title
|
44
|
+
# end
|
45
|
+
|
46
|
+
def draw
|
47
|
+
# TODO Left label
|
48
|
+
# TODO Bottom labels and markers
|
49
|
+
# @graph_bottom
|
50
|
+
# Calculations are off 800x???
|
51
|
+
|
52
|
+
@colors.reverse!
|
53
|
+
|
54
|
+
draw_title
|
55
|
+
|
56
|
+
@margin = 30.0
|
57
|
+
@thickness = @raw_rows / 6.0
|
58
|
+
@right_margin = @margin
|
59
|
+
@graph_left = @title_width * 1.3 rescue @margin # HACK Need to calculate real width
|
60
|
+
@graph_width = @raw_columns - @graph_left - @right_margin
|
61
|
+
@graph_height = @thickness * 3.0
|
62
|
+
|
63
|
+
# Background
|
64
|
+
@d = @d.fill @colors[0]
|
65
|
+
@d = @d.rectangle(@graph_left, 0, @graph_left + @graph_width, @graph_height)
|
66
|
+
|
67
|
+
[:high, :low].each_with_index do |indicator, index|
|
68
|
+
next unless @options.has_key?(indicator)
|
69
|
+
@d = @d.fill @colors[index + 1]
|
70
|
+
indicator_width_x = @graph_left + @graph_width * (@options[indicator] / @maximum_value)
|
71
|
+
@d = @d.rectangle(@graph_left, 0, indicator_width_x, @graph_height)
|
72
|
+
end
|
73
|
+
|
74
|
+
if @options.has_key?(:target)
|
75
|
+
@d = @d.fill @font_color
|
76
|
+
target_x = @graph_left + @graph_width * (@options[:target] / @maximum_value)
|
77
|
+
half_thickness = @thickness / 2.0
|
78
|
+
@d = @d.rectangle(target_x, half_thickness, target_x + half_thickness, @thickness * 2 + half_thickness)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Value
|
82
|
+
@d = @d.fill @font_color
|
83
|
+
@d = @d.rectangle(@graph_left, @thickness, @graph_left + @graph_width * (@value / @maximum_value), @thickness * 2)
|
84
|
+
|
85
|
+
@d.draw(@base_image)
|
86
|
+
end
|
87
|
+
|
88
|
+
def draw_title
|
89
|
+
return unless @title
|
90
|
+
|
91
|
+
@font_height = calculate_caps_height(scale_fontsize(@title_font_size))
|
92
|
+
@title_width = calculate_width(@title_font_size, @title)
|
93
|
+
|
94
|
+
@d.fill = @font_color
|
95
|
+
@d.font = @font if @font
|
96
|
+
@d.stroke('transparent')
|
97
|
+
@d.font_weight = NormalWeight
|
98
|
+
@d.pointsize = scale_fontsize(@title_font_size)
|
99
|
+
@d.gravity = NorthWestGravity
|
100
|
+
@d = @d.annotate_scaled(*[
|
101
|
+
@base_image,
|
102
|
+
1.0, 1.0,
|
103
|
+
@font_height/2, @font_height/2,
|
104
|
+
@title,
|
105
|
+
@scale
|
106
|
+
])
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
|
2
|
+
##
|
3
|
+
# A mixin for methods that need to be deleted or have been
|
4
|
+
# replaced by cleaner code.
|
5
|
+
|
6
|
+
module Gruff
|
7
|
+
module Deprecated
|
8
|
+
|
9
|
+
def scale_measurements
|
10
|
+
setup_graph_measurements
|
11
|
+
end
|
12
|
+
|
13
|
+
def total_height
|
14
|
+
@rows + 10
|
15
|
+
end
|
16
|
+
|
17
|
+
def graph_top
|
18
|
+
@graph_top * @scale
|
19
|
+
end
|
20
|
+
|
21
|
+
def graph_height
|
22
|
+
@graph_height * @scale
|
23
|
+
end
|
24
|
+
|
25
|
+
def graph_left
|
26
|
+
@graph_left * @scale
|
27
|
+
end
|
28
|
+
|
29
|
+
def graph_width
|
30
|
+
@graph_width * @scale
|
31
|
+
end
|
32
|
+
|
33
|
+
# TODO Should be calculate_graph_height
|
34
|
+
# def setup_graph_height
|
35
|
+
# @graph_height = @graph_bottom - @graph_top
|
36
|
+
# end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
data/lib/gruff/dot.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/base'
|
2
|
+
|
3
|
+
##
|
4
|
+
# Graph with dots and labels along a vertical access
|
5
|
+
# see: 'Creating More Effective Graphs' by Robbins
|
6
|
+
|
7
|
+
class Gruff::Dot < Gruff::Base
|
8
|
+
|
9
|
+
def draw
|
10
|
+
@has_left_labels = true
|
11
|
+
super
|
12
|
+
|
13
|
+
return unless @has_data
|
14
|
+
|
15
|
+
# Setup spacing.
|
16
|
+
#
|
17
|
+
spacing_factor = 1.0
|
18
|
+
|
19
|
+
@items_width = @graph_height / @column_count.to_f
|
20
|
+
@item_width = @items_width * spacing_factor / @norm_data.size
|
21
|
+
@d = @d.stroke_opacity 0.0
|
22
|
+
height = Array.new(@column_count, 0)
|
23
|
+
length = Array.new(@column_count, @graph_left)
|
24
|
+
padding = (@items_width * (1 - spacing_factor)) / 2
|
25
|
+
|
26
|
+
@norm_data.each_with_index do |data_row, row_index|
|
27
|
+
data_row[DATA_VALUES_INDEX].each_with_index do |data_point, point_index|
|
28
|
+
|
29
|
+
x_pos = @graph_left + (data_point * @graph_width) - (@item_width.to_f/6.0).round
|
30
|
+
y_pos = @graph_top + (@items_width * point_index) + padding + (@item_width.to_f/2.0).round
|
31
|
+
|
32
|
+
if row_index == 0
|
33
|
+
@d = @d.stroke(@marker_color)
|
34
|
+
@d = @d.stroke_width 1.0
|
35
|
+
@d = @d.opacity 0.1
|
36
|
+
@d = @d.line(@graph_left, y_pos, @graph_left + @graph_width, y_pos)
|
37
|
+
end
|
38
|
+
|
39
|
+
@d = @d.fill data_row[DATA_COLOR_INDEX]
|
40
|
+
@d = @d.stroke('transparent')
|
41
|
+
@d = @d.circle(x_pos, y_pos, x_pos + (@item_width.to_f/3.0).round, y_pos)
|
42
|
+
|
43
|
+
# Calculate center based on item_width and current row
|
44
|
+
label_center = @graph_top + (@items_width * point_index + @items_width / 2) + padding
|
45
|
+
draw_label(label_center, point_index)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
@d.draw(@base_image)
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
# Instead of base class version, draws vertical background lines and label
|
56
|
+
def draw_line_markers
|
57
|
+
|
58
|
+
return if @hide_line_markers
|
59
|
+
|
60
|
+
@d = @d.stroke_antialias false
|
61
|
+
|
62
|
+
# Draw horizontal line markers and annotate with numbers
|
63
|
+
@d = @d.stroke(@marker_color)
|
64
|
+
@d = @d.stroke_width 1
|
65
|
+
number_of_lines = 5
|
66
|
+
|
67
|
+
# TODO Round maximum marker value to a round number like 100, 0.1, 0.5, etc.
|
68
|
+
increment = significant(@maximum_value.to_f / number_of_lines)
|
69
|
+
(0..number_of_lines).each do |index|
|
70
|
+
|
71
|
+
line_diff = (@graph_right - @graph_left) / number_of_lines
|
72
|
+
x = @graph_right - (line_diff * index) - 1
|
73
|
+
@d = @d.line(x, @graph_bottom, x, @graph_bottom + 0.5 * LABEL_MARGIN)
|
74
|
+
diff = index - number_of_lines
|
75
|
+
marker_label = diff.abs * increment
|
76
|
+
|
77
|
+
unless @hide_line_numbers
|
78
|
+
@d.fill = @font_color
|
79
|
+
@d.font = @font if @font
|
80
|
+
@d.stroke = 'transparent'
|
81
|
+
@d.pointsize = scale_fontsize(@marker_font_size)
|
82
|
+
@d.gravity = CenterGravity
|
83
|
+
# TODO Center text over line
|
84
|
+
@d = @d.annotate_scaled( @base_image,
|
85
|
+
0, 0, # Width of box to draw text in
|
86
|
+
x, @graph_bottom + (LABEL_MARGIN * 2.0), # Coordinates of text
|
87
|
+
marker_label.to_s, @scale)
|
88
|
+
end # unless
|
89
|
+
@d = @d.stroke_antialias true
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
##
|
94
|
+
# Draw on the Y axis instead of the X
|
95
|
+
|
96
|
+
def draw_label(y_offset, index)
|
97
|
+
if !@labels[index].nil? && @labels_seen[index].nil?
|
98
|
+
@d.fill = @font_color
|
99
|
+
@d.font = @font if @font
|
100
|
+
@d.stroke = 'transparent'
|
101
|
+
@d.font_weight = NormalWeight
|
102
|
+
@d.pointsize = scale_fontsize(@marker_font_size)
|
103
|
+
@d.gravity = EastGravity
|
104
|
+
@d = @d.annotate_scaled(@base_image,
|
105
|
+
1, 1,
|
106
|
+
-@graph_left + LABEL_MARGIN * 2.0, y_offset,
|
107
|
+
@labels[index], @scale)
|
108
|
+
@labels_seen[index] = 1
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
data/lib/gruff/line.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
|
2
|
+
require File.dirname(__FILE__) + '/base'
|
3
|
+
|
4
|
+
##
|
5
|
+
# Here's how to make a Line graph:
|
6
|
+
#
|
7
|
+
# g = Gruff::Line.new
|
8
|
+
# g.title = "A Line Graph"
|
9
|
+
# g.data 'Fries', [20, 23, 19, 8]
|
10
|
+
# g.data 'Hamburgers', [50, 19, 99, 29]
|
11
|
+
# g.write("test/output/line.png")
|
12
|
+
#
|
13
|
+
# There are also other options described below, such as #baseline_value, #baseline_color, #hide_dots, and #hide_lines.
|
14
|
+
|
15
|
+
class Gruff::Line < Gruff::Base
|
16
|
+
|
17
|
+
# Draw a dashed line at the given value
|
18
|
+
attr_accessor :baseline_value
|
19
|
+
|
20
|
+
# Color of the baseline
|
21
|
+
attr_accessor :baseline_color
|
22
|
+
|
23
|
+
# Dimensions of lines and dots; calculated based on dataset size if left unspecified
|
24
|
+
attr_accessor :line_width
|
25
|
+
attr_accessor :dot_radius
|
26
|
+
|
27
|
+
# Hide parts of the graph to fit more datapoints, or for a different appearance.
|
28
|
+
attr_accessor :hide_dots, :hide_lines
|
29
|
+
|
30
|
+
# Call with target pixel width of graph (800, 400, 300), and/or 'false' to omit lines (points only).
|
31
|
+
#
|
32
|
+
# g = Gruff::Line.new(400) # 400px wide with lines
|
33
|
+
#
|
34
|
+
# g = Gruff::Line.new(400, false) # 400px wide, no lines (for backwards compatibility)
|
35
|
+
#
|
36
|
+
# g = Gruff::Line.new(false) # Defaults to 800px wide, no lines (for backwards compatibility)
|
37
|
+
#
|
38
|
+
# The preferred way is to call hide_dots or hide_lines instead.
|
39
|
+
def initialize(*args)
|
40
|
+
raise ArgumentError, "Wrong number of arguments" if args.length > 2
|
41
|
+
if args.empty? or ((not Numeric === args.first) && (not String === args.first)) then
|
42
|
+
super()
|
43
|
+
else
|
44
|
+
super args.shift
|
45
|
+
end
|
46
|
+
|
47
|
+
@hide_dots = @hide_lines = false
|
48
|
+
@baseline_color = 'red'
|
49
|
+
@baseline_value = nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def draw
|
53
|
+
super
|
54
|
+
|
55
|
+
return unless @has_data
|
56
|
+
|
57
|
+
# Check to see if more than one datapoint was given. NaN can result otherwise.
|
58
|
+
@x_increment = (@column_count > 1) ? (@graph_width / (@column_count - 1).to_f) : @graph_width
|
59
|
+
|
60
|
+
if (defined?(@norm_baseline)) then
|
61
|
+
level = @graph_top + (@graph_height - @norm_baseline * @graph_height)
|
62
|
+
@d = @d.push
|
63
|
+
@d.stroke_color @baseline_color
|
64
|
+
@d.fill_opacity 0.0
|
65
|
+
@d.stroke_dasharray(10, 20)
|
66
|
+
@d.stroke_width 5
|
67
|
+
@d.line(@graph_left, level, @graph_left + @graph_width, level)
|
68
|
+
@d = @d.pop
|
69
|
+
end
|
70
|
+
|
71
|
+
@norm_data.each do |data_row|
|
72
|
+
prev_x = prev_y = nil
|
73
|
+
|
74
|
+
@one_point = contains_one_point_only?(data_row)
|
75
|
+
|
76
|
+
data_row[DATA_VALUES_INDEX].each_with_index do |data_point, index|
|
77
|
+
new_x = @graph_left + (@x_increment * index)
|
78
|
+
next if data_point.nil?
|
79
|
+
|
80
|
+
draw_label(new_x, index)
|
81
|
+
|
82
|
+
new_y = @graph_top + (@graph_height - data_point * @graph_height)
|
83
|
+
|
84
|
+
# Reset each time to avoid thin-line errors
|
85
|
+
@d = @d.stroke data_row[DATA_COLOR_INDEX]
|
86
|
+
@d = @d.fill data_row[DATA_COLOR_INDEX]
|
87
|
+
@d = @d.stroke_opacity 1.0
|
88
|
+
@d = @d.stroke_width line_width ||
|
89
|
+
clip_value_if_greater_than(@columns / (@norm_data.first[DATA_VALUES_INDEX].size * 4), 5.0)
|
90
|
+
|
91
|
+
|
92
|
+
circle_radius = dot_radius ||
|
93
|
+
clip_value_if_greater_than(@columns / (@norm_data.first[DATA_VALUES_INDEX].size * 2.5), 5.0)
|
94
|
+
|
95
|
+
if !@hide_lines and !prev_x.nil? and !prev_y.nil? then
|
96
|
+
@d = @d.line(prev_x, prev_y, new_x, new_y)
|
97
|
+
elsif @one_point
|
98
|
+
# Show a circle if there's just one_point
|
99
|
+
@d = @d.circle(new_x, new_y, new_x - circle_radius, new_y)
|
100
|
+
end
|
101
|
+
@d = @d.circle(new_x, new_y, new_x - circle_radius, new_y) unless @hide_dots
|
102
|
+
|
103
|
+
prev_x = new_x
|
104
|
+
prev_y = new_y
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
@d.draw(@base_image)
|
110
|
+
end
|
111
|
+
|
112
|
+
def normalize
|
113
|
+
@maximum_value = [@maximum_value.to_f, @baseline_value.to_f].max
|
114
|
+
super
|
115
|
+
@norm_baseline = (@baseline_value.to_f / @maximum_value.to_f) if @baseline_value
|
116
|
+
end
|
117
|
+
|
118
|
+
def contains_one_point_only?(data_row)
|
119
|
+
# Spin through data to determine if there is just one_value present.
|
120
|
+
one_point = false
|
121
|
+
data_row[DATA_VALUES_INDEX].each do |data_point|
|
122
|
+
if !data_point.nil?
|
123
|
+
if one_point
|
124
|
+
# more than one point, bail
|
125
|
+
return false
|
126
|
+
else
|
127
|
+
# there is at least one data point
|
128
|
+
return true
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
return one_point
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
##
|
2
|
+
#
|
3
|
+
# Makes a small bar graph suitable for display at 200px or even smaller.
|
4
|
+
#
|
5
|
+
module Gruff
|
6
|
+
module Mini
|
7
|
+
|
8
|
+
class Bar < Gruff::Bar
|
9
|
+
|
10
|
+
include Gruff::Mini::Legend
|
11
|
+
|
12
|
+
def initialize_ivars
|
13
|
+
super
|
14
|
+
|
15
|
+
@hide_legend = true
|
16
|
+
@hide_title = true
|
17
|
+
@hide_line_numbers = true
|
18
|
+
|
19
|
+
@marker_font_size = 50.0
|
20
|
+
@minimum_value = 0.0
|
21
|
+
@maximum_value = 0.0
|
22
|
+
@legend_font_size = 60.0
|
23
|
+
end
|
24
|
+
|
25
|
+
def draw
|
26
|
+
expand_canvas_for_vertical_legend
|
27
|
+
|
28
|
+
super
|
29
|
+
|
30
|
+
draw_vertical_legend
|
31
|
+
@d.draw(@base_image)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Gruff
|
2
|
+
module Mini
|
3
|
+
module Legend
|
4
|
+
|
5
|
+
attr_accessor :hide_mini_legend, :legend_position
|
6
|
+
|
7
|
+
##
|
8
|
+
# The canvas needs to be bigger so we can put the legend beneath it.
|
9
|
+
|
10
|
+
def expand_canvas_for_vertical_legend
|
11
|
+
return if @hide_mini_legend
|
12
|
+
|
13
|
+
@legend_labels = @data.collect {|item| item[Gruff::Base::DATA_LABEL_INDEX] }
|
14
|
+
|
15
|
+
legend_height = scale_fontsize(
|
16
|
+
@data.length * calculate_line_height +
|
17
|
+
@top_margin + @bottom_margin)
|
18
|
+
|
19
|
+
@original_rows = @raw_rows
|
20
|
+
@original_columns = @raw_columns
|
21
|
+
|
22
|
+
case @legend_position
|
23
|
+
when :right then
|
24
|
+
@rows = [@rows, legend_height].max
|
25
|
+
@columns += calculate_legend_width + @left_margin
|
26
|
+
else
|
27
|
+
@rows += @data.length * calculate_caps_height(scale_fontsize(@legend_font_size)) * 1.7
|
28
|
+
end
|
29
|
+
render_background
|
30
|
+
end
|
31
|
+
|
32
|
+
def calculate_line_height
|
33
|
+
calculate_caps_height(@legend_font_size) * 1.7
|
34
|
+
end
|
35
|
+
|
36
|
+
def calculate_legend_width
|
37
|
+
width = @legend_labels.map { |label| calculate_width(@legend_font_size, label) }.max
|
38
|
+
scale_fontsize(width + 40*1.7)
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# Draw the legend beneath the existing graph.
|
43
|
+
|
44
|
+
def draw_vertical_legend
|
45
|
+
return if @hide_mini_legend
|
46
|
+
|
47
|
+
legend_square_width = 40.0 # small square with color of this item
|
48
|
+
legend_square_margin = 10.0
|
49
|
+
@legend_left_margin = 100.0
|
50
|
+
legend_top_margin = 40.0
|
51
|
+
|
52
|
+
# May fix legend drawing problem at small sizes
|
53
|
+
@d.font = @font if @font
|
54
|
+
@d.pointsize = @legend_font_size
|
55
|
+
|
56
|
+
case @legend_position
|
57
|
+
when :right then
|
58
|
+
current_x_offset = @original_columns + @left_margin
|
59
|
+
current_y_offset = @top_margin + legend_top_margin
|
60
|
+
else
|
61
|
+
current_x_offset = @legend_left_margin
|
62
|
+
current_y_offset = @original_rows + legend_top_margin
|
63
|
+
end
|
64
|
+
|
65
|
+
debug { @d.line 0.0, current_y_offset, @raw_columns, current_y_offset }
|
66
|
+
|
67
|
+
@legend_labels.each_with_index do |legend_label, index|
|
68
|
+
|
69
|
+
# Draw label
|
70
|
+
@d.fill = @font_color
|
71
|
+
@d.font = @font if @font
|
72
|
+
@d.pointsize = scale_fontsize(@legend_font_size)
|
73
|
+
@d.stroke = 'transparent'
|
74
|
+
@d.font_weight = Magick::NormalWeight
|
75
|
+
@d.gravity = Magick::WestGravity
|
76
|
+
@d = @d.annotate_scaled( @base_image,
|
77
|
+
@raw_columns, 1.0,
|
78
|
+
current_x_offset + (legend_square_width * 1.7), current_y_offset,
|
79
|
+
truncate_legend_label(legend_label), @scale)
|
80
|
+
|
81
|
+
# Now draw box with color of this dataset
|
82
|
+
@d = @d.stroke 'transparent'
|
83
|
+
@d = @d.fill @data[index][Gruff::Base::DATA_COLOR_INDEX]
|
84
|
+
@d = @d.rectangle(current_x_offset,
|
85
|
+
current_y_offset - legend_square_width / 2.0,
|
86
|
+
current_x_offset + legend_square_width,
|
87
|
+
current_y_offset + legend_square_width / 2.0)
|
88
|
+
|
89
|
+
current_y_offset += calculate_line_height
|
90
|
+
end
|
91
|
+
@color_index = 0
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# Shorten long labels so they will fit on the canvas.
|
96
|
+
#
|
97
|
+
# Department of Hu...
|
98
|
+
|
99
|
+
def truncate_legend_label(label)
|
100
|
+
truncated_label = label.to_s
|
101
|
+
while calculate_width(scale_fontsize(@legend_font_size), truncated_label) > (@columns - @legend_left_margin - @right_margin) && (truncated_label.length > 1)
|
102
|
+
truncated_label = truncated_label[0..truncated_label.length-2]
|
103
|
+
end
|
104
|
+
truncated_label + (truncated_label.length < label.to_s.length ? "..." : '')
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|