gruff 0.15.0 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +21 -5
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +0 -12
  5. data/CHANGELOG.md +52 -0
  6. data/README.md +14 -3
  7. data/gruff.gemspec +3 -4
  8. data/lib/gruff/accumulator_bar.rb +1 -1
  9. data/lib/gruff/area.rb +6 -4
  10. data/lib/gruff/bar.rb +53 -32
  11. data/lib/gruff/base.rb +297 -186
  12. data/lib/gruff/bezier.rb +4 -2
  13. data/lib/gruff/box.rb +180 -0
  14. data/lib/gruff/bubble.rb +99 -0
  15. data/lib/gruff/bullet.rb +5 -5
  16. data/lib/gruff/candlestick.rb +120 -0
  17. data/lib/gruff/dot.rb +11 -12
  18. data/lib/gruff/font.rb +3 -0
  19. data/lib/gruff/helper/bar_conversion.rb +6 -10
  20. data/lib/gruff/helper/bar_mixin.rb +25 -0
  21. data/lib/gruff/helper/bar_value_label.rb +24 -43
  22. data/lib/gruff/helper/stacked_mixin.rb +19 -1
  23. data/lib/gruff/histogram.rb +9 -6
  24. data/lib/gruff/line.rb +67 -43
  25. data/lib/gruff/mini/legend.rb +15 -11
  26. data/lib/gruff/net.rb +23 -18
  27. data/lib/gruff/patch/string.rb +1 -0
  28. data/lib/gruff/pie.rb +26 -12
  29. data/lib/gruff/renderer/circle.rb +3 -1
  30. data/lib/gruff/renderer/dash_line.rb +3 -2
  31. data/lib/gruff/renderer/dot.rb +28 -15
  32. data/lib/gruff/renderer/line.rb +1 -3
  33. data/lib/gruff/renderer/rectangle.rb +6 -2
  34. data/lib/gruff/renderer/renderer.rb +0 -4
  35. data/lib/gruff/renderer/text.rb +7 -1
  36. data/lib/gruff/scatter.rb +84 -81
  37. data/lib/gruff/side_bar.rb +64 -31
  38. data/lib/gruff/side_stacked_bar.rb +43 -55
  39. data/lib/gruff/spider.rb +52 -14
  40. data/lib/gruff/stacked_area.rb +18 -8
  41. data/lib/gruff/stacked_bar.rb +59 -29
  42. data/lib/gruff/store/xy_data.rb +8 -9
  43. data/lib/gruff/store/xy_pointsizes_data.rb +60 -0
  44. data/lib/gruff/version.rb +1 -1
  45. data/lib/gruff.rb +11 -12
  46. metadata +14 -11
  47. data/lib/gruff/scene.rb +0 -208
  48. 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).to_f
