gruff 0.15.0 → 0.18.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +21 -5
- data/.gitignore +1 -0
- data/.rubocop.yml +0 -12
- data/CHANGELOG.md +52 -0
- data/README.md +14 -3
- data/gruff.gemspec +3 -4
- data/lib/gruff/accumulator_bar.rb +1 -1
- data/lib/gruff/area.rb +6 -4
- data/lib/gruff/bar.rb +53 -32
- data/lib/gruff/base.rb +297 -186
- data/lib/gruff/bezier.rb +4 -2
- data/lib/gruff/box.rb +180 -0
- data/lib/gruff/bubble.rb +99 -0
- data/lib/gruff/bullet.rb +5 -5
- data/lib/gruff/candlestick.rb +120 -0
- data/lib/gruff/dot.rb +11 -12
- data/lib/gruff/font.rb +3 -0
- data/lib/gruff/helper/bar_conversion.rb +6 -10
- data/lib/gruff/helper/bar_mixin.rb +25 -0
- data/lib/gruff/helper/bar_value_label.rb +24 -43
- data/lib/gruff/helper/stacked_mixin.rb +19 -1
- data/lib/gruff/histogram.rb +9 -6
- data/lib/gruff/line.rb +67 -43
- data/lib/gruff/mini/legend.rb +15 -11
- data/lib/gruff/net.rb +23 -18
- data/lib/gruff/patch/string.rb +1 -0
- data/lib/gruff/pie.rb +26 -12
- data/lib/gruff/renderer/circle.rb +3 -1
- data/lib/gruff/renderer/dash_line.rb +3 -2
- data/lib/gruff/renderer/dot.rb +28 -15
- data/lib/gruff/renderer/line.rb +1 -3
- data/lib/gruff/renderer/rectangle.rb +6 -2
- data/lib/gruff/renderer/renderer.rb +0 -4
- data/lib/gruff/renderer/text.rb +7 -1
- data/lib/gruff/scatter.rb +84 -81
- data/lib/gruff/side_bar.rb +64 -31
- data/lib/gruff/side_stacked_bar.rb +43 -55
- data/lib/gruff/spider.rb +52 -14
- data/lib/gruff/stacked_area.rb +18 -8
- data/lib/gruff/stacked_bar.rb +59 -29
- data/lib/gruff/store/xy_data.rb +8 -9
- data/lib/gruff/store/xy_pointsizes_data.rb +60 -0
- data/lib/gruff/version.rb +1 -1
- data/lib/gruff.rb +11 -12
- metadata +14 -11
- data/lib/gruff/scene.rb +0 -208
- data/lib/gruff/store/custom_data.rb +0 -36
data/lib/gruff/renderer/text.rb
CHANGED
@@ -25,6 +25,7 @@ module Gruff
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def render(width, height, x, y, gravity = Magick::NorthGravity)
|
28
|
+
@renderer.draw.push
|
28
29
|
@renderer.draw.rotation = @rotation if @rotation
|
29
30
|
@renderer.draw.fill = @font.color
|
30
31
|
@renderer.draw.stroke = 'transparent'
|
@@ -37,9 +38,11 @@ module Gruff
|
|
37
38
|
x, y,
|
38
39
|
@text, @renderer.scale)
|
39
40
|
@renderer.draw.rotation = -@rotation if @rotation
|
41
|
+
@renderer.draw.pop
|
40
42
|
end
|
41
43
|
|
42
44
|
def metrics
|
45
|
+
@renderer.draw.push
|
43
46
|
@renderer.draw.font = @font.file_path
|
44
47
|
@renderer.draw.font_weight = @font.weight
|
45
48
|
@renderer.draw.pointsize = @font.size
|
@@ -50,7 +53,10 @@ module Gruff
|
|
50
53
|
# So, in here, it just escape % in order to avoid SEGV.
|
51
54
|
text = @text.to_s.gsub(/(%+)/) { ('%' * Regexp.last_match(1).size * 2).to_s }
|
52
55
|
|
53
|
-
@renderer.draw.get_type_metrics(@renderer.image, text)
|
56
|
+
metrics = @renderer.draw.get_type_metrics(@renderer.image, text)
|
57
|
+
@renderer.draw.pop
|
58
|
+
|
59
|
+
metrics
|
54
60
|
end
|
55
61
|
end
|
56
62
|
end
|
data/lib/gruff/scatter.rb
CHANGED
@@ -24,24 +24,35 @@ class Gruff::Scatter < Gruff::Base
|
|
24
24
|
attr_writer :circle_radius
|
25
25
|
attr_writer :stroke_width
|
26
26
|
|
27
|
-
# Allow disabling the significant rounding when labeling the X axis.
|
28
|
-
# This is useful when working with a small range of high values (for example, a date range of months, while seconds as units).
|
29
|
-
attr_writer :disable_significant_rounding_x_axis
|
30
|
-
|
31
27
|
# Allow for vertical marker lines.
|
32
28
|
attr_writer :show_vertical_markers
|
33
29
|
|
34
|
-
# Allow using vertical labels in the X axis (and setting the label margin).
|
35
|
-
attr_writer :x_label_margin
|
36
|
-
attr_writer :use_vertical_x_labels
|
37
|
-
|
38
30
|
# Allow enabling vertical lines. When you have a lot of data, they can work great.
|
39
|
-
# @deprecated Please use
|
31
|
+
# @deprecated Please use {#show_vertical_markers=} instead.
|
40
32
|
def enable_vertical_line_markers=(value)
|
41
33
|
warn '#enable_vertical_line_markers= is deprecated. Please use `show_vertical_markers` attribute instead'
|
42
34
|
@show_vertical_markers = value
|
43
35
|
end
|
44
36
|
|
37
|
+
# Allow using vertical labels in the X axis.
|
38
|
+
# @deprecated Please use {Gruff::Base#label_rotation=} instead.
|
39
|
+
def use_vertical_x_labels=(_value)
|
40
|
+
warn '#use_vertical_x_labels= is deprecated. It is no longer effective. Please use `#label_rotation=` instead'
|
41
|
+
end
|
42
|
+
|
43
|
+
# Allow using vertical labels in the X axis (and setting the label margin).
|
44
|
+
# @deprecated
|
45
|
+
def x_label_margin=(_value)
|
46
|
+
warn '#x_label_margin= is deprecated. It is no longer effective.'
|
47
|
+
end
|
48
|
+
|
49
|
+
# Allow disabling the significant rounding when labeling the X axis.
|
50
|
+
# This is useful when working with a small range of high values (for example, a date range of months, while seconds as units).
|
51
|
+
# @deprecated
|
52
|
+
def disable_significant_rounding_x_axis=(_value)
|
53
|
+
warn '#disable_significant_rounding_x_axis= is deprecated. It is no longer effective.'
|
54
|
+
end
|
55
|
+
|
45
56
|
# The first parameter is the name of the dataset. The next two are the
|
46
57
|
# x and y axis data points contain in their own array in that respective
|
47
58
|
# order. The final parameter is the color.
|
@@ -55,11 +66,10 @@ class Gruff::Scatter < Gruff::Base
|
|
55
66
|
# @note If you want to use a preset theme, you must set it before calling {#data}.
|
56
67
|
#
|
57
68
|
# @param name [String, Symbol] containing the name of the dataset.
|
58
|
-
# @param x_data_points [Array] An Array of
|
59
|
-
# @param y_data_points [Array] An Array of
|
69
|
+
# @param x_data_points [Array] An Array of x-axis data points.
|
70
|
+
# @param y_data_points [Array] An Array of y-axis data points.
|
60
71
|
# @param color [String] The hex string for the color of the dataset. Defaults to nil.
|
61
72
|
#
|
62
|
-
#
|
63
73
|
# @raise [ArgumentError] Data points contain nil values.
|
64
74
|
# This error will get raised if either the x or y axis data points array
|
65
75
|
# contains a +nil+ value. The graph will not make an assumption
|
@@ -88,7 +98,7 @@ class Gruff::Scatter < Gruff::Base
|
|
88
98
|
raise ArgumentError, 'x_data_points.length != y_data_points.length!' if x_data_points.length != y_data_points.length
|
89
99
|
|
90
100
|
# Call the existing data routine for the x/y axis data
|
91
|
-
store.add(name, y_data_points, color
|
101
|
+
store.add(name, x_data_points, y_data_points, color)
|
92
102
|
end
|
93
103
|
|
94
104
|
alias dataxy data
|
@@ -102,56 +112,55 @@ private
|
|
102
112
|
def initialize_attributes
|
103
113
|
super
|
104
114
|
|
105
|
-
@baseline_x_color = @baseline_y_color = 'red'
|
106
|
-
@baseline_x_value = @baseline_y_value = nil
|
107
115
|
@circle_radius = nil
|
108
|
-
@disable_significant_rounding_x_axis = false
|
109
116
|
@show_vertical_markers = false
|
110
117
|
@marker_x_count = nil
|
111
118
|
@maximum_x_value = @minimum_x_value = nil
|
112
119
|
@stroke_width = nil
|
113
|
-
@use_vertical_x_labels = false
|
114
|
-
@x_label_margin = nil
|
115
120
|
end
|
116
121
|
|
117
|
-
def
|
118
|
-
|
119
|
-
|
122
|
+
def setup_drawing
|
123
|
+
@center_labels_over_point = false
|
124
|
+
super
|
125
|
+
end
|
120
126
|
|
127
|
+
def setup_data
|
128
|
+
# TODO: Need to get x-axis labels working. Current behavior will be to not allow.
|
129
|
+
@labels = {}
|
130
|
+
|
131
|
+
# Update the global min/max values for the x data
|
132
|
+
@maximum_x_value = (@maximum_x_value || store.max_x).to_f
|
133
|
+
@minimum_x_value = (@minimum_x_value || store.min_x).to_f
|
134
|
+
|
135
|
+
if @x_axis_increment
|
136
|
+
# TODO: Make this work for negative values
|
137
|
+
@maximum_x_value = [@maximum_x_value.ceil, @x_axis_increment.to_f].max
|
138
|
+
@minimum_x_value = @minimum_x_value.floor
|
139
|
+
end
|
140
|
+
|
141
|
+
super
|
142
|
+
end
|
143
|
+
|
144
|
+
def draw_graph
|
121
145
|
store.norm_data.each do |data_row|
|
122
146
|
data_row.coordinates.each do |x_value, y_value|
|
123
147
|
next if y_value.nil? || x_value.nil?
|
124
148
|
|
125
|
-
new_x =
|
126
|
-
new_y = @
|
149
|
+
new_x = @graph_left + (x_value * @graph_width)
|
150
|
+
new_y = @graph_bottom - (y_value * @graph_height)
|
127
151
|
|
128
152
|
# Reset each time to avoid thin-line errors
|
129
|
-
stroke_width = @stroke_width || clip_value_if_greater_than(@columns / (store.norm_data.first[1].size * 4), 5.0)
|
153
|
+
stroke_width = @stroke_width || clip_value_if_greater_than(@columns / (store.norm_data.first[1].size * 4.0), 5.0)
|
130
154
|
circle_radius = @circle_radius || clip_value_if_greater_than(@columns / (store.norm_data.first[1].size * 2.5), 5.0)
|
131
155
|
Gruff::Renderer::Circle.new(renderer, color: data_row.color, width: stroke_width).render(new_x, new_y, new_x - circle_radius, new_y)
|
132
156
|
end
|
133
157
|
end
|
134
158
|
end
|
135
159
|
|
136
|
-
def setup_data
|
137
|
-
# Update the global min/max values for the x data
|
138
|
-
@maximum_x_value ||= store.max_x
|
139
|
-
@minimum_x_value ||= store.min_x
|
140
|
-
|
141
|
-
super
|
142
|
-
end
|
143
|
-
|
144
|
-
def setup_drawing
|
145
|
-
# TODO: Need to get x-axis labels working. Current behavior will be to not allow.
|
146
|
-
@labels = {}
|
147
|
-
|
148
|
-
super
|
149
|
-
end
|
150
|
-
|
151
160
|
def calculate_spread
|
152
161
|
super
|
153
162
|
@x_spread = @maximum_x_value.to_f - @minimum_x_value.to_f
|
154
|
-
@x_spread = @x_spread > 0 ? @x_spread : 1
|
163
|
+
@x_spread = @x_spread > 0 ? @x_spread : 1.0
|
155
164
|
end
|
156
165
|
|
157
166
|
def normalize
|
@@ -165,57 +174,51 @@ private
|
|
165
174
|
super
|
166
175
|
return if @hide_line_markers
|
167
176
|
|
168
|
-
|
169
|
-
# TODO: Do the same for larger numbers...100, 75, 50, 25
|
170
|
-
if @marker_x_count.nil?
|
171
|
-
(3..7).each do |lines|
|
172
|
-
if @x_spread % lines == 0.0
|
173
|
-
@marker_x_count = lines
|
174
|
-
break
|
175
|
-
end
|
176
|
-
end
|
177
|
-
@marker_x_count ||= 4
|
178
|
-
end
|
179
|
-
@x_increment = @x_spread > 0 ? (@x_spread / @marker_x_count) : 1
|
180
|
-
unless @disable_significant_rounding_x_axis
|
181
|
-
@x_increment = significant(@x_increment)
|
182
|
-
end
|
183
|
-
else
|
184
|
-
# TODO: Make this work for negative values
|
185
|
-
@maximum_x_value = [@maximum_x_value.ceil, @x_axis_increment].max
|
186
|
-
@minimum_x_value = @minimum_x_value.floor
|
187
|
-
calculate_spread
|
188
|
-
normalize
|
189
|
-
|
190
|
-
@marker_x_count = (@x_spread / @x_axis_increment).to_i
|
191
|
-
@x_increment = @x_axis_increment
|
192
|
-
end
|
193
|
-
increment_x_scaled = @graph_width.to_f / (@x_spread / @x_increment)
|
177
|
+
increment_x_scaled = (@graph_width / (@x_spread / x_increment)).to_f
|
194
178
|
|
195
179
|
# Draw vertical line markers and annotate with numbers
|
196
|
-
(0
|
180
|
+
(0..marker_x_count).each do |index|
|
197
181
|
# TODO: Fix the vertical lines, and enable them by default. Not pretty when they don't match up with top y-axis line
|
198
182
|
if @show_vertical_markers
|
199
|
-
|
200
|
-
|
201
|
-
line_renderer = Gruff::Renderer::Line.new(renderer, color: @marker_color, shadow_color: @marker_shadow_color)
|
202
|
-
line_renderer.render(x, @graph_top, x, @graph_bottom)
|
183
|
+
draw_marker_vertical_line(@graph_left + (index * increment_x_scaled))
|
203
184
|
end
|
204
185
|
|
205
186
|
unless @hide_line_numbers
|
206
|
-
marker_label = BigDecimal(index.to_s) * BigDecimal(
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
text_renderer.add_to_render_queue(1.0, 1.0, x_offset, y_offset)
|
187
|
+
marker_label = (BigDecimal(index.to_s) * BigDecimal(x_increment.to_s)) + BigDecimal(@minimum_x_value.to_s)
|
188
|
+
label = x_axis_label(marker_label, x_increment)
|
189
|
+
x = @graph_left + (increment_x_scaled * index)
|
190
|
+
y = @graph_bottom
|
191
|
+
x_offset, y_offset = calculate_label_offset(@marker_font, label, LABEL_MARGIN, @label_rotation)
|
192
|
+
|
193
|
+
draw_label_at(1.0, 1.0, x + x_offset, y + y_offset, label, rotation: @label_rotation)
|
214
194
|
end
|
215
195
|
end
|
216
196
|
end
|
217
197
|
|
218
|
-
def
|
219
|
-
|
198
|
+
def marker_x_count
|
199
|
+
# TODO: Do the same for larger numbers...100, 75, 50, 25
|
200
|
+
@marker_x_count ||= begin
|
201
|
+
if @x_axis_increment.nil?
|
202
|
+
count = nil
|
203
|
+
(3..7).each do |lines|
|
204
|
+
if @x_spread % lines == 0.0
|
205
|
+
count = lines and break
|
206
|
+
end
|
207
|
+
end
|
208
|
+
count || 4
|
209
|
+
else
|
210
|
+
(@x_spread / @x_axis_increment).to_i
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def x_increment
|
216
|
+
@x_increment ||= begin
|
217
|
+
if @x_axis_increment.nil?
|
218
|
+
@x_spread > 0 ? significant(@x_spread / marker_x_count) : 1.0
|
219
|
+
else
|
220
|
+
@x_axis_increment.to_f
|
221
|
+
end
|
222
|
+
end
|
220
223
|
end
|
221
224
|
end
|
data/lib/gruff/side_bar.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'helper/bar_mixin'
|
4
|
+
|
3
5
|
# Graph with individual horizontal bars instead of vertical bars.
|
4
6
|
#
|
5
7
|
# Here's how to set up a Gruff::SideBar.
|
@@ -19,6 +21,8 @@
|
|
19
21
|
# g.write('sidebar.png')
|
20
22
|
#
|
21
23
|
class Gruff::SideBar < Gruff::Base
|
24
|
+
include BarMixin
|
25
|
+
|
22
26
|
# Spacing factor applied between bars.
|
23
27
|
attr_writer :bar_spacing
|
24
28
|
|
@@ -36,6 +40,15 @@ class Gruff::SideBar < Gruff::Base
|
|
36
40
|
# Prevent drawing of column labels left of a side bar graph. Default is +false+.
|
37
41
|
attr_writer :hide_labels
|
38
42
|
|
43
|
+
# Value to avoid completely overwriting the coordinate axis
|
44
|
+
AXIS_MARGIN = 0.5
|
45
|
+
private_constant :AXIS_MARGIN
|
46
|
+
|
47
|
+
def initialize(*)
|
48
|
+
super
|
49
|
+
@has_left_labels = true
|
50
|
+
end
|
51
|
+
|
39
52
|
# With Side Bars use the data label for the marker value to the left of the bar.
|
40
53
|
# @deprecated
|
41
54
|
def use_data_label=(_value)
|
@@ -51,7 +64,6 @@ private
|
|
51
64
|
@label_formatting = nil
|
52
65
|
@show_labels_for_bar_values = false
|
53
66
|
@hide_labels = false
|
54
|
-
@has_left_labels = true
|
55
67
|
end
|
56
68
|
|
57
69
|
def hide_labels?
|
@@ -59,20 +71,37 @@ private
|
|
59
71
|
end
|
60
72
|
|
61
73
|
def hide_left_label_area?
|
62
|
-
hide_labels?
|
74
|
+
hide_labels? && @y_axis_label.nil?
|
63
75
|
end
|
64
76
|
|
65
77
|
def hide_bottom_label_area?
|
66
|
-
@hide_line_markers
|
78
|
+
@hide_line_markers && @x_axis_label.nil? && @legend_at_bottom == false
|
67
79
|
end
|
68
80
|
|
69
|
-
|
70
|
-
|
81
|
+
def setup_graph_measurements
|
82
|
+
super
|
83
|
+
return if @hide_line_markers
|
84
|
+
|
85
|
+
if @show_labels_for_bar_values
|
86
|
+
if maximum_value >= 0
|
87
|
+
_, metrics = Gruff::BarValueLabel.metrics(maximum_value, @label_formatting, proc_text_metrics)
|
88
|
+
@graph_right -= metrics.width
|
89
|
+
end
|
90
|
+
|
91
|
+
if minimum_value < 0
|
92
|
+
_, metrics = Gruff::BarValueLabel.metrics(minimum_value, @label_formatting, proc_text_metrics)
|
93
|
+
width = metrics.width + LABEL_MARGIN
|
94
|
+
@graph_left += width - @graph_left if width > @graph_left
|
95
|
+
end
|
96
|
+
|
97
|
+
@graph_width = @graph_right - @graph_left
|
98
|
+
end
|
99
|
+
end
|
71
100
|
|
72
101
|
def draw_graph
|
73
102
|
# Setup spacing.
|
74
103
|
#
|
75
|
-
bars_width = (@graph_height - calculate_spacing) / column_count
|
104
|
+
bars_width = (@graph_height - calculate_spacing) / column_count
|
76
105
|
bar_width = bars_width / store.length
|
77
106
|
padding = (bar_width * (1 - @bar_spacing)) / 2
|
78
107
|
|
@@ -82,30 +111,32 @@ private
|
|
82
111
|
minimum_value: minimum_value, maximum_value: maximum_value, spread: @spread
|
83
112
|
)
|
84
113
|
|
85
|
-
|
86
|
-
data_row.points.each_with_index do |data_point, point_index|
|
87
|
-
group_spacing = @group_spacing * @scale * point_index
|
88
|
-
|
89
|
-
left_y = @graph_top + (bars_width * point_index) + (bar_width * row_index) + padding + group_spacing
|
90
|
-
right_y = left_y + bar_width * @bar_spacing
|
114
|
+
group_left_y = @graph_top
|
91
115
|
|
92
|
-
|
116
|
+
normalized_group_bars.each_with_index do |group_bars, group_index|
|
117
|
+
right_y = 0
|
118
|
+
group_bars.each_with_index do |bar, index|
|
119
|
+
left_y = group_left_y + (bar_width * index) + padding
|
120
|
+
right_y = left_y + (bar_width * @bar_spacing)
|
93
121
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
122
|
+
bottom_x, top_x = conversion.get_top_bottom_scaled(bar.point).sort
|
123
|
+
if bar.point != 0
|
124
|
+
rect_renderer = Gruff::Renderer::Rectangle.new(renderer, color: bar.color)
|
125
|
+
rect_renderer.render(bottom_x + AXIS_MARGIN, left_y, top_x, right_y)
|
126
|
+
end
|
99
127
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
bar_value_label.prepare_rendering(@label_formatting, bar_width) do |x, y, text|
|
105
|
-
draw_value_label(x, y, text)
|
128
|
+
if @show_labels_for_bar_values && bar.value
|
129
|
+
bar_value_label = Gruff::BarValueLabel::SideBar.new([bottom_x, left_y, top_x, right_y], bar.value)
|
130
|
+
bar_value_label.prepare_rendering(@label_formatting, proc_text_metrics) do |x, y, text, text_width, _text_height|
|
131
|
+
draw_value_label(text_width, bar_width * @bar_spacing, x, y, text)
|
106
132
|
end
|
107
133
|
end
|
108
134
|
end
|
135
|
+
|
136
|
+
label_center = group_left_y + (bars_width / 2.0)
|
137
|
+
draw_label(label_center, group_index)
|
138
|
+
|
139
|
+
group_left_y = right_y + padding + @group_spacing
|
109
140
|
end
|
110
141
|
end
|
111
142
|
|
@@ -118,17 +149,15 @@ private
|
|
118
149
|
number_of_lines = 1 if number_of_lines == 0
|
119
150
|
|
120
151
|
# TODO: Round maximum marker value to a round number like 100, 0.1, 0.5, etc.
|
121
|
-
increment = significant(@spread
|
152
|
+
increment = significant(@spread / number_of_lines)
|
122
153
|
(0..number_of_lines).each do |index|
|
123
154
|
line_diff = (@graph_right - @graph_left) / number_of_lines
|
124
155
|
x = @graph_right - (line_diff * index) - 1
|
125
|
-
|
126
|
-
line_renderer = Gruff::Renderer::Line.new(renderer, color: @marker_color, shadow_color: @marker_shadow_color)
|
127
|
-
line_renderer.render(x, @graph_bottom, x, @graph_top)
|
156
|
+
draw_marker_vertical_line(x)
|
128
157
|
|
129
158
|
unless @hide_line_numbers
|
130
159
|
diff = index - number_of_lines
|
131
|
-
marker_label = BigDecimal(diff.abs.to_s) * BigDecimal(increment.to_s) + BigDecimal(minimum_value.to_s)
|
160
|
+
marker_label = (BigDecimal(diff.abs.to_s) * BigDecimal(increment.to_s)) + BigDecimal(minimum_value.to_s)
|
132
161
|
label = x_axis_label(marker_label, @increment)
|
133
162
|
text_renderer = Gruff::Renderer::Text.new(renderer, label, font: @marker_font)
|
134
163
|
text_renderer.add_to_render_queue(0, 0, x, @graph_bottom + LABEL_MARGIN, Magick::CenterGravity)
|
@@ -141,11 +170,15 @@ private
|
|
141
170
|
|
142
171
|
def draw_label(y_offset, index)
|
143
172
|
draw_unique_label(index) do
|
144
|
-
draw_label_at(@graph_left - LABEL_MARGIN, 1.0, 0.0, y_offset, @labels[index], Magick::EastGravity)
|
173
|
+
draw_label_at(@graph_left - LABEL_MARGIN, 1.0, 0.0, y_offset, @labels[index], gravity: Magick::EastGravity)
|
145
174
|
end
|
146
175
|
end
|
147
176
|
|
148
177
|
def calculate_spacing
|
149
|
-
@
|
178
|
+
@group_spacing * (column_count - 1)
|
179
|
+
end
|
180
|
+
|
181
|
+
def proc_text_metrics
|
182
|
+
->(text) { text_metrics(@marker_font, text) }
|
150
183
|
end
|
151
184
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'helper/stacked_mixin'
|
4
|
+
|
3
5
|
#
|
4
6
|
# New gruff graph type added to enable sideways stacking bar charts
|
5
7
|
# (basically looks like a x/y flip of a standard stacking bar chart)
|
@@ -39,6 +41,11 @@ class Gruff::SideStackedBar < Gruff::SideBar
|
|
39
41
|
# Prevent drawing of column labels left of a side stacked bar graph. Default is +false+.
|
40
42
|
attr_writer :hide_labels
|
41
43
|
|
44
|
+
def initialize(*)
|
45
|
+
super
|
46
|
+
@has_left_labels = true
|
47
|
+
end
|
48
|
+
|
42
49
|
private
|
43
50
|
|
44
51
|
def initialize_attributes
|
@@ -48,7 +55,7 @@ private
|
|
48
55
|
@label_formatting = nil
|
49
56
|
@show_labels_for_bar_values = false
|
50
57
|
@hide_labels = false
|
51
|
-
@
|
58
|
+
@minimum_value = 0.0
|
52
59
|
end
|
53
60
|
|
54
61
|
def setup_data
|
@@ -56,67 +63,48 @@ private
|
|
56
63
|
super
|
57
64
|
end
|
58
65
|
|
59
|
-
def hide_labels?
|
60
|
-
@hide_labels
|
61
|
-
end
|
62
|
-
|
63
|
-
def hide_left_label_area?
|
64
|
-
hide_labels?
|
65
|
-
end
|
66
|
-
|
67
|
-
def hide_bottom_label_area?
|
68
|
-
@hide_line_markers
|
69
|
-
end
|
70
|
-
|
71
66
|
def draw_graph
|
72
67
|
# Setup spacing.
|
73
68
|
#
|
74
69
|
# Columns sit stacked.
|
75
|
-
bar_width = @graph_height / column_count
|
76
|
-
height = Array.new(column_count, 0)
|
77
|
-
length = Array.new(column_count, @graph_left)
|
70
|
+
bar_width = @graph_height / column_count
|
78
71
|
padding = (bar_width * (1 - @bar_spacing)) / 2
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
if data_point != 0
|
106
|
-
rect_renderer = Gruff::Renderer::Rectangle.new(renderer, color: data_row.color)
|
107
|
-
rect_renderer.render(left_x, left_y, right_x, right_y)
|
108
|
-
# Calculate center based on bar_width and current row
|
109
|
-
end
|
110
|
-
# we still need to draw the labels
|
111
|
-
# Calculate center based on bar_width and current row
|
112
|
-
label_center = left_y + bar_width / 2
|
113
|
-
draw_label(label_center, point_index)
|
72
|
+
|
73
|
+
# Setup the BarConversion Object
|
74
|
+
conversion = Gruff::BarConversion.new(
|
75
|
+
top: @graph_right, bottom: @graph_left,
|
76
|
+
minimum_value: minimum_value, maximum_value: maximum_value, spread: @spread
|
77
|
+
)
|
78
|
+
|
79
|
+
proc_text_metrics = ->(text) { text_metrics(@marker_font, text) }
|
80
|
+
|
81
|
+
normalized_stacked_bars.each_with_index do |stacked_bars, stacked_index|
|
82
|
+
total = 0
|
83
|
+
left_y = @graph_top + (bar_width * stacked_index) + padding
|
84
|
+
right_y = left_y + (bar_width * @bar_spacing)
|
85
|
+
|
86
|
+
top_x = 0
|
87
|
+
stacked_bars.each do |bar|
|
88
|
+
next if bar.point == 0
|
89
|
+
|
90
|
+
bottom_x, = conversion.get_top_bottom_scaled(total)
|
91
|
+
bottom_x += @segment_spacing
|
92
|
+
top_x, = conversion.get_top_bottom_scaled(total + bar.point)
|
93
|
+
|
94
|
+
rect_renderer = Gruff::Renderer::Rectangle.new(renderer, color: bar.color)
|
95
|
+
rect_renderer.render(bottom_x, left_y, top_x, right_y)
|
96
|
+
|
97
|
+
total += bar.point
|
114
98
|
end
|
115
|
-
end
|
116
99
|
|
117
|
-
|
118
|
-
|
119
|
-
|
100
|
+
label_center = left_y + (bar_width / 2.0)
|
101
|
+
draw_label(label_center, stacked_index)
|
102
|
+
|
103
|
+
if @show_labels_for_bar_values
|
104
|
+
bar_value_label = Gruff::BarValueLabel::SideBar.new([@graph_left, left_y, top_x, right_y], stacked_bars.sum(&:value))
|
105
|
+
bar_value_label.prepare_rendering(@label_formatting, proc_text_metrics) do |x, y, text, text_width, _text_height|
|
106
|
+
draw_value_label(text_width, bar_width * @bar_spacing, x, y, text)
|
107
|
+
end
|
120
108
|
end
|
121
109
|
end
|
122
110
|
end
|