gruff 0.6.0-java → 0.11.0-java
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.
- checksums.yaml +5 -5
- data/.editorconfig +14 -0
- data/.github/ISSUE_TEMPLATE.md +18 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +109 -0
- data/.rubocop_todo.yml +112 -0
- data/.travis.yml +24 -15
- data/.yardopts +1 -0
- data/{History.txt → CHANGELOG.md} +72 -25
- data/Gemfile +3 -7
- data/README.md +57 -25
- data/Rakefile +21 -192
- data/assets/plastik/blue.png +0 -0
- data/assets/plastik/green.png +0 -0
- data/assets/plastik/red.png +0 -0
- data/docker/Dockerfile +14 -0
- data/docker/build.sh +4 -0
- data/docker/launch.sh +4 -0
- data/gruff.gemspec +21 -13
- data/init.rb +2 -0
- data/lib/gruff.rb +26 -2
- data/lib/gruff/accumulator_bar.rb +18 -8
- data/lib/gruff/area.rb +33 -19
- data/lib/gruff/bar.rb +76 -45
- data/lib/gruff/base.rb +435 -704
- data/lib/gruff/bezier.rb +32 -17
- data/lib/gruff/bullet.rb +62 -68
- data/lib/gruff/dot.rb +38 -82
- data/lib/gruff/helper/bar_conversion.rb +47 -0
- data/lib/gruff/helper/bar_value_label_mixin.rb +30 -0
- data/lib/gruff/helper/stacked_mixin.rb +23 -0
- data/lib/gruff/histogram.rb +60 -0
- data/lib/gruff/line.rb +134 -170
- data/lib/gruff/mini/bar.rb +17 -10
- data/lib/gruff/mini/legend.rb +24 -36
- data/lib/gruff/mini/pie.rb +18 -12
- data/lib/gruff/mini/side_bar.rb +26 -12
- data/lib/gruff/net.rb +68 -81
- data/lib/gruff/patch/rmagick.rb +33 -0
- data/lib/gruff/patch/string.rb +10 -0
- data/lib/gruff/photo_bar.rb +39 -42
- data/lib/gruff/pie.rb +180 -89
- data/lib/gruff/renderer/bezier.rb +21 -0
- data/lib/gruff/renderer/circle.rb +21 -0
- data/lib/gruff/renderer/dash_line.rb +22 -0
- data/lib/gruff/renderer/dot.rb +39 -0
- data/lib/gruff/renderer/ellipse.rb +21 -0
- data/lib/gruff/renderer/line.rb +42 -0
- data/lib/gruff/renderer/polygon.rb +23 -0
- data/lib/gruff/renderer/polyline.rb +21 -0
- data/lib/gruff/renderer/rectangle.rb +19 -0
- data/lib/gruff/renderer/renderer.rb +132 -0
- data/lib/gruff/renderer/text.rb +53 -0
- data/lib/gruff/scatter.rb +163 -182
- data/lib/gruff/scene.rb +31 -41
- data/lib/gruff/side_bar.rb +81 -65
- data/lib/gruff/side_stacked_bar.rb +78 -62
- data/lib/gruff/spider.rb +49 -57
- data/lib/gruff/stacked_area.rb +40 -32
- data/lib/gruff/stacked_bar.rb +86 -53
- data/lib/gruff/store/base_data.rb +38 -0
- data/lib/gruff/store/custom_data.rb +38 -0
- data/lib/gruff/store/store.rb +80 -0
- data/lib/gruff/store/xy_data.rb +59 -0
- data/lib/gruff/themes.rb +32 -33
- data/lib/gruff/version.rb +3 -1
- metadata +80 -102
- data/Manifest.txt +0 -81
- data/RELEASE.md +0 -30
- 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/lib/gruff/bar_conversion.rb +0 -46
- data/lib/gruff/deprecated.rb +0 -39
- data/lib/gruff/stacked_mixin.rb +0 -23
- data/test/gruff_test_case.rb +0 -154
- data/test/image_compare.rb +0 -58
- data/test/test_accumulator_bar.rb +0 -51
- data/test/test_area.rb +0 -134
- data/test/test_bar.rb +0 -505
- data/test/test_base.rb +0 -8
- data/test/test_bezier.rb +0 -33
- data/test/test_bullet.rb +0 -26
- data/test/test_dot.rb +0 -263
- data/test/test_labels_for_null_data.rb +0 -27
- data/test/test_legend.rb +0 -68
- data/test/test_line.rb +0 -657
- data/test/test_mini_bar.rb +0 -33
- data/test/test_mini_pie.rb +0 -25
- data/test/test_mini_side_bar.rb +0 -36
- data/test/test_net.rb +0 -231
- data/test/test_photo.rb +0 -41
- data/test/test_pie.rb +0 -161
- data/test/test_scatter.rb +0 -233
- data/test/test_scene.rb +0 -100
- data/test/test_side_bar.rb +0 -56
- data/test/test_sidestacked_bar.rb +0 -105
- data/test/test_spider.rb +0 -226
- data/test/test_stacked_area.rb +0 -52
- data/test/test_stacked_bar.rb +0 -68
data/lib/gruff/spider.rb
CHANGED
|
@@ -1,20 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
|
|
2
|
-
require
|
|
3
|
+
require 'gruff/base'
|
|
3
4
|
|
|
4
5
|
# Experimental!!! See also the Net graph.
|
|
5
6
|
#
|
|
6
|
-
#
|
|
7
|
+
# Here's how to set up a Gruff::Spider.
|
|
8
|
+
#
|
|
9
|
+
# g = Gruff::Spider.new(30)
|
|
10
|
+
# g.title = "Spider Graph"
|
|
11
|
+
# g.data :Strength, [10]
|
|
12
|
+
# g.data :Dexterity, [16]
|
|
13
|
+
# g.data :Constitution, [12]
|
|
14
|
+
# g.data :Intelligence, [12]
|
|
15
|
+
# g.data :Wisdom, [10]
|
|
16
|
+
# g.data 'Charisma', [16]
|
|
17
|
+
# g.write("spider.png")
|
|
18
|
+
|
|
7
19
|
class Gruff::Spider < Gruff::Base
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
attr_reader :transparent_background
|
|
13
|
-
attr_accessor :rotation
|
|
14
|
-
|
|
20
|
+
# Hide all text.
|
|
21
|
+
attr_writer :hide_axes
|
|
22
|
+
attr_writer :rotation
|
|
23
|
+
|
|
15
24
|
def transparent_background=(value)
|
|
16
|
-
@
|
|
17
|
-
@base_image = render_transparent_background if value
|
|
25
|
+
Gruff::Renderer.setup_transparent_background(@columns, @rows) if value
|
|
18
26
|
end
|
|
19
27
|
|
|
20
28
|
def hide_text=(value)
|
|
@@ -24,39 +32,40 @@ class Gruff::Spider < Gruff::Base
|
|
|
24
32
|
def initialize(max_value, target_width = 800)
|
|
25
33
|
super(target_width)
|
|
26
34
|
@max_value = max_value
|
|
27
|
-
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def initialize_ivars
|
|
38
|
+
super
|
|
39
|
+
@hide_legend = true
|
|
40
|
+
@hide_axes = false
|
|
41
|
+
@hide_text = false
|
|
28
42
|
@rotation = 0
|
|
29
43
|
end
|
|
30
|
-
|
|
44
|
+
private :initialize_ivars
|
|
45
|
+
|
|
31
46
|
def draw
|
|
32
47
|
@hide_line_markers = true
|
|
33
|
-
|
|
48
|
+
|
|
34
49
|
super
|
|
35
50
|
|
|
36
|
-
return unless
|
|
51
|
+
return unless data_given?
|
|
37
52
|
|
|
38
53
|
# Setup basic positioning
|
|
39
|
-
diameter = @graph_height
|
|
40
54
|
radius = @graph_height / 2.0
|
|
41
|
-
top_x = @graph_left + (@graph_width - diameter) / 2.0
|
|
42
55
|
center_x = @graph_left + (@graph_width / 2.0)
|
|
43
56
|
center_y = @graph_top + (@graph_height / 2.0) - 25 # Move graph up a bit
|
|
44
57
|
|
|
45
58
|
@unit_length = radius / @max_value
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
prev_degrees = 0.0
|
|
49
|
-
additive_angle = (2 * Math::PI)/ @data.size
|
|
50
|
-
|
|
51
|
-
current_angle = rotation * Math::PI / 180.0
|
|
59
|
+
|
|
60
|
+
additive_angle = (2 * Math::PI) / store.length
|
|
52
61
|
|
|
53
62
|
# Draw axes
|
|
54
|
-
draw_axes(center_x, center_y, radius, additive_angle) unless hide_axes
|
|
63
|
+
draw_axes(center_x, center_y, radius, additive_angle) unless @hide_axes
|
|
55
64
|
|
|
56
65
|
# Draw polygon
|
|
57
66
|
draw_polygon(center_x, center_y, additive_angle)
|
|
58
67
|
|
|
59
|
-
|
|
68
|
+
Gruff::Renderer.finish
|
|
60
69
|
end
|
|
61
70
|
|
|
62
71
|
private
|
|
@@ -66,42 +75,30 @@ private
|
|
|
66
75
|
end
|
|
67
76
|
|
|
68
77
|
def draw_label(center_x, center_y, angle, radius, amount)
|
|
69
|
-
r_offset = 50
|
|
78
|
+
r_offset = 50 # The distance out from the center of the pie to get point
|
|
70
79
|
x_offset = center_x # The label points need to be tweaked slightly
|
|
71
80
|
y_offset = center_y + 0 # This one doesn't though
|
|
72
81
|
x = x_offset + ((radius + r_offset) * Math.cos(angle))
|
|
73
82
|
y = y_offset + ((radius + r_offset) * Math.sin(angle))
|
|
74
83
|
|
|
75
84
|
# Draw label
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
@d.pointsize = scale_fontsize(legend_font_size)
|
|
79
|
-
@d.stroke = 'transparent'
|
|
80
|
-
@d.font_weight = BoldWeight
|
|
81
|
-
@d.gravity = CenterGravity
|
|
82
|
-
@d.annotate_scaled( @base_image,
|
|
83
|
-
0, 0,
|
|
84
|
-
x, y,
|
|
85
|
-
amount, @scale)
|
|
85
|
+
text_renderer = Gruff::Renderer::Text.new(amount, font: @font, size: @legend_font_size, color: @marker_color, weight: Magick::BoldWeight)
|
|
86
|
+
text_renderer.add_to_render_queue(0, 0, x, y, Magick::CenterGravity)
|
|
86
87
|
end
|
|
87
88
|
|
|
88
89
|
def draw_axes(center_x, center_y, radius, additive_angle, line_color = nil)
|
|
89
|
-
return if hide_axes
|
|
90
|
+
return if @hide_axes
|
|
90
91
|
|
|
91
|
-
current_angle = rotation * Math::PI / 180.0
|
|
92
|
-
|
|
93
|
-
@data.each do |data_row|
|
|
94
|
-
@d.stroke(line_color || data_row[DATA_COLOR_INDEX])
|
|
95
|
-
@d.stroke_width 5.0
|
|
92
|
+
current_angle = @rotation * Math::PI / 180.0
|
|
96
93
|
|
|
94
|
+
store.data.each do |data_row|
|
|
97
95
|
x_offset = radius * Math.cos(current_angle)
|
|
98
96
|
y_offset = radius * Math.sin(current_angle)
|
|
99
97
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
center_y + y_offset)
|
|
98
|
+
Gruff::Renderer::Line.new(color: line_color || data_row.color, width: 5.0)
|
|
99
|
+
.render(center_x, center_y, center_x + x_offset, center_y + y_offset)
|
|
103
100
|
|
|
104
|
-
draw_label(center_x, center_y, current_angle, radius, data_row
|
|
101
|
+
draw_label(center_x, center_y, current_angle, radius, data_row.label.to_s) unless @hide_text
|
|
105
102
|
|
|
106
103
|
current_angle += additive_angle
|
|
107
104
|
end
|
|
@@ -109,23 +106,18 @@ private
|
|
|
109
106
|
|
|
110
107
|
def draw_polygon(center_x, center_y, additive_angle, color = nil)
|
|
111
108
|
points = []
|
|
112
|
-
current_angle = rotation * Math::PI / 180.0
|
|
109
|
+
current_angle = @rotation * Math::PI / 180.0
|
|
113
110
|
|
|
114
|
-
|
|
115
|
-
points << center_x + normalize_points(data_row
|
|
116
|
-
points << center_y + normalize_points(data_row
|
|
111
|
+
store.data.each do |data_row|
|
|
112
|
+
points << center_x + normalize_points(data_row.points.first) * Math.cos(current_angle)
|
|
113
|
+
points << center_y + normalize_points(data_row.points.first) * Math.sin(current_angle)
|
|
117
114
|
current_angle += additive_angle
|
|
118
115
|
end
|
|
119
116
|
|
|
120
|
-
|
|
121
|
-
@d.stroke(color || @marker_color)
|
|
122
|
-
@d.fill(color || @marker_color)
|
|
123
|
-
@d.fill_opacity 0.4
|
|
124
|
-
@d.polygon(*points)
|
|
117
|
+
Gruff::Renderer::Polygon.new(color: color || @marker_color, opacity: 0.4).render(points)
|
|
125
118
|
end
|
|
126
119
|
|
|
127
120
|
def sums_for_spider
|
|
128
|
-
|
|
121
|
+
store.data.reduce(0.0) { |sum, data_row| sum + data_row.points.first }
|
|
129
122
|
end
|
|
130
|
-
|
|
131
123
|
end
|
data/lib/gruff/stacked_area.rb
CHANGED
|
@@ -1,67 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
|
|
2
|
-
require
|
|
3
|
-
require
|
|
3
|
+
require 'gruff/base'
|
|
4
|
+
require 'gruff/helper/stacked_mixin'
|
|
4
5
|
|
|
6
|
+
#
|
|
7
|
+
# Here's how to set up a Gruff::StackedArea.
|
|
8
|
+
#
|
|
9
|
+
# g = Gruff::StackedArea.new
|
|
10
|
+
# g.title = 'StackedArea Graph'
|
|
11
|
+
# g.data :Jimmy, [25, 36, 86, 39, 25, 31, 79, 88]
|
|
12
|
+
# g.data :Charles, [80, 54, 67, 54, 68, 70, 90, 95]
|
|
13
|
+
# g.data :Julie, [22, 29, 35, 38, 36, 40, 46, 57]
|
|
14
|
+
# g.write('stacked_area.png')
|
|
15
|
+
#
|
|
5
16
|
class Gruff::StackedArea < Gruff::Base
|
|
6
17
|
include StackedMixin
|
|
7
|
-
|
|
8
|
-
|
|
18
|
+
attr_writer :last_series_goes_on_bottom
|
|
19
|
+
|
|
20
|
+
def initialize_ivars
|
|
21
|
+
super
|
|
22
|
+
@last_series_goes_on_bottom = false
|
|
23
|
+
end
|
|
24
|
+
private :initialize_ivars
|
|
25
|
+
|
|
9
26
|
def draw
|
|
10
|
-
|
|
27
|
+
calculate_maximum_by_stack
|
|
11
28
|
super
|
|
12
29
|
|
|
13
|
-
return unless
|
|
30
|
+
return unless data_given?
|
|
14
31
|
|
|
15
|
-
|
|
16
|
-
@d = @d.stroke 'transparent'
|
|
32
|
+
x_increment = @graph_width / (column_count - 1).to_f
|
|
17
33
|
|
|
18
|
-
height = Array.new(
|
|
34
|
+
height = Array.new(column_count, 0)
|
|
19
35
|
|
|
20
36
|
data_points = nil
|
|
21
|
-
iterator = last_series_goes_on_bottom ? :reverse_each : :each
|
|
22
|
-
|
|
37
|
+
iterator = @last_series_goes_on_bottom ? :reverse_each : :each
|
|
38
|
+
store.norm_data.public_send(iterator) do |data_row|
|
|
23
39
|
prev_data_points = data_points
|
|
24
|
-
data_points =
|
|
25
|
-
|
|
26
|
-
@d = @d.fill data_row[DATA_COLOR_INDEX]
|
|
40
|
+
data_points = []
|
|
27
41
|
|
|
28
|
-
data_row
|
|
42
|
+
data_row.points.each_with_index do |data_point, index|
|
|
29
43
|
# Use incremented x and scaled y
|
|
30
|
-
new_x = @graph_left + (
|
|
44
|
+
new_x = @graph_left + (x_increment * index)
|
|
31
45
|
new_y = @graph_top + (@graph_height - data_point * @graph_height - height[index])
|
|
32
46
|
|
|
33
47
|
height[index] += (data_point * @graph_height)
|
|
34
|
-
|
|
48
|
+
|
|
35
49
|
data_points << new_x
|
|
36
50
|
data_points << new_y
|
|
37
|
-
|
|
38
|
-
draw_label(new_x, index)
|
|
39
51
|
|
|
52
|
+
draw_label(new_x, index)
|
|
40
53
|
end
|
|
41
54
|
|
|
55
|
+
poly_points = data_points.dup
|
|
42
56
|
if prev_data_points
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
poly_points << prev_data_points[2*i]
|
|
46
|
-
poly_points << prev_data_points[2*i+1]
|
|
57
|
+
(prev_data_points.length / 2 - 1).downto(0) do |i|
|
|
58
|
+
poly_points << prev_data_points[2 * i]
|
|
59
|
+
poly_points << prev_data_points[2 * i + 1]
|
|
47
60
|
end
|
|
48
|
-
poly_points << data_points[0]
|
|
49
|
-
poly_points << data_points[1]
|
|
50
61
|
else
|
|
51
|
-
poly_points = data_points.dup
|
|
52
62
|
poly_points << @graph_right
|
|
53
63
|
poly_points << @graph_bottom - 1
|
|
54
64
|
poly_points << @graph_left
|
|
55
65
|
poly_points << @graph_bottom - 1
|
|
56
|
-
poly_points << data_points[0]
|
|
57
|
-
poly_points << data_points[1]
|
|
58
66
|
end
|
|
59
|
-
|
|
67
|
+
poly_points << data_points[0]
|
|
68
|
+
poly_points << data_points[1]
|
|
60
69
|
|
|
70
|
+
Gruff::Renderer::Polygon.new(color: data_row.color).render(poly_points)
|
|
61
71
|
end
|
|
62
72
|
|
|
63
|
-
|
|
73
|
+
Gruff::Renderer.finish
|
|
64
74
|
end
|
|
65
|
-
|
|
66
|
-
|
|
67
75
|
end
|
data/lib/gruff/stacked_bar.rb
CHANGED
|
@@ -1,61 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
|
|
2
|
-
require
|
|
3
|
-
require
|
|
3
|
+
require 'gruff/base'
|
|
4
|
+
require 'gruff/helper/stacked_mixin'
|
|
5
|
+
require 'gruff/helper/bar_value_label_mixin'
|
|
4
6
|
|
|
7
|
+
#
|
|
8
|
+
# Here's how to set up a Gruff::StackedBar.
|
|
9
|
+
#
|
|
10
|
+
# g = Gruff::StackedBar.new
|
|
11
|
+
# g.title = 'StackedBar Graph'
|
|
12
|
+
# g.data :Art, [0, 5, 8, 15]
|
|
13
|
+
# g.data :Philosophy, [10, 3, 2, 8]
|
|
14
|
+
# g.data :Science, [2, 15, 8, 11]
|
|
15
|
+
# g.write('stacked_bar.png')
|
|
16
|
+
#
|
|
5
17
|
class Gruff::StackedBar < Gruff::Base
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
# Spacing factor applied between bars
|
|
9
|
-
attr_accessor :bar_spacing
|
|
10
|
-
|
|
11
|
-
# Number of pixels between bar segments
|
|
12
|
-
attr_accessor :segment_spacing
|
|
13
|
-
|
|
14
|
-
# Draws a bar graph, but multiple sets are stacked on top of each other.
|
|
15
|
-
def draw
|
|
16
|
-
get_maximum_by_stack
|
|
17
|
-
super
|
|
18
|
-
return unless @has_data
|
|
19
|
-
|
|
20
|
-
# Setup spacing.
|
|
21
|
-
#
|
|
22
|
-
# Columns sit stacked.
|
|
23
|
-
@bar_spacing ||= 0.9
|
|
24
|
-
@segment_spacing ||= 1
|
|
25
|
-
@bar_width = @graph_width / @column_count.to_f
|
|
26
|
-
padding = (@bar_width * (1 - @bar_spacing)) / 2
|
|
27
|
-
|
|
28
|
-
@d = @d.stroke_opacity 0.0
|
|
29
|
-
|
|
30
|
-
height = Array.new(@column_count, 0)
|
|
31
|
-
|
|
32
|
-
@norm_data.each_with_index do |data_row, row_index|
|
|
33
|
-
data_row[DATA_VALUES_INDEX].each_with_index do |data_point, point_index|
|
|
34
|
-
@d = @d.fill data_row[DATA_COLOR_INDEX]
|
|
35
|
-
|
|
36
|
-
# Calculate center based on bar_width and current row
|
|
37
|
-
label_center = @graph_left + (@bar_width * point_index) + (@bar_width * @bar_spacing / 2.0)
|
|
38
|
-
draw_label(label_center, point_index)
|
|
39
|
-
|
|
40
|
-
next if (data_point == 0)
|
|
41
|
-
# Use incremented x and scaled y
|
|
42
|
-
left_x = @graph_left + (@bar_width * point_index) + padding
|
|
43
|
-
left_y = @graph_top + (@graph_height -
|
|
44
|
-
data_point * @graph_height -
|
|
45
|
-
height[point_index]) + @segment_spacing
|
|
46
|
-
right_x = left_x + @bar_width * @bar_spacing
|
|
47
|
-
right_y = @graph_top + @graph_height - height[point_index] - @segment_spacing
|
|
48
|
-
|
|
49
|
-
# update the total height of the current stacked bar
|
|
50
|
-
height[point_index] += (data_point * @graph_height )
|
|
51
|
-
|
|
52
|
-
@d = @d.rectangle(left_x, left_y, right_x, right_y)
|
|
53
|
-
|
|
54
|
-
end
|
|
18
|
+
include StackedMixin
|
|
19
|
+
include BarValueLabelMixin
|
|
55
20
|
|
|
21
|
+
# Spacing factor applied between bars.
|
|
22
|
+
attr_writer :bar_spacing
|
|
23
|
+
|
|
24
|
+
# Number of pixels between bar segments.
|
|
25
|
+
attr_writer :segment_spacing
|
|
26
|
+
|
|
27
|
+
# Set the number output format for labels using sprintf.
|
|
28
|
+
# Default is +"%.2f"+.
|
|
29
|
+
attr_writer :label_formatting
|
|
30
|
+
|
|
31
|
+
# Output the values for the bars on a bar graph.
|
|
32
|
+
# Default is +false+.
|
|
33
|
+
attr_writer :show_labels_for_bar_values
|
|
34
|
+
|
|
35
|
+
def initialize_ivars
|
|
36
|
+
super
|
|
37
|
+
@bar_spacing = 0.9
|
|
38
|
+
@segment_spacing = 2
|
|
39
|
+
@label_formatting = nil
|
|
40
|
+
@show_labels_for_bar_values = false
|
|
41
|
+
end
|
|
42
|
+
private :initialize_ivars
|
|
43
|
+
|
|
44
|
+
# Draws a bar graph, but multiple sets are stacked on top of each other.
|
|
45
|
+
def draw
|
|
46
|
+
calculate_maximum_by_stack
|
|
47
|
+
super
|
|
48
|
+
return unless data_given?
|
|
49
|
+
|
|
50
|
+
# Setup spacing.
|
|
51
|
+
#
|
|
52
|
+
# Columns sit stacked.
|
|
53
|
+
bar_width = @graph_width / column_count.to_f
|
|
54
|
+
padding = (bar_width * (1 - @bar_spacing)) / 2
|
|
55
|
+
|
|
56
|
+
height = Array.new(column_count, 0)
|
|
57
|
+
bar_value_label = BarValueLabel.new(column_count, bar_width)
|
|
58
|
+
|
|
59
|
+
store.norm_data.each_with_index do |data_row, row_index|
|
|
60
|
+
data_row.points.each_with_index do |data_point, point_index|
|
|
61
|
+
next if data_point == 0
|
|
62
|
+
|
|
63
|
+
# Use incremented x and scaled y
|
|
64
|
+
left_x = @graph_left + (bar_width * point_index) + padding
|
|
65
|
+
left_y = @graph_top + (@graph_height -
|
|
66
|
+
data_point * @graph_height -
|
|
67
|
+
height[point_index]) + @segment_spacing
|
|
68
|
+
right_x = left_x + bar_width * @bar_spacing
|
|
69
|
+
right_y = @graph_top + @graph_height - height[point_index]
|
|
70
|
+
|
|
71
|
+
# update the total height of the current stacked bar
|
|
72
|
+
height[point_index] += (data_point * @graph_height)
|
|
73
|
+
|
|
74
|
+
rect_renderer = Gruff::Renderer::Rectangle.new(color: data_row.color)
|
|
75
|
+
rect_renderer.render(left_x, left_y, right_x, right_y)
|
|
76
|
+
|
|
77
|
+
# Calculate center based on bar_width and current row
|
|
78
|
+
label_center = left_x + bar_width * @bar_spacing / 2.0
|
|
79
|
+
draw_label(label_center, point_index)
|
|
80
|
+
|
|
81
|
+
bar_value_label.coordinates[point_index] = [left_x, left_y, right_x, right_y]
|
|
82
|
+
bar_value_label.values[point_index] += store.data[row_index].points[point_index]
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
if @show_labels_for_bar_values
|
|
87
|
+
bar_value_label.prepare_rendering(@label_formatting) do |x, y, text|
|
|
88
|
+
draw_value_label(x, y, text, true)
|
|
56
89
|
end
|
|
57
|
-
|
|
58
|
-
@d.draw(@base_image)
|
|
59
90
|
end
|
|
60
91
|
|
|
92
|
+
Gruff::Renderer.finish
|
|
93
|
+
end
|
|
61
94
|
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gruff
|
|
4
|
+
# @private
|
|
5
|
+
class Store
|
|
6
|
+
class BaseData < Struct.new(:label, :points, :color)
|
|
7
|
+
def initialize(label, points, color)
|
|
8
|
+
self.label = label.to_s
|
|
9
|
+
self.points = Array(points)
|
|
10
|
+
self.color = color
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def empty?
|
|
14
|
+
points.empty?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def columns
|
|
18
|
+
points.length
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def min
|
|
22
|
+
points.compact.min
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def max
|
|
26
|
+
points.compact.max
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def normalize(args = {})
|
|
30
|
+
norm_points = points.map do |point|
|
|
31
|
+
point.nil? ? nil : (point.to_f - args[:minimum].to_f) / args[:spread]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
self.class.new(label, norm_points, color)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|