gruff 0.14.0-java → 0.17.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 +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 +17 -16
- 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
|