gruff 0.14.0 → 0.17.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 +28 -12
- data/.gitignore +1 -0
- data/.rubocop.yml +20 -24
- data/CHANGELOG.md +52 -0
- data/README.md +10 -3
- data/gruff.gemspec +9 -10
- data/lib/gruff/accumulator_bar.rb +1 -1
- data/lib/gruff/area.rb +6 -4
- data/lib/gruff/bar.rb +53 -31
- data/lib/gruff/base.rb +292 -184
- data/lib/gruff/bezier.rb +4 -2
- data/lib/gruff/box_plot.rb +180 -0
- data/lib/gruff/bullet.rb +6 -6
- 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 -40
- data/lib/gruff/helper/stacked_mixin.rb +19 -1
- data/lib/gruff/histogram.rb +9 -5
- data/lib/gruff/line.rb +49 -48
- data/lib/gruff/mini/legend.rb +11 -11
- data/lib/gruff/net.rb +23 -18
- data/lib/gruff/patch/rmagick.rb +0 -1
- data/lib/gruff/patch/string.rb +1 -0
- data/lib/gruff/pie.rb +26 -12
- 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 +4 -8
- data/lib/gruff/renderer/text.rb +7 -1
- data/lib/gruff/scatter.rb +64 -56
- data/lib/gruff/side_bar.rb +64 -30
- data/lib/gruff/side_stacked_bar.rb +43 -54
- data/lib/gruff/spider.rb +52 -18
- data/lib/gruff/stacked_area.rb +18 -8
- data/lib/gruff/stacked_bar.rb +59 -29
- data/lib/gruff/store/xy_data.rb +2 -0
- data/lib/gruff/version.rb +1 -1
- data/lib/gruff.rb +67 -58
- metadata +22 -21
- data/.rubocop_todo.yml +0 -116
- data/lib/gruff/scene.rb +0 -200
- data/lib/gruff/store/custom_data.rb +0 -36
data/lib/gruff/bezier.rb
CHANGED
@@ -22,15 +22,17 @@ class Gruff::Bezier < Gruff::Base
|
|
22
22
|
private
|
23
23
|
|
24
24
|
def draw_graph
|
25
|
-
x_increment = @graph_width / (column_count - 1)
|
25
|
+
x_increment = @graph_width / (column_count - 1)
|
26
26
|
|
27
27
|
store.norm_data.each do |data_row|
|
28
|
+
next if data_row[1].empty?
|
29
|
+
|
28
30
|
poly_points = []
|
29
31
|
|
30
32
|
data_row[1].each_with_index do |data_point, index|
|
31
33
|
# Use incremented x and scaled y
|
32
34
|
new_x = @graph_left + (x_increment * index)
|
33
|
-
new_y = @graph_top + (@graph_height - data_point * @graph_height)
|
35
|
+
new_y = @graph_top + (@graph_height - (data_point * @graph_height))
|
34
36
|
|
35
37
|
if index == 0 && RUBY_PLATFORM != 'java'
|
36
38
|
poly_points << new_x
|
@@ -0,0 +1,180 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Here's how to set up a Gruff::BoxPlot.
|
5
|
+
#
|
6
|
+
# g = Gruff::BoxPlot.new
|
7
|
+
# g.data "A", [2, 3, 5, 6, 8, 10, 11, 15, 17, 20, 28, 29, 33, 34, 45, 46, 49, 61]
|
8
|
+
# g.data "B", [3, 4, 34, 35, 38, 39, 45, 60, 61, 69, 80, 130]
|
9
|
+
# g.data "C", [4, 40, 41, 46, 57, 64, 77, 76, 79, 78, 99, 153]
|
10
|
+
# g.write("box_plot.png")
|
11
|
+
#
|
12
|
+
class Gruff::BoxPlot < Gruff::Base
|
13
|
+
# Specifies the filling opacity in area graph. Default is +0.2+.
|
14
|
+
attr_writer :fill_opacity
|
15
|
+
|
16
|
+
# Specifies the stroke width in line. Default is +3.0+.
|
17
|
+
attr_writer :stroke_width
|
18
|
+
|
19
|
+
# Can be used to adjust the spaces between the bars.
|
20
|
+
# Accepts values between 0.00 and 1.00 where 0.00 means no spacing at all
|
21
|
+
# and 1 means that each bars' width is nearly 0 (so each bar is a simple
|
22
|
+
# line with no x dimension).
|
23
|
+
#
|
24
|
+
# Default value is +0.8+.
|
25
|
+
def spacing_factor=(space_percent)
|
26
|
+
raise ArgumentError, 'spacing_factor must be between 0.00 and 1.00' unless (space_percent >= 0) && (space_percent <= 1)
|
27
|
+
|
28
|
+
@spacing_factor = (1 - space_percent)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def initialize_attributes
|
34
|
+
super
|
35
|
+
@fill_opacity = 0.2
|
36
|
+
@stroke_width = 3.0
|
37
|
+
@spacing_factor = 0.8
|
38
|
+
end
|
39
|
+
|
40
|
+
def draw_graph
|
41
|
+
# Setup the BarConversion Object
|
42
|
+
conversion = Gruff::BarConversion.new(
|
43
|
+
top: @graph_top, bottom: @graph_bottom,
|
44
|
+
minimum_value: minimum_value, maximum_value: maximum_value, spread: @spread
|
45
|
+
)
|
46
|
+
|
47
|
+
width = (@graph_width - calculate_spacing) / column_count
|
48
|
+
bar_width = width * @spacing_factor
|
49
|
+
padding = width - bar_width
|
50
|
+
|
51
|
+
normalized_boxes.each_with_index do |box, index|
|
52
|
+
next if box.points.empty?
|
53
|
+
|
54
|
+
left_x = @graph_left + (width * index) + (padding / 2.0)
|
55
|
+
right_x = left_x + bar_width
|
56
|
+
center_x = (left_x + right_x) / 2.0
|
57
|
+
|
58
|
+
first_y, = conversion.get_top_bottom_scaled(box.first_quartile)
|
59
|
+
third_y, = conversion.get_top_bottom_scaled(box.third_quartile)
|
60
|
+
Gruff::Renderer::Rectangle.new(renderer, color: box.color, width: @stroke_width, opacity: @fill_opacity)
|
61
|
+
.render(left_x, first_y, right_x, third_y)
|
62
|
+
|
63
|
+
median_y, = conversion.get_top_bottom_scaled(box.median)
|
64
|
+
Gruff::Renderer::Line.new(renderer, color: box.color, width: @stroke_width * 2).render(left_x, median_y, right_x, median_y)
|
65
|
+
|
66
|
+
minmax_left_x = left_x + (bar_width / 4.0)
|
67
|
+
minmax_right_x = right_x - (bar_width / 4.0)
|
68
|
+
min_y, = conversion.get_top_bottom_scaled(box.lower_whisker)
|
69
|
+
Gruff::Renderer::Line.new(renderer, color: box.color, width: @stroke_width).render(minmax_left_x, min_y, minmax_right_x, min_y)
|
70
|
+
Gruff::Renderer::DashLine.new(renderer, color: box.color, width: @stroke_width, dasharray: [@stroke_width, @stroke_width * 2])
|
71
|
+
.render(center_x, min_y, center_x, first_y)
|
72
|
+
|
73
|
+
max_y, = conversion.get_top_bottom_scaled(box.upper_whisker)
|
74
|
+
Gruff::Renderer::Line.new(renderer, color: box.color, width: @stroke_width).render(minmax_left_x, max_y, minmax_right_x, max_y)
|
75
|
+
Gruff::Renderer::DashLine.new(renderer, color: box.color, width: @stroke_width, dasharray: [@stroke_width, @stroke_width * 2])
|
76
|
+
.render(center_x, max_y, center_x, third_y)
|
77
|
+
|
78
|
+
box.lower_outliers.each do |outlier|
|
79
|
+
outlier_y, = conversion.get_top_bottom_scaled(outlier)
|
80
|
+
Gruff::Renderer::Dot.new(renderer, :circle, color: box.color, opacity: @fill_opacity).render(center_x, outlier_y, @stroke_width * 2)
|
81
|
+
end
|
82
|
+
|
83
|
+
box.upper_outliers.each do |outlier|
|
84
|
+
outlier_y, = conversion.get_top_bottom_scaled(outlier)
|
85
|
+
Gruff::Renderer::Dot.new(renderer, :circle, color: box.color, opacity: @fill_opacity).render(center_x, outlier_y, @stroke_width * 2)
|
86
|
+
end
|
87
|
+
|
88
|
+
draw_label(center_x, index)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def normalized_boxes
|
93
|
+
@normalized_boxes ||= store.norm_data.map { |data| Gruff::BoxPlot::BoxData.new(data.label, data.points, data.color) }
|
94
|
+
end
|
95
|
+
|
96
|
+
def column_count
|
97
|
+
normalized_boxes.size
|
98
|
+
end
|
99
|
+
|
100
|
+
def calculate_spacing
|
101
|
+
@scale * (column_count - 1)
|
102
|
+
end
|
103
|
+
|
104
|
+
# @private
|
105
|
+
class BoxData < Struct.new(:label, :points, :color)
|
106
|
+
def initialize(label, points, color)
|
107
|
+
super(label, points.sort, color)
|
108
|
+
end
|
109
|
+
|
110
|
+
def min
|
111
|
+
points.first || 0
|
112
|
+
end
|
113
|
+
|
114
|
+
def max
|
115
|
+
points.last || 0
|
116
|
+
end
|
117
|
+
|
118
|
+
def min_whisker
|
119
|
+
[min, first_quartile - (1.5 * interquartile_range)].max
|
120
|
+
end
|
121
|
+
|
122
|
+
def max_whisker
|
123
|
+
[max, third_quartile + (1.5 * interquartile_range)].min
|
124
|
+
end
|
125
|
+
|
126
|
+
def upper_whisker
|
127
|
+
max = max_whisker
|
128
|
+
points.select { |point| point <= max }.max
|
129
|
+
end
|
130
|
+
|
131
|
+
def lower_whisker
|
132
|
+
min = min_whisker
|
133
|
+
points.select { |point| point >= min }.min
|
134
|
+
end
|
135
|
+
|
136
|
+
def median
|
137
|
+
if points.size.zero?
|
138
|
+
0
|
139
|
+
elsif points.size.odd?
|
140
|
+
points[points.size / 2]
|
141
|
+
else
|
142
|
+
(points[points.size / 2] + points[(points.size / 2) - 1]) / 2.0
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def first_quartile
|
147
|
+
if points.size.zero?
|
148
|
+
0
|
149
|
+
elsif points.size.odd?
|
150
|
+
points[points.size / 4]
|
151
|
+
else
|
152
|
+
(points[points.size / 4] + points[(points.size / 4) - 1]) / 2.0
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def third_quartile
|
157
|
+
if points.size.zero?
|
158
|
+
0
|
159
|
+
elsif points.size.odd?
|
160
|
+
points[(points.size * 3) / 4]
|
161
|
+
else
|
162
|
+
(points[(points.size * 3) / 4] + points[((points.size * 3) / 4) - 1]) / 2.0
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def lower_outliers
|
167
|
+
min = lower_whisker
|
168
|
+
points.select { |point| point < min }
|
169
|
+
end
|
170
|
+
|
171
|
+
def upper_outliers
|
172
|
+
max = upper_whisker
|
173
|
+
points.select { |point| point > max }
|
174
|
+
end
|
175
|
+
|
176
|
+
def interquartile_range
|
177
|
+
third_quartile - first_quartile
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
data/lib/gruff/bullet.rb
CHANGED
@@ -64,26 +64,26 @@ class Gruff::Bullet < Gruff::Base
|
|
64
64
|
rect_renderer = Gruff::Renderer::Rectangle.new(renderer, color: @colors[0])
|
65
65
|
rect_renderer.render(graph_left, 0, graph_left + graph_width, graph_height)
|
66
66
|
|
67
|
-
[
|
67
|
+
%i[high low].each_with_index do |indicator, index|
|
68
68
|
next unless @options.key?(indicator)
|
69
69
|
|
70
|
-
indicator_width_x = graph_left + graph_width * (@options[indicator] / maximum_value)
|
70
|
+
indicator_width_x = graph_left + (graph_width * (@options[indicator] / maximum_value))
|
71
71
|
|
72
72
|
rect_renderer = Gruff::Renderer::Rectangle.new(renderer, color: @colors[index + 1])
|
73
73
|
rect_renderer.render(graph_left, 0, indicator_width_x, graph_height)
|
74
74
|
end
|
75
75
|
|
76
76
|
if @options.key?(:target)
|
77
|
-
target_x = graph_left + graph_width * (@options[:target] / maximum_value)
|
77
|
+
target_x = graph_left + (graph_width * (@options[:target] / maximum_value))
|
78
78
|
half_thickness = thickness / 2.0
|
79
79
|
|
80
80
|
rect_renderer = Gruff::Renderer::Rectangle.new(renderer, color: @marker_color)
|
81
|
-
rect_renderer.render(target_x, half_thickness, target_x + half_thickness, thickness * 2 + half_thickness)
|
81
|
+
rect_renderer.render(target_x, half_thickness, target_x + half_thickness, (thickness * 2) + half_thickness)
|
82
82
|
end
|
83
83
|
|
84
84
|
# Value
|
85
85
|
rect_renderer = Gruff::Renderer::Rectangle.new(renderer, color: @marker_color)
|
86
|
-
rect_renderer.render(graph_left, thickness, graph_left + graph_width * (@value / maximum_value), thickness * 2)
|
86
|
+
rect_renderer.render(graph_left, thickness, graph_left + (graph_width * (@value / maximum_value)), thickness * 2)
|
87
87
|
end
|
88
88
|
|
89
89
|
private
|
@@ -94,6 +94,6 @@ private
|
|
94
94
|
font_height = calculate_caps_height(@title_font)
|
95
95
|
|
96
96
|
text_renderer = Gruff::Renderer::Text.new(renderer, @title, font: @title_font)
|
97
|
-
text_renderer.add_to_render_queue(1.0, 1.0, font_height / 2, font_height / 2, Magick::NorthWestGravity)
|
97
|
+
text_renderer.add_to_render_queue(1.0, 1.0, font_height / 2.0, font_height / 2.0, Magick::NorthWestGravity)
|
98
98
|
end
|
99
99
|
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Here's how to set up a Gruff::Candlestick.
|
5
|
+
#
|
6
|
+
# g = Gruff::Candlestick.new
|
7
|
+
# g.data low: 79.30, high: 93.10, open: 79.44, close: 91.20
|
8
|
+
# g.data low: 89.14, high: 106.42, open: 91.28, close: 106.26
|
9
|
+
# g.data low: 107.89, high: 131.00, open: 108.20, close: 129.04
|
10
|
+
# g.data low: 103.10, high: 137.98, open: 132.76, close: 115.81
|
11
|
+
# g.write("candlestick.png")
|
12
|
+
#
|
13
|
+
class Gruff::Candlestick < Gruff::Base
|
14
|
+
# Allow for vertical marker lines.
|
15
|
+
attr_writer :show_vertical_markers
|
16
|
+
|
17
|
+
# Specifies the filling opacity in area graph. Default is +0.4+.
|
18
|
+
attr_writer :fill_opacity
|
19
|
+
|
20
|
+
# Specifies the stroke width in line. Default is +2.0+.
|
21
|
+
attr_writer :stroke_width
|
22
|
+
|
23
|
+
# Specifies the color with up bar. Default is +'#579773'+.
|
24
|
+
attr_writer :up_color
|
25
|
+
|
26
|
+
# Specifies the color with down bar. Default is +'#eb5242'+.
|
27
|
+
attr_writer :down_color
|
28
|
+
|
29
|
+
# Can be used to adjust the spaces between the bars.
|
30
|
+
# Accepts values between 0.00 and 1.00 where 0.00 means no spacing at all
|
31
|
+
# and 1 means that each bars' width is nearly 0 (so each bar is a simple
|
32
|
+
# line with no x dimension).
|
33
|
+
#
|
34
|
+
# Default value is +0.9+.
|
35
|
+
def spacing_factor=(space_percent)
|
36
|
+
raise ArgumentError, 'spacing_factor must be between 0.00 and 1.00' unless (space_percent >= 0) && (space_percent <= 1)
|
37
|
+
|
38
|
+
@spacing_factor = (1 - space_percent)
|
39
|
+
end
|
40
|
+
|
41
|
+
# The sort feature is not supported in this graph.
|
42
|
+
def sort=(_value)
|
43
|
+
raise 'Not support #sort= in Gruff::Candlestick'
|
44
|
+
end
|
45
|
+
|
46
|
+
# The sort feature is not supported in this graph.
|
47
|
+
def sorted_drawing=(_value)
|
48
|
+
raise 'Not support #sorted_drawing= in Gruff::Candlestick'
|
49
|
+
end
|
50
|
+
|
51
|
+
def data(low:, high:, open:, close:)
|
52
|
+
super('', [low, high, open, close])
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def initialize_attributes
|
58
|
+
super
|
59
|
+
@show_vertical_markers = false
|
60
|
+
@fill_opacity = 0.4
|
61
|
+
@spacing_factor = 0.9
|
62
|
+
@stroke_width = 2.0
|
63
|
+
@up_color = '#579773'
|
64
|
+
@down_color = '#eb5242'
|
65
|
+
|
66
|
+
@hide_legend = true
|
67
|
+
end
|
68
|
+
|
69
|
+
def draw_graph
|
70
|
+
# Setup the BarConversion Object
|
71
|
+
conversion = Gruff::BarConversion.new(
|
72
|
+
top: @graph_top, bottom: @graph_bottom,
|
73
|
+
minimum_value: minimum_value, maximum_value: maximum_value, spread: @spread
|
74
|
+
)
|
75
|
+
|
76
|
+
width = (@graph_width - calculate_spacing) / column_count
|
77
|
+
bar_width = width * @spacing_factor
|
78
|
+
padding = width - bar_width
|
79
|
+
|
80
|
+
normalized_candlesticks.each_with_index do |candlestick, index|
|
81
|
+
left_x = @graph_left + (width * index) + (padding / 2.0)
|
82
|
+
right_x = left_x + bar_width
|
83
|
+
center_x = (left_x + right_x) / 2.0
|
84
|
+
color = candlestick.close >= candlestick.open ? @up_color : @down_color
|
85
|
+
|
86
|
+
draw_label(center_x, index) { draw_marker_vertical_line(center_x) if show_marker_vertical_line? }
|
87
|
+
|
88
|
+
open_y, = conversion.get_top_bottom_scaled(candlestick.open)
|
89
|
+
close_y, = conversion.get_top_bottom_scaled(candlestick.close)
|
90
|
+
Gruff::Renderer::Rectangle.new(renderer, color: color, opacity: @fill_opacity, width: @stroke_width).render(left_x, open_y, right_x, close_y)
|
91
|
+
|
92
|
+
low_y, = conversion.get_top_bottom_scaled(candlestick.low)
|
93
|
+
y = open_y < close_y ? close_y : open_y
|
94
|
+
Gruff::Renderer::Line.new(renderer, color: color, width: @stroke_width).render(center_x, low_y, center_x, y)
|
95
|
+
high_y, = conversion.get_top_bottom_scaled(candlestick.high)
|
96
|
+
y = open_y > close_y ? close_y : open_y
|
97
|
+
Gruff::Renderer::Line.new(renderer, color: color, width: @stroke_width).render(center_x, y, center_x, high_y)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def normalized_candlesticks
|
102
|
+
@normalized_candlesticks ||= store.norm_data.map { |data| Gruff::Candlestick::CandlestickData.new(*data.points) }
|
103
|
+
end
|
104
|
+
|
105
|
+
def column_count
|
106
|
+
normalized_candlesticks.size
|
107
|
+
end
|
108
|
+
|
109
|
+
def calculate_spacing
|
110
|
+
@scale * (column_count - 1)
|
111
|
+
end
|
112
|
+
|
113
|
+
def show_marker_vertical_line?
|
114
|
+
!@hide_line_markers && @show_vertical_markers
|
115
|
+
end
|
116
|
+
|
117
|
+
# @private
|
118
|
+
class CandlestickData < Struct.new(:low, :high, :open, :close)
|
119
|
+
end
|
120
|
+
end
|
data/lib/gruff/dot.rb
CHANGED
@@ -14,32 +14,33 @@
|
|
14
14
|
# g.write('dot.png')
|
15
15
|
#
|
16
16
|
class Gruff::Dot < Gruff::Base
|
17
|
-
|
18
|
-
|
19
|
-
def initialize_attributes
|
17
|
+
def initialize(*)
|
20
18
|
super
|
21
19
|
@has_left_labels = true
|
20
|
+
@dot_style = 'circle'
|
22
21
|
end
|
23
22
|
|
23
|
+
private
|
24
|
+
|
24
25
|
def draw_graph
|
25
26
|
# Setup spacing.
|
26
27
|
#
|
27
28
|
spacing_factor = 1.0
|
28
29
|
|
29
|
-
items_width = @graph_height / column_count
|
30
|
+
items_width = @graph_height / column_count
|
30
31
|
item_width = items_width * spacing_factor / store.length
|
31
32
|
padding = (items_width * (1 - spacing_factor)) / 2
|
32
33
|
|
33
34
|
store.norm_data.each_with_index do |data_row, row_index|
|
34
35
|
data_row.points.each_with_index do |data_point, point_index|
|
35
36
|
x_pos = @graph_left + (data_point * @graph_width)
|
36
|
-
y_pos = @graph_top + (items_width * point_index) + padding + (items_width
|
37
|
+
y_pos = @graph_top + (items_width * point_index) + padding + (items_width / 2.0)
|
37
38
|
|
38
39
|
if row_index == 0
|
39
40
|
Gruff::Renderer::Line.new(renderer, color: @marker_color).render(@graph_left, y_pos, @graph_left + @graph_width, y_pos)
|
40
41
|
end
|
41
42
|
|
42
|
-
Gruff::Renderer::
|
43
|
+
Gruff::Renderer::Dot.new(renderer, @dot_style, color: data_row.color).render(x_pos, y_pos, item_width / 3.0)
|
43
44
|
|
44
45
|
draw_label(y_pos, point_index)
|
45
46
|
end
|
@@ -51,11 +52,9 @@ private
|
|
51
52
|
return if @hide_line_markers
|
52
53
|
|
53
54
|
(0..marker_count).each do |index|
|
54
|
-
marker_label = BigDecimal(index.to_s) * BigDecimal(@increment.to_s) + BigDecimal(minimum_value.to_s)
|
55
|
-
x = @graph_left + (marker_label - minimum_value) * @graph_width / @spread
|
56
|
-
|
57
|
-
line_renderer = Gruff::Renderer::Line.new(renderer, color: @marker_color, shadow_color: @marker_shadow_color)
|
58
|
-
line_renderer.render(x, @graph_bottom, x, @graph_bottom + 5)
|
55
|
+
marker_label = (BigDecimal(index.to_s) * BigDecimal(@increment.to_s)) + BigDecimal(minimum_value.to_s)
|
56
|
+
x = @graph_left + ((marker_label - minimum_value) * @graph_width / @spread)
|
57
|
+
draw_marker_vertical_line(x, tick_mark_mode: true)
|
59
58
|
|
60
59
|
unless @hide_line_numbers
|
61
60
|
label = y_axis_label(marker_label, @increment)
|
@@ -70,7 +69,7 @@ private
|
|
70
69
|
|
71
70
|
def draw_label(y_offset, index)
|
72
71
|
draw_unique_label(index) do
|
73
|
-
draw_label_at(@graph_left - LABEL_MARGIN, 1.0, 0.0, y_offset, @labels[index], Magick::EastGravity)
|
72
|
+
draw_label_at(@graph_left - LABEL_MARGIN, 1.0, 0.0, y_offset, @labels[index], gravity: Magick::EastGravity)
|
74
73
|
end
|
75
74
|
end
|
76
75
|
end
|
data/lib/gruff/font.rb
CHANGED
@@ -3,7 +3,10 @@
|
|
3
3
|
# Handle font setting to draw texts
|
4
4
|
class Gruff::Font
|
5
5
|
BOLD_PATH = File.expand_path(File.join(__FILE__, '../../../assets/fonts/Roboto-Bold.ttf')).freeze
|
6
|
+
private_constant :BOLD_PATH
|
7
|
+
|
6
8
|
REGULAR_PATH = File.expand_path(File.join(__FILE__, '../../../assets/fonts/Roboto-Regular.ttf')).freeze
|
9
|
+
private_constant :REGULAR_PATH
|
7
10
|
|
8
11
|
# Get/set font path.
|
9
12
|
attr_accessor :path
|
@@ -13,8 +13,6 @@
|
|
13
13
|
#
|
14
14
|
# @private
|
15
15
|
class Gruff::BarConversion
|
16
|
-
attr_writer :mode
|
17
|
-
|
18
16
|
def initialize(top:, bottom:, minimum_value:, maximum_value:, spread:)
|
19
17
|
@graph_top = top
|
20
18
|
@graph_height = bottom - top
|
@@ -36,25 +34,23 @@ class Gruff::BarConversion
|
|
36
34
|
end
|
37
35
|
|
38
36
|
def get_top_bottom_scaled(data_point)
|
37
|
+
data_point = data_point.to_f
|
39
38
|
result = []
|
40
39
|
|
41
40
|
case @mode
|
42
41
|
when 1
|
43
42
|
# minimum value >= 0 ( only positive values )
|
44
|
-
result[0] = @graph_top + @graph_height * (1 - data_point)
|
43
|
+
result[0] = @graph_top + (@graph_height * (1 - data_point))
|
45
44
|
result[1] = @graph_top + @graph_height
|
46
45
|
when 2
|
47
46
|
# only negative values
|
48
47
|
result[0] = @graph_top
|
49
|
-
result[1] = @graph_top + @graph_height * (1 - data_point)
|
48
|
+
result[1] = @graph_top + (@graph_height * (1 - data_point))
|
50
49
|
when 3
|
51
50
|
# positive and negative values
|
52
|
-
val = data_point - @minimum_value / @spread
|
53
|
-
result[0] = @graph_top + @graph_height * (1 - (val - @zero))
|
54
|
-
result[1] = @graph_top + @graph_height * (1 - @zero)
|
55
|
-
else
|
56
|
-
result[0] = 0.0
|
57
|
-
result[1] = 0.0
|
51
|
+
val = data_point - (@minimum_value / @spread)
|
52
|
+
result[0] = @graph_top + (@graph_height * (1 - (val - @zero)))
|
53
|
+
result[1] = @graph_top + (@graph_height * (1 - @zero))
|
58
54
|
end
|
59
55
|
|
60
56
|
result
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @private
|
4
|
+
module Gruff::Base::BarMixin
|
5
|
+
def normalized_group_bars
|
6
|
+
@normalized_group_bars ||= begin
|
7
|
+
group_bars = Array.new(column_count) { [] }
|
8
|
+
store.norm_data.each_with_index do |data_row, row_index|
|
9
|
+
data_row.points.each_with_index do |data_point, point_index|
|
10
|
+
group_bars[point_index] << BarData.new(data_point, store.data[row_index].points[point_index], data_row.color)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Adjust the number of each group with empty bar
|
14
|
+
(data_row.points.size..(column_count - 1)).each do |index|
|
15
|
+
group_bars[index] << BarData.new(0, nil, data_row.color)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
group_bars
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# @private
|
23
|
+
class BarData < Struct.new(:point, :value, :color)
|
24
|
+
end
|
25
|
+
end
|
@@ -4,6 +4,20 @@
|
|
4
4
|
module Gruff::BarValueLabel
|
5
5
|
using String::GruffCommify
|
6
6
|
|
7
|
+
# @private
|
8
|
+
def self.metrics(value, format, proc_text_metrics)
|
9
|
+
val = begin
|
10
|
+
if format.is_a?(Proc)
|
11
|
+
format.call(value)
|
12
|
+
else
|
13
|
+
sprintf(format || '%.2f', value).commify
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
metrics = proc_text_metrics.call(val)
|
18
|
+
[val, metrics]
|
19
|
+
end
|
20
|
+
|
7
21
|
# @private
|
8
22
|
class Base
|
9
23
|
attr_reader :coordinate, :value
|
@@ -16,53 +30,23 @@ module Gruff::BarValueLabel
|
|
16
30
|
|
17
31
|
# @private
|
18
32
|
class Bar < Base
|
19
|
-
def prepare_rendering(format,
|
20
|
-
left_x, left_y,
|
21
|
-
|
22
|
-
val = format.call(@value)
|
23
|
-
else
|
24
|
-
val = sprintf(format || '%.2f', @value).commify
|
25
|
-
end
|
33
|
+
def prepare_rendering(format, proc_text_metrics)
|
34
|
+
left_x, left_y, _right_x, _right_y = @coordinate
|
35
|
+
val, metrics = Gruff::BarValueLabel.metrics(@value, format, proc_text_metrics)
|
26
36
|
|
27
|
-
y = @value >= 0 ? left_y -
|
28
|
-
yield left_x
|
37
|
+
y = @value >= 0 ? left_y - metrics.height - 5 : left_y + 5
|
38
|
+
yield left_x, y, val, metrics.width, metrics.height
|
29
39
|
end
|
30
40
|
end
|
31
41
|
|
32
42
|
# @private
|
33
43
|
class SideBar < Base
|
34
|
-
def prepare_rendering(format,
|
35
|
-
left_x,
|
36
|
-
|
37
|
-
val = format.call(@value)
|
38
|
-
else
|
39
|
-
val = sprintf(format || '%.2f', @value).commify
|
40
|
-
end
|
41
|
-
|
42
|
-
x = @value >= 0 ? right_x + 40 : left_x - 40
|
43
|
-
yield x, right_y - bar_width / 2, val
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
# @private
|
48
|
-
class StackedBar
|
49
|
-
def initialize
|
50
|
-
@bars = []
|
51
|
-
end
|
52
|
-
|
53
|
-
def add(bar, index)
|
54
|
-
bars = @bars[index] || []
|
55
|
-
bars << bar
|
56
|
-
@bars[index] = bars
|
57
|
-
end
|
44
|
+
def prepare_rendering(format, proc_text_metrics)
|
45
|
+
left_x, left_y, right_x, _right_y = @coordinate
|
46
|
+
val, metrics = Gruff::BarValueLabel.metrics(@value, format, proc_text_metrics)
|
58
47
|
|
59
|
-
|
60
|
-
|
61
|
-
value = bars.sum(&:value)
|
62
|
-
bar = bars.last
|
63
|
-
bar_value_label = bar.class.new(bar.coordinate, value)
|
64
|
-
bar_value_label.prepare_rendering(format, bar_width, &block)
|
65
|
-
end
|
48
|
+
x = @value >= 0 ? right_x + 10 : left_x - metrics.width - 10
|
49
|
+
yield x, left_y, val, metrics.width, metrics.height
|
66
50
|
end
|
67
51
|
end
|
68
52
|
end
|
@@ -16,7 +16,25 @@ module Gruff::Base::StackedMixin
|
|
16
16
|
|
17
17
|
max_hash.each_key do |key|
|
18
18
|
self.maximum_value = max_hash[key] if max_hash[key] > maximum_value
|
19
|
+
self.minimum_value = max_hash[key] if max_hash[key] < minimum_value
|
19
20
|
end
|
20
|
-
|
21
|
+
|
22
|
+
raise "Can't handle negative values in stacked graph" if minimum_value < 0
|
23
|
+
end
|
24
|
+
|
25
|
+
def normalized_stacked_bars
|
26
|
+
@normalized_stacked_bars ||= begin
|
27
|
+
stacked_bars = Array.new(column_count) { [] }
|
28
|
+
store.norm_data.each_with_index do |data_row, row_index|
|
29
|
+
data_row.points.each_with_index do |data_point, point_index|
|
30
|
+
stacked_bars[point_index] << BarData.new(data_point, store.data[row_index].points[point_index], data_row.color)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
stacked_bars
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# @private
|
38
|
+
class BarData < Struct.new(:point, :value, :color)
|
21
39
|
end
|
22
40
|
end
|
data/lib/gruff/histogram.rb
CHANGED
@@ -29,7 +29,7 @@ class Gruff::Histogram < Gruff::Bar
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def data(name, data_points = [], color = nil)
|
32
|
-
@data << [name, data_points, color]
|
32
|
+
@data << [name, Array(data_points), color]
|
33
33
|
end
|
34
34
|
|
35
35
|
private
|
@@ -44,11 +44,15 @@ private
|
|
44
44
|
|
45
45
|
def setup_data
|
46
46
|
@data.each do |(name, data_points, color)|
|
47
|
-
|
48
|
-
|
49
|
-
|
47
|
+
if data_points.empty?
|
48
|
+
store.add(name, [], color)
|
49
|
+
else
|
50
|
+
bins, freqs = HistogramArray.new(data_points).histogram(bin_width: @bin_width, min: @minimum_bin, max: @maximum_bin)
|
51
|
+
bins.each_with_index do |bin, index|
|
52
|
+
@labels[index] = bin
|
53
|
+
end
|
54
|
+
store.add(name, freqs, color)
|
50
55
|
end
|
51
|
-
store.add(name, freqs, color)
|
52
56
|
end
|
53
57
|
|
54
58
|
super
|