25
+ x_increment = (@graph_width / (column_count - 1)).to_f
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
data/lib/gruff/box.rb ADDED
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Here's how to set up a Gruff::Box.
5
+ #
6
+ # g = Gruff::Box.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::Box < 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::Box::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
+ 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
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Here's how to set up a Gruff::Bubble.
5
+ #
6
+ # g = Gruff::Bubble.new
7
+ # g.title = 'Bubble plot'
8
+ # g.write('bubble.png')
9
+ #
10
+ class Gruff::Bubble < Gruff::Scatter
11
+ # Specifies the filling opacity in area graph. Default is +0.6+.
12
+ attr_writer :fill_opacity
13
+
14
+ # Specifies the stroke width in line. Default is +1.0+.
15
+ attr_writer :stroke_width
16
+
17
+ # The first parameter is the name of the dataset. The next two are the
18
+ # x and y axis data points contain in their own array in that respective
19
+ # order. The 4th argument represents sizes of points.
20
+ # The final parameter is the color.
21
+ #
22
+ # Can be called multiple times with different datasets for a multi-valued
23
+ # graph.
24
+ #
25
+ # If the color argument is nil, the next color from the default theme will
26
+ # be used.
27
+ #
28
+ # @note If you want to use a preset theme, you must set it before calling {#data}.
29
+ #
30
+ # @param name [String, Symbol] containing the name of the dataset.
31
+ # @param x_data_points [Array] An Array of x-axis data points.
32
+ # @param y_data_points [Array] An Array of y-axis data points.
33
+ # @param point_sizes [Array] An Array of sizes for points.
34
+ # @param color [String] The hex string for the color of the dataset. Defaults to nil.
35
+ #
36
+ # @raise [ArgumentError] Data points contain nil values.
37
+ # This error will get raised if either the x or y axis data points array
38
+ # contains a +nil+ value. The graph will not make an assumption
39
+ # as how to graph +nil+.
40
+ # @raise [ArgumentError] +x_data_points+ is empty.
41
+ # This error is raised when the array for the x-axis points are empty
42
+ # @raise [ArgumentError] +y_data_points+ is empty.
43
+ # This error is raised when the array for the y-axis points are empty.
44
+ # @raise [ArgumentError] +point_sizes+ is empty.
45
+ # This error is raised when the array for the point_sizes are empty
46
+ # @raise [ArgumentError] +x_data_points.length != y_data_points.length+.
47
+ # Error means that the x and y axis point arrays do not match in length.
48
+ # @raise [ArgumentError] +x_data_points.length != point_sizes.length+.
49
+ # Error means that the x and point_sizes arrays do not match in length.
50
+ #
51
+ # @example
52
+ # g = Gruff::Bubble.new
53
+ # g.title = "Bubble Graph"
54
+ # g.data :A, [-1, 19, -4, -23], [-35, 21, 23, -4], [4.5, 1.0, 2.1, 0.9]
55
+ #
56
+ def data(name, x_data_points = [], y_data_points = [], point_sizes = [], color = nil)
57
+ # make sure it's an array
58
+ x_data_points = Array(x_data_points)
59
+ y_data_points = Array(y_data_points)
60
+ point_sizes = Array(point_sizes)
61
+
62
+ raise ArgumentError, 'Data Points contain nil Value!' if x_data_points.include?(nil) || y_data_points.include?(nil)
63
+ raise ArgumentError, 'x_data_points is empty!' if x_data_points.empty?
64
+ raise ArgumentError, 'y_data_points is empty!' if y_data_points.empty?
65
+ raise ArgumentError, 'point_sizes is empty!' if point_sizes.empty?
66
+ raise ArgumentError, 'x_data_points.length != y_data_points.length!' if x_data_points.length != y_data_points.length
67
+ raise ArgumentError, 'x_data_points.length != point_sizes.length!' if x_data_points.length != point_sizes.length
68
+
69
+ store.add(name, x_data_points, y_data_points, point_sizes, color)
70
+ end
71
+
72
+ private
73
+
74
+ def initialize_store
75
+ @store = Gruff::Store.new(Gruff::Store::XYPointsizeData)
76
+ end
77
+
78
+ def initialize_attributes
79
+ super
80
+
81
+ @fill_opacity = 0.6
82
+ @stroke_width = 1.0
83
+ end
84
+
85
+ def draw_graph
86
+ store.norm_data.each do |data_row|
87
+ data_row.coordinate_and_pointsizes.each do |x_value, y_value, point_size|
88
+ next if y_value.nil? || x_value.nil?
89
+
90
+ new_x = @graph_left + (x_value * @graph_width)
91
+ new_y = @graph_bottom - (y_value * @graph_height)
92
+ diameter = @graph_width / (@marker_count * x_increment) * point_size.to_f
93
+
94
+ Gruff::Renderer::Circle.new(renderer, color: data_row.color, width: @stroke_width, opacity: @fill_opacity)
95
+ .render(new_x, new_y, new_x - (diameter / 2.0), new_y)
96
+ end
97
+ end
98
+ end
99
+ end
data/lib/gruff/bullet.rb CHANGED
@@ -67,23 +67,23 @@ class Gruff::Bullet < Gruff::Base
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
+ 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
- private
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.to_f
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.to_f / 2.0).round
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::Circle.new(renderer, color: data_row.color).render(x_pos, y_pos, x_pos + (item_width.to_f / 3.0).round, y_pos)
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,56 +30,23 @@ module Gruff::BarValueLabel
16
30
 
17
31
  # @private
18
32
  class Bar < Base
19
- def prepare_rendering(format, _bar_width = 0)
20
- left_x, left_y, right_x, _right_y = @coordinate
21
- val = begin
22
- if format.is_a?(Proc)
23
- format.call(@value)
24
- else
25
- sprintf(format || '%.2f', @value).commify
26
- end
27
- 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)
28
36
 
29
- y = @value >= 0 ? left_y - 30 : left_y + 12
30
- yield left_x + (right_x - left_x) / 2, y, val
37
+ y = @value >= 0 ? left_y - metrics.height - 5 : left_y + 5
38
+ yield left_x, y, val, metrics.width, metrics.height
31
39
  end
32
40
  end
33
41
 
34
42
  # @private
35
43
  class SideBar < Base
36
- def prepare_rendering(format, bar_width = 0)
37
- left_x, _left_y, right_x, right_y = @coordinate
38
- val = begin
39
- if format.is_a?(Proc)
40
- format.call(@value)
41
- else
42
- sprintf(format || '%.2f', @value).commify
43
- end
44
- end
45
- x = @value >= 0 ? right_x + 40 : left_x - 40
46
- yield x, right_y - bar_width / 2, val
47
- end
48
- end
49
-
50
- # @private
51
- class StackedBar
52
- def initialize
53
- @bars = []
54
- end
55
-
56
- def add(bar, index)
57
- bars = @bars[index] || []
58
- bars << bar
59
- @bars[index] = bars
60
- 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)
61
47
 
62
- def prepare_rendering(format, bar_width = 0, &block)
63
- @bars.each do |bars|
64
- value = bars.sum(&:value)
65
- bar = bars.last
66
- bar_value_label = bar.class.new(bar.coordinate, value)
67
- bar_value_label.prepare_rendering(format, bar_width, &block)
68
- end
48
+ x = @value >= 0 ? right_x + 10 : left_x - metrics.width - 10
49
+ yield x, left_y, val, metrics.width, metrics.height
69
50
  end
70
51
  end
71
52
  end