gruff 0.7.0-java → 0.12.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/{History.txt → CHANGELOG.md} +81 -25
- data/Gemfile +3 -1
- data/README.md +51 -23
- data/assets/plastik/blue.png +0 -0
- data/assets/plastik/green.png +0 -0
- data/assets/plastik/red.png +0 -0
- data/gruff.gemspec +23 -13
- data/init.rb +2 -0
- data/lib/gruff.rb +33 -4
- data/lib/gruff/accumulator_bar.rb +17 -9
- data/lib/gruff/area.rb +31 -21
- data/lib/gruff/bar.rb +90 -46
- data/lib/gruff/base.rb +476 -710
- data/lib/gruff/bezier.rb +29 -18
- data/lib/gruff/bullet.rb +58 -71
- data/lib/gruff/dot.rb +35 -83
- data/lib/gruff/helper/bar_conversion.rb +47 -0
- data/lib/gruff/helper/bar_value_label_mixin.rb +33 -0
- data/lib/gruff/helper/stacked_mixin.rb +23 -0
- data/lib/gruff/histogram.rb +59 -0
- data/lib/gruff/line.rb +121 -199
- data/lib/gruff/mini/bar.rb +17 -10
- data/lib/gruff/mini/legend.rb +26 -38
- data/lib/gruff/mini/pie.rb +18 -13
- data/lib/gruff/mini/side_bar.rb +25 -12
- data/lib/gruff/net.rb +69 -83
- data/lib/gruff/patch/rmagick.rb +31 -0
- data/lib/gruff/patch/string.rb +13 -0
- data/lib/gruff/photo_bar.rb +36 -43
- data/lib/gruff/pie.rb +42 -103
- data/lib/gruff/renderer/bezier.rb +22 -0
- data/lib/gruff/renderer/circle.rb +22 -0
- data/lib/gruff/renderer/dash_line.rb +23 -0
- data/lib/gruff/renderer/dot.rb +40 -0
- data/lib/gruff/renderer/ellipse.rb +22 -0
- data/lib/gruff/renderer/line.rb +43 -0
- data/lib/gruff/renderer/polygon.rb +24 -0
- data/lib/gruff/renderer/polyline.rb +22 -0
- data/lib/gruff/renderer/rectangle.rb +20 -0
- data/lib/gruff/renderer/renderer.rb +120 -0
- data/lib/gruff/renderer/text.rb +57 -0
- data/lib/gruff/scatter.rb +128 -201
- data/lib/gruff/scene.rb +30 -41
- data/lib/gruff/side_bar.rb +100 -68
- data/lib/gruff/side_stacked_bar.rb +92 -63
- data/lib/gruff/spider.rb +47 -53
- data/lib/gruff/stacked_area.rb +37 -34
- data/lib/gruff/stacked_bar.rb +99 -54
- data/lib/gruff/store/basic_data.rb +36 -0
- data/lib/gruff/store/custom_data.rb +36 -0
- data/lib/gruff/store/store.rb +81 -0
- data/lib/gruff/store/xy_data.rb +58 -0
- data/lib/gruff/themes.rb +32 -33
- data/lib/gruff/version.rb +3 -1
- metadata +74 -102
- data/.gitignore +0 -7
- data/.travis.yml +0 -19
- data/Manifest.txt +0 -81
- data/RELEASE.md +0 -30
- data/Rakefile +0 -218
- 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 -152
- 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 -33
- 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 -674
- 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 -194
- data/test/test_scatter.rb +0 -270
- 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/scene.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
|
-
require
|
3
|
-
require File.dirname(__FILE__) + '/base'
|
3
|
+
require 'observer'
|
4
4
|
|
5
|
-
##
|
6
5
|
# A scene is a non-linear graph that assembles layers together to tell a story.
|
7
|
-
# Layers are folders with appropriately named files (see below). You can group
|
6
|
+
# Layers are folders with appropriately named files (see below). You can group
|
8
7
|
# layers and control them together or just set their values individually.
|
9
8
|
#
|
10
9
|
# Examples:
|
@@ -12,8 +11,6 @@ require File.dirname(__FILE__) + '/base'
|
|
12
11
|
# * A city scene that changes with the time of day and the weather conditions.
|
13
12
|
# * A traffic map that shows red lines on streets that are crowded and green on free-flowing ones.
|
14
13
|
#
|
15
|
-
# Usage:
|
16
|
-
#
|
17
14
|
# g = Gruff::Scene.new("500x100", "path/to/city_scene_directory")
|
18
15
|
#
|
19
16
|
# # Define order of layers, back to front
|
@@ -31,34 +28,31 @@ require File.dirname(__FILE__) + '/base'
|
|
31
28
|
# # Write the final graph to disk
|
32
29
|
# g.write "hazy_daytime_city_scene.png"
|
33
30
|
#
|
34
|
-
#
|
35
31
|
# There are several rules that will magically select a layer when possible.
|
36
32
|
#
|
37
33
|
# * Numbered files will be selected according to the closest value that is less than the input value.
|
38
|
-
# * 'true.png' and 'false.png' will be used as booleans.
|
34
|
+
# * +'true.png'+ and +'false.png'+ will be used as booleans.
|
39
35
|
# * Other named files will be used if the input matches the filename (without the filetype extension).
|
40
|
-
# * If there is a file named 'default.png'
|
41
|
-
|
36
|
+
# * If there is a file named +'default.png'+, it will be used unless other input values are set for the corresponding layer.
|
42
37
|
class Gruff::Scene < Gruff::Base
|
43
|
-
|
44
|
-
# An array listing the foldernames that will be rendered, from back to front.
|
45
|
-
#
|
46
|
-
# g.layers = %w(sky clouds buildings street people)
|
38
|
+
# An array listing the folder names that will be rendered, from back to front.
|
47
39
|
#
|
40
|
+
# @example
|
41
|
+
# g.layers = %w(sky clouds buildings street people)
|
48
42
|
attr_reader :layers
|
49
43
|
|
50
44
|
def initialize(target_width, base_dir)
|
51
45
|
@base_dir = base_dir
|
52
46
|
@groups = {}
|
53
|
-
@layers = []
|
47
|
+
@layers = []
|
54
48
|
super target_width
|
55
49
|
end
|
56
50
|
|
57
51
|
def draw
|
58
52
|
# Join all the custom paths and filter out the empty ones
|
59
|
-
image_paths = @layers.map
|
53
|
+
image_paths = @layers.map(&:path).reject(&:empty?)
|
60
54
|
images = Magick::ImageList.new(*image_paths)
|
61
|
-
|
55
|
+
Gruff::Renderer.background_image = images.flatten_images
|
62
56
|
end
|
63
57
|
|
64
58
|
def layers=(ordered_list)
|
@@ -78,10 +72,10 @@ class Gruff::Scene < Gruff::Base
|
|
78
72
|
def method_missing(method_name, *args)
|
79
73
|
case method_name.to_s
|
80
74
|
when /^(\w+)_group=$/
|
81
|
-
add_group
|
75
|
+
add_group Regexp.last_match(1), *args
|
82
76
|
return
|
83
77
|
when /^(\w+)=$/
|
84
|
-
set_input
|
78
|
+
set_input Regexp.last_match(1), args.first
|
85
79
|
return
|
86
80
|
end
|
87
81
|
super
|
@@ -94,20 +88,16 @@ private
|
|
94
88
|
end
|
95
89
|
|
96
90
|
def set_input(input_name, input_value)
|
97
|
-
if
|
91
|
+
if !@groups[input_name].nil?
|
98
92
|
@groups[input_name].send_updates(input_value)
|
99
|
-
|
100
|
-
|
101
|
-
chosen_layer.update input_value
|
102
|
-
end
|
93
|
+
elsif chosen_layer = @layers.find { |layer| layer.name == input_name }
|
94
|
+
chosen_layer.update input_value
|
103
95
|
end
|
104
96
|
end
|
105
|
-
|
106
97
|
end
|
107
98
|
|
108
|
-
|
99
|
+
# @private
|
109
100
|
class Gruff::Group
|
110
|
-
|
111
101
|
include Observable
|
112
102
|
attr_reader :name
|
113
103
|
|
@@ -117,31 +107,29 @@ class Gruff::Group
|
|
117
107
|
layer.observe self
|
118
108
|
end
|
119
109
|
end
|
120
|
-
|
110
|
+
|
121
111
|
def send_updates(value)
|
122
112
|
changed
|
123
113
|
notify_observers value
|
124
114
|
end
|
125
|
-
|
126
115
|
end
|
127
116
|
|
128
|
-
|
117
|
+
# @private
|
129
118
|
class Gruff::Layer
|
130
|
-
|
131
119
|
attr_reader :name
|
132
|
-
|
120
|
+
|
133
121
|
def initialize(base_dir, folder_name)
|
134
122
|
@base_dir = base_dir.to_s
|
135
123
|
@name = folder_name.to_s
|
136
124
|
@filenames = Dir.open(File.join(base_dir, folder_name)).entries.select { |file| file =~ /^[^.]+\.png$/ }.sort
|
137
125
|
@selected_filename = select_default
|
138
126
|
end
|
139
|
-
|
127
|
+
|
140
128
|
# Register this layer so it receives updates from the group
|
141
129
|
def observe(obj)
|
142
130
|
obj.add_observer self
|
143
131
|
end
|
144
|
-
|
132
|
+
|
145
133
|
# Choose the appropriate filename for this layer, based on the input
|
146
134
|
def update(value)
|
147
135
|
@selected_filename = case value.to_s
|
@@ -152,7 +140,7 @@ class Gruff::Layer
|
|
152
140
|
when /^-?(\d+\.)?\d+$/
|
153
141
|
select_numeric value
|
154
142
|
when /(\d\d):(\d\d):\d\d/
|
155
|
-
select_time "#{
|
143
|
+
select_time "#{Regexp.last_match(1)}#{Regexp.last_match(2)}"
|
156
144
|
else
|
157
145
|
select_default
|
158
146
|
end
|
@@ -165,6 +153,7 @@ class Gruff::Layer
|
|
165
153
|
unless @selected_filename.nil? || @selected_filename.empty?
|
166
154
|
return File.join(@base_dir, @name, @selected_filename)
|
167
155
|
end
|
156
|
+
|
168
157
|
''
|
169
158
|
end
|
170
159
|
|
@@ -179,7 +168,7 @@ private
|
|
179
168
|
def select_numeric(value)
|
180
169
|
file_exists_or_blank value.to_s.gsub('-', '_')
|
181
170
|
end
|
182
|
-
|
171
|
+
|
183
172
|
def select_time(value)
|
184
173
|
times = @filenames.map { |filename| filename.gsub('.png', '') }
|
185
174
|
times.each_with_index do |time, index|
|
@@ -187,16 +176,17 @@ private
|
|
187
176
|
return "#{times[index - 1]}.png"
|
188
177
|
end
|
189
178
|
end
|
190
|
-
|
179
|
+
|
180
|
+
"#{times.last}.png"
|
191
181
|
end
|
192
|
-
|
182
|
+
|
193
183
|
# Match "partly cloudy" to "partly_cloudy.png"
|
194
184
|
def select_string(value)
|
195
185
|
file_exists_or_blank value.to_s.gsub(' ', '_')
|
196
186
|
end
|
197
|
-
|
187
|
+
|
198
188
|
def select_default
|
199
|
-
@filenames.include?(
|
189
|
+
@filenames.include?('default.png') ? 'default.png' : ''
|
200
190
|
end
|
201
191
|
|
202
192
|
# Returns the string "#{filename}.png", if it exists.
|
@@ -205,5 +195,4 @@ private
|
|
205
195
|
def file_exists_or_blank(filename)
|
206
196
|
@filenames.include?("#{filename}.png") ? "#{filename}.png" : select_default
|
207
197
|
end
|
208
|
-
|
209
198
|
end
|
data/lib/gruff/side_bar.rb
CHANGED
@@ -1,46 +1,97 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
##
|
4
3
|
# Graph with individual horizontal bars instead of vertical bars.
|
5
|
-
|
4
|
+
#
|
5
|
+
# Here's how to set up a Gruff::SideBar.
|
6
|
+
#
|
7
|
+
# g = Gruff::SideBar.new
|
8
|
+
# g.title = 'SideBar Graph'
|
9
|
+
# g.labels = {
|
10
|
+
# 0 => '5/6',
|
11
|
+
# 1 => '5/15',
|
12
|
+
# 2 => '5/24',
|
13
|
+
# 3 => '5/30',
|
14
|
+
# }
|
15
|
+
# g.group_spacing = 20
|
16
|
+
# g.data :Art, [0, 5, 8, 15]
|
17
|
+
# g.data :Philosophy, [10, 3, 2, 8]
|
18
|
+
# g.data :Science, [2, 15, 8, 11]
|
19
|
+
# g.write('sidebar.png')
|
20
|
+
#
|
6
21
|
class Gruff::SideBar < Gruff::Base
|
22
|
+
using String::GruffCommify
|
23
|
+
|
24
|
+
# Spacing factor applied between bars.
|
25
|
+
attr_writer :bar_spacing
|
26
|
+
|
27
|
+
# Spacing factor applied between a group of bars belonging to the same label.
|
28
|
+
attr_writer :group_spacing
|
29
|
+
|
30
|
+
# Set the number output format for labels using sprintf.
|
31
|
+
# Default is +"%.2f"+.
|
32
|
+
attr_writer :label_formatting
|
7
33
|
|
8
|
-
#
|
9
|
-
|
34
|
+
# Output the values for the bars on a bar graph.
|
35
|
+
# Default is +false+.
|
36
|
+
attr_writer :show_labels_for_bar_values
|
37
|
+
|
38
|
+
# Prevent drawing of column labels left of a side bar graph. Default is +false+.
|
39
|
+
attr_writer :hide_labels
|
40
|
+
|
41
|
+
def initialize_ivars
|
42
|
+
super
|
43
|
+
@bar_spacing = 0.9
|
44
|
+
@group_spacing = 10
|
45
|
+
@label_formatting = nil
|
46
|
+
@show_labels_for_bar_values = false
|
47
|
+
@hide_labels = false
|
48
|
+
end
|
49
|
+
private :initialize_ivars
|
10
50
|
|
11
51
|
def draw
|
12
52
|
@has_left_labels = true
|
13
53
|
super
|
14
54
|
|
15
|
-
return unless
|
55
|
+
return unless data_given?
|
56
|
+
|
16
57
|
draw_bars
|
17
58
|
end
|
18
59
|
|
19
|
-
|
60
|
+
protected
|
61
|
+
|
62
|
+
def hide_labels?
|
63
|
+
@hide_labels
|
64
|
+
end
|
65
|
+
|
66
|
+
def hide_left_label_area?
|
67
|
+
hide_labels?
|
68
|
+
end
|
69
|
+
|
70
|
+
def hide_bottom_label_area?
|
71
|
+
@hide_line_markers
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
20
75
|
|
21
76
|
def draw_bars
|
22
77
|
# Setup spacing.
|
23
78
|
#
|
24
|
-
@
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
height = Array.new(@column_count, 0)
|
30
|
-
length = Array.new(@column_count, @graph_left)
|
31
|
-
padding = (@bar_width * (1 - @bar_spacing)) / 2
|
79
|
+
bars_width = (@graph_height - calculate_spacing) / column_count.to_f
|
80
|
+
bar_width = bars_width / store.length
|
81
|
+
height = Array.new(column_count, 0)
|
82
|
+
length = Array.new(column_count, @graph_left)
|
83
|
+
padding = (bar_width * (1 - @bar_spacing)) / 2
|
32
84
|
|
33
85
|
# if we're a side stacked bar then we don't need to draw ourself at all
|
34
86
|
# because sometimes (due to different heights/min/max) you can actually
|
35
87
|
# see both graphs and it looks like crap
|
36
|
-
return if
|
37
|
-
|
38
|
-
@norm_data.each_with_index do |data_row, row_index|
|
39
|
-
@d = @d.fill data_row[DATA_COLOR_INDEX]
|
88
|
+
return if is_a?(Gruff::SideStackedBar)
|
40
89
|
|
41
|
-
|
90
|
+
store.norm_data.each_with_index do |data_row, row_index|
|
91
|
+
data_row.points.each_with_index do |data_point, point_index|
|
92
|
+
group_spacing = @group_spacing * @scale * point_index
|
42
93
|
|
43
|
-
# Using the original
|
94
|
+
# Using the original calculations from the stacked bar chart
|
44
95
|
# to get the difference between
|
45
96
|
# part of the bart chart we wish to stack.
|
46
97
|
temp1 = @graph_left + (@graph_width - data_point * @graph_width - height[point_index])
|
@@ -48,91 +99,72 @@ class Gruff::SideBar < Gruff::Base
|
|
48
99
|
difference = temp2 - temp1
|
49
100
|
|
50
101
|
left_x = length[point_index] - 1
|
51
|
-
left_y = @graph_top + (
|
102
|
+
left_y = @graph_top + (bars_width * point_index) + (bar_width * row_index) + padding + group_spacing
|
52
103
|
right_x = left_x + difference
|
53
|
-
right_y = left_y +
|
104
|
+
right_y = left_y + bar_width * @bar_spacing
|
54
105
|
|
55
106
|
height[point_index] += (data_point * @graph_width)
|
56
107
|
|
57
|
-
|
108
|
+
rect_renderer = Gruff::Renderer::Rectangle.new(color: data_row.color)
|
109
|
+
rect_renderer.render(left_x, left_y, right_x, right_y)
|
58
110
|
|
59
111
|
# Calculate center based on bar_width and current row
|
60
112
|
|
61
113
|
if @use_data_label
|
62
|
-
label_center =
|
63
|
-
draw_label(label_center, row_index,
|
114
|
+
label_center = left_y + bar_width / 2
|
115
|
+
draw_label(label_center, row_index, store.norm_data[row_index].label)
|
64
116
|
else
|
65
|
-
label_center =
|
117
|
+
label_center = left_y + bars_width / 2
|
66
118
|
draw_label(label_center, point_index)
|
67
119
|
end
|
68
120
|
if @show_labels_for_bar_values
|
69
|
-
val = (@label_formatting || '%.2f') %
|
70
|
-
draw_value_label(right_x+40,
|
121
|
+
val = (@label_formatting || '%.2f') % store.data[row_index].points[point_index]
|
122
|
+
draw_value_label(right_x + 40, right_y - bar_width / 2, val.commify, true)
|
71
123
|
end
|
72
124
|
end
|
73
|
-
|
74
125
|
end
|
75
|
-
|
76
|
-
@d.draw(@base_image)
|
77
126
|
end
|
78
127
|
|
79
128
|
# Instead of base class version, draws vertical background lines and label
|
80
129
|
def draw_line_markers
|
81
|
-
|
82
130
|
return if @hide_line_markers
|
83
131
|
|
84
|
-
@d = @d.stroke_antialias false
|
85
|
-
|
86
132
|
# Draw horizontal line markers and annotate with numbers
|
87
|
-
|
88
|
-
@d = @d.stroke_width 1
|
89
|
-
number_of_lines = @marker_count || 5
|
133
|
+
number_of_lines = marker_count
|
90
134
|
number_of_lines = 1 if number_of_lines == 0
|
91
135
|
|
92
|
-
# TODO Round maximum marker value to a round number like 100, 0.1, 0.5, etc.
|
136
|
+
# TODO: Round maximum marker value to a round number like 100, 0.1, 0.5, etc.
|
93
137
|
increment = significant(@spread.to_f / number_of_lines)
|
94
138
|
(0..number_of_lines).each do |index|
|
95
|
-
|
96
139
|
line_diff = (@graph_right - @graph_left) / number_of_lines
|
97
140
|
x = @graph_right - (line_diff * index) - 1
|
98
|
-
|
141
|
+
|
142
|
+
line_renderer = Gruff::Renderer::Line.new(color: @marker_color, shadow_color: @marker_shadow_color)
|
143
|
+
line_renderer.render(x, @graph_bottom, x, @graph_top)
|
144
|
+
|
99
145
|
diff = index - number_of_lines
|
100
|
-
marker_label = diff.abs * increment +
|
146
|
+
marker_label = diff.abs * increment + minimum_value
|
101
147
|
|
102
148
|
unless @hide_line_numbers
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
@d.pointsize = scale_fontsize(@marker_font_size)
|
107
|
-
@d.gravity = CenterGravity
|
108
|
-
# TODO Center text over line
|
109
|
-
@d = @d.annotate_scaled(@base_image,
|
110
|
-
0, 0, # Width of box to draw text in
|
111
|
-
x, @graph_bottom + (LABEL_MARGIN * 2.0), # Coordinates of text
|
112
|
-
marker_label.to_s, @scale)
|
113
|
-
end # unless
|
114
|
-
@d = @d.stroke_antialias true
|
149
|
+
text_renderer = Gruff::Renderer::Text.new(marker_label, font: @font, size: @marker_font_size, color: @font_color)
|
150
|
+
text_renderer.add_to_render_queue(0, 0, x, @graph_bottom + LABEL_MARGIN, Magick::CenterGravity)
|
151
|
+
end
|
115
152
|
end
|
116
153
|
end
|
117
154
|
|
118
155
|
##
|
119
156
|
# Draw on the Y axis instead of the X
|
120
157
|
|
121
|
-
def draw_label(y_offset, index, label=nil)
|
122
|
-
|
123
|
-
lbl =
|
124
|
-
|
125
|
-
|
126
|
-
@
|
127
|
-
@d.font_weight = NormalWeight
|
128
|
-
@d.pointsize = scale_fontsize(@marker_font_size)
|
129
|
-
@d.gravity = EastGravity
|
130
|
-
@d = @d.annotate_scaled(@base_image,
|
131
|
-
1, 1,
|
132
|
-
-@graph_left + LABEL_MARGIN * 2.0, y_offset,
|
133
|
-
lbl, @scale)
|
134
|
-
@labels_seen[index] = 1
|
158
|
+
def draw_label(y_offset, index, label = nil)
|
159
|
+
draw_unique_label(index) do
|
160
|
+
lbl = @use_data_label ? label : @labels[index]
|
161
|
+
|
162
|
+
text_renderer = Gruff::Renderer::Text.new(lbl, font: @font, size: @marker_font_size, color: @font_color)
|
163
|
+
text_renderer.add_to_render_queue(@graph_left - LABEL_MARGIN, 1.0, 0.0, y_offset, Magick::EastGravity)
|
135
164
|
end
|
136
165
|
end
|
137
166
|
|
167
|
+
def calculate_spacing
|
168
|
+
@scale * @group_spacing * (column_count - 1)
|
169
|
+
end
|
138
170
|
end
|
@@ -1,97 +1,126 @@
|
|
1
|
-
|
2
|
-
require File.dirname(__FILE__) + '/side_bar'
|
3
|
-
require File.dirname(__FILE__) + '/stacked_mixin'
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
|
-
|
6
|
-
# New gruff graph type added to enable sideways stacking bar charts
|
3
|
+
#
|
4
|
+
# New gruff graph type added to enable sideways stacking bar charts
|
7
5
|
# (basically looks like a x/y flip of a standard stacking bar chart)
|
8
6
|
#
|
9
|
-
#
|
10
|
-
|
7
|
+
# Here's how to set up a Gruff::SideStackedBar.
|
8
|
+
#
|
9
|
+
# g = Gruff::SideStackedBar.new
|
10
|
+
# g.title = 'SideStackedBar Graph'
|
11
|
+
# g.labels = {
|
12
|
+
# 0 => '5/6',
|
13
|
+
# 1 => '5/15',
|
14
|
+
# 2 => '5/24',
|
15
|
+
# 3 => '5/30',
|
16
|
+
# }
|
17
|
+
# g.data :Art, [0, 5, 8, 15]
|
18
|
+
# g.data :Philosophy, [10, 3, 2, 8]
|
19
|
+
# g.data :Science, [2, 15, 8, 11]
|
20
|
+
# g.write('side_stacked_bar.png')
|
21
|
+
#
|
11
22
|
class Gruff::SideStackedBar < Gruff::SideBar
|
12
23
|
include StackedMixin
|
24
|
+
include BarValueLabelMixin
|
25
|
+
|
26
|
+
# Spacing factor applied between bars.
|
27
|
+
attr_writer :bar_spacing
|
28
|
+
|
29
|
+
# Number of pixels between bar segments.
|
30
|
+
attr_writer :segment_spacing
|
31
|
+
|
32
|
+
# Set the number output format for labels using sprintf.
|
33
|
+
# Default is +"%.2f"+.
|
34
|
+
attr_writer :label_formatting
|
35
|
+
|
36
|
+
# Output the values for the bars on a bar graph.
|
37
|
+
# Default is +false+.
|
38
|
+
attr_writer :show_labels_for_bar_values
|
39
|
+
|
40
|
+
# Prevent drawing of column labels left of a side stacked bar graph. Default is +false+.
|
41
|
+
attr_writer :hide_labels
|
42
|
+
|
43
|
+
def initialize_ivars
|
44
|
+
super
|
45
|
+
@bar_spacing = 0.9
|
46
|
+
@segment_spacing = 2.0
|
47
|
+
@label_formatting = nil
|
48
|
+
@show_labels_for_bar_values = false
|
49
|
+
@hide_labels = false
|
50
|
+
end
|
51
|
+
private :initialize_ivars
|
13
52
|
|
14
|
-
# Spacing factor applied between bars
|
15
|
-
attr_accessor :bar_spacing
|
16
|
-
|
17
53
|
def draw
|
18
54
|
@has_left_labels = true
|
19
|
-
|
55
|
+
calculate_maximum_by_stack
|
20
56
|
super
|
21
57
|
end
|
22
58
|
|
23
|
-
|
59
|
+
protected
|
60
|
+
|
61
|
+
def hide_labels?
|
62
|
+
@hide_labels
|
63
|
+
end
|
64
|
+
|
65
|
+
def hide_left_label_area?
|
66
|
+
hide_labels?
|
67
|
+
end
|
68
|
+
|
69
|
+
def hide_bottom_label_area?
|
70
|
+
@hide_line_markers
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
24
74
|
|
25
75
|
def draw_bars
|
26
76
|
# Setup spacing.
|
27
77
|
#
|
28
78
|
# Columns sit stacked.
|
29
|
-
@
|
79
|
+
bar_width = @graph_height / column_count.to_f
|
80
|
+
height = Array.new(column_count, 0)
|
81
|
+
length = Array.new(column_count, @graph_left)
|
82
|
+
padding = (bar_width * (1 - @bar_spacing)) / 2
|
83
|
+
bar_value_label = BarValueLabel.new(column_count, bar_width)
|
30
84
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
data_point * @graph_width -
|
47
|
-
height[point_index]) + 1
|
48
|
-
temp2 = @graph_left + @graph_width - height[point_index] - 1
|
49
|
-
difference = temp2 - temp1
|
50
|
-
|
51
|
-
@d = @d.fill data_row[DATA_COLOR_INDEX]
|
52
|
-
|
53
|
-
left_x = length[point_index] #+ 1
|
54
|
-
left_y = @graph_top + (@bar_width * point_index) + padding
|
55
|
-
right_x = left_x + difference
|
56
|
-
right_y = left_y + @bar_width * @bar_spacing
|
57
|
-
length[point_index] += difference
|
85
|
+
store.norm_data.each_with_index do |data_row, row_index|
|
86
|
+
data_row.points.each_with_index do |data_point, point_index|
|
87
|
+
## using the original calculations from the stacked bar chart to get the difference between
|
88
|
+
## part of the bart chart we wish to stack.
|
89
|
+
temp1 = @graph_left + (@graph_width -
|
90
|
+
data_point * @graph_width -
|
91
|
+
height[point_index]) + 1
|
92
|
+
temp2 = @graph_left + @graph_width - height[point_index] - 1
|
93
|
+
difference = temp2 - temp1
|
94
|
+
|
95
|
+
left_x = length[point_index]
|
96
|
+
left_y = @graph_top + (bar_width * point_index) + padding
|
97
|
+
right_x = left_x + difference - @segment_spacing
|
98
|
+
right_y = left_y + bar_width * @bar_spacing
|
99
|
+
length[point_index] += difference
|
58
100
|
height[point_index] += (data_point * @graph_width - 2)
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
end
|
64
|
-
|
101
|
+
|
102
|
+
bar_value_label.coordinates[point_index] = [left_x, left_y, right_x, right_y]
|
103
|
+
bar_value_label.values[point_index] += store.data[row_index].points[point_index]
|
104
|
+
|
65
105
|
# if a data point is 0 it can result in weird really thing lines
|
66
106
|
# that shouldn't even be there being drawn on top of the existing
|
67
107
|
# bar - this is bad
|
68
108
|
if data_point != 0
|
69
|
-
|
109
|
+
rect_renderer = Gruff::Renderer::Rectangle.new(color: data_row.color)
|
110
|
+
rect_renderer.render(left_x, left_y, right_x, right_y)
|
70
111
|
# Calculate center based on bar_width and current row
|
71
112
|
end
|
72
113
|
# we still need to draw the labels
|
73
114
|
# Calculate center based on bar_width and current row
|
74
|
-
label_center =
|
115
|
+
label_center = left_y + bar_width / 2
|
75
116
|
draw_label(label_center, point_index)
|
76
117
|
end
|
77
|
-
|
78
118
|
end
|
119
|
+
|
79
120
|
if @show_labels_for_bar_values
|
80
|
-
|
81
|
-
|
82
|
-
draw_value_label(data[:right_x]+40, (@graph_top + (((i+1) * @bar_width) - (@bar_width / 2)))-12, val.commify, true)
|
121
|
+
bar_value_label.prepare_sidebar_rendering(@label_formatting) do |x, y, text|
|
122
|
+
draw_value_label(x, y, text, true)
|
83
123
|
end
|
84
124
|
end
|
85
|
-
|
86
|
-
@d.draw(@base_image)
|
87
|
-
end
|
88
|
-
|
89
|
-
def larger_than_max?(data_point, index=0)
|
90
|
-
max(data_point, index) > @maximum_value
|
91
|
-
end
|
92
|
-
|
93
|
-
def max(data_point, index)
|
94
|
-
@data.inject(0) {|sum, item| sum + item[DATA_VALUES_INDEX][index]}
|
95
125
|
end
|
96
|
-
|
97
126
|
end
|