gruff 0.13.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +79 -0
  3. data/.rubocop.yml +29 -31
  4. data/CHANGELOG.md +43 -0
  5. data/README.md +11 -5
  6. data/gruff.gemspec +8 -10
  7. data/lib/gruff/accumulator_bar.rb +4 -2
  8. data/lib/gruff/area.rb +9 -12
  9. data/lib/gruff/bar.rb +46 -31
  10. data/lib/gruff/base.rb +236 -207
  11. data/lib/gruff/bezier.rb +6 -8
  12. data/lib/gruff/box_plot.rb +174 -0
  13. data/lib/gruff/bullet.rb +17 -16
  14. data/lib/gruff/candlestick.rb +112 -0
  15. data/lib/gruff/dot.rb +14 -14
  16. data/lib/gruff/font.rb +42 -0
  17. data/lib/gruff/helper/bar_conversion.rb +5 -5
  18. data/lib/gruff/helper/bar_value_label.rb +26 -20
  19. data/lib/gruff/helper/stacked_mixin.rb +4 -3
  20. data/lib/gruff/histogram.rb +9 -7
  21. data/lib/gruff/line.rb +96 -83
  22. data/lib/gruff/mini/bar.rb +9 -6
  23. data/lib/gruff/mini/legend.rb +16 -12
  24. data/lib/gruff/mini/pie.rb +9 -6
  25. data/lib/gruff/mini/side_bar.rb +9 -6
  26. data/lib/gruff/net.rb +16 -22
  27. data/lib/gruff/patch/rmagick.rb +0 -1
  28. data/lib/gruff/patch/string.rb +2 -1
  29. data/lib/gruff/pie.rb +42 -75
  30. data/lib/gruff/renderer/bezier.rb +8 -9
  31. data/lib/gruff/renderer/circle.rb +8 -9
  32. data/lib/gruff/renderer/dash_line.rb +10 -10
  33. data/lib/gruff/renderer/dot.rb +15 -14
  34. data/lib/gruff/renderer/ellipse.rb +8 -9
  35. data/lib/gruff/renderer/line.rb +8 -11
  36. data/lib/gruff/renderer/polygon.rb +9 -10
  37. data/lib/gruff/renderer/polyline.rb +8 -9
  38. data/lib/gruff/renderer/rectangle.rb +11 -8
  39. data/lib/gruff/renderer/renderer.rb +25 -40
  40. data/lib/gruff/renderer/text.rb +21 -37
  41. data/lib/gruff/scatter.rb +86 -85
  42. data/lib/gruff/side_bar.rb +50 -37
  43. data/lib/gruff/side_stacked_bar.rb +26 -35
  44. data/lib/gruff/spider.rb +52 -28
  45. data/lib/gruff/stacked_area.rb +20 -16
  46. data/lib/gruff/stacked_bar.rb +44 -22
  47. data/lib/gruff/store/store.rb +6 -10
  48. data/lib/gruff/store/xy_data.rb +2 -0
  49. data/lib/gruff/themes.rb +6 -6
  50. data/lib/gruff/version.rb +1 -1
  51. data/lib/gruff.rb +70 -57
  52. data/rails_generators/gruff/templates/controller.rb +1 -1
  53. metadata +25 -28
  54. data/.rubocop_todo.yml +0 -182
  55. data/.travis.yml +0 -23
  56. data/assets/plastik/blue.png +0 -0
  57. data/assets/plastik/green.png +0 -0
  58. data/assets/plastik/red.png +0 -0
  59. data/lib/gruff/photo_bar.rb +0 -93
  60. data/lib/gruff/scene.rb +0 -198
  61. data/lib/gruff/store/custom_data.rb +0 -36
data/lib/gruff/bezier.rb CHANGED
@@ -19,12 +19,10 @@
19
19
  # g.write('bezier.png')
20
20
  #
21
21
  class Gruff::Bezier < Gruff::Base
22
- def draw
23
- super
22
+ private
24
23
 
25
- return unless data_given?
26
-
27
- x_increment = @graph_width / (column_count - 1).to_f
24
+ def draw_graph
25
+ x_increment = @graph_width / (column_count - 1)
28
26
 
29
27
  store.norm_data.each do |data_row|
30
28
  poly_points = []
@@ -32,7 +30,7 @@ class Gruff::Bezier < Gruff::Base
32
30
  data_row[1].each_with_index do |data_point, index|
33
31
  # Use incremented x and scaled y
34
32
  new_x = @graph_left + (x_increment * index)
35
- new_y = @graph_top + (@graph_height - data_point * @graph_height)
33
+ new_y = @graph_top + (@graph_height - (data_point * @graph_height))
36
34
 
37
35
  if index == 0 && RUBY_PLATFORM != 'java'
38
36
  poly_points << new_x
@@ -48,9 +46,9 @@ class Gruff::Bezier < Gruff::Base
48
46
  stroke_width = clip_value_if_greater_than(@columns / (store.norm_data.first[1].size * 4), 5.0)
49
47
 
50
48
  if RUBY_PLATFORM == 'java'
51
- Gruff::Renderer::Polyline.new(color: data_row.color, width: stroke_width).render(poly_points)
49
+ Gruff::Renderer::Polyline.new(renderer, color: data_row.color, width: stroke_width).render(poly_points)
52
50
  else
53
- Gruff::Renderer::Bezier.new(color: data_row.color, width: stroke_width).render(poly_points)
51
+ Gruff::Renderer::Bezier.new(renderer, color: data_row.color, width: stroke_width).render(poly_points)
54
52
  end
55
53
  end
56
54
  end
@@ -0,0 +1,174 @@
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) / normalized_boxes.size
48
+ bar_width = width * @spacing_factor
49
+ padding = width - bar_width
50
+
51
+ normalized_boxes.each_with_index do |box, index|
52
+ left_x = @graph_left + (width * index) + (padding / 2.0)
53
+ right_x = left_x + bar_width
54
+ center_x = (left_x + right_x) / 2.0
55
+
56
+ first_y, = conversion.get_top_bottom_scaled(box.first_quartile)
57
+ third_y, = conversion.get_top_bottom_scaled(box.third_quartile)
58
+ Gruff::Renderer::Rectangle.new(renderer, color: box.color, width: @stroke_width, opacity: @fill_opacity)
59
+ .render(left_x, first_y, right_x, third_y)
60
+
61
+ median_y, = conversion.get_top_bottom_scaled(box.median)
62
+ Gruff::Renderer::Line.new(renderer, color: box.color, width: @stroke_width * 2).render(left_x, median_y, right_x, median_y)
63
+
64
+ minmax_left_x = left_x + (bar_width / 4.0)
65
+ minmax_right_x = right_x - (bar_width / 4.0)
66
+ min_y, = conversion.get_top_bottom_scaled(box.lower_whisker)
67
+ Gruff::Renderer::Line.new(renderer, color: box.color, width: @stroke_width).render(minmax_left_x, min_y, minmax_right_x, min_y)
68
+ Gruff::Renderer::DashLine.new(renderer, color: box.color, width: @stroke_width, dasharray: [@stroke_width, @stroke_width * 2])
69
+ .render(center_x, min_y, center_x, first_y)
70
+
71
+ max_y, = conversion.get_top_bottom_scaled(box.upper_whisker)
72
+ Gruff::Renderer::Line.new(renderer, color: box.color, width: @stroke_width).render(minmax_left_x, max_y, minmax_right_x, max_y)
73
+ Gruff::Renderer::DashLine.new(renderer, color: box.color, width: @stroke_width, dasharray: [@stroke_width, @stroke_width * 2])
74
+ .render(center_x, max_y, center_x, third_y)
75
+
76
+ box.lower_outliers.each do |outlier|
77
+ outlier_y, = conversion.get_top_bottom_scaled(outlier)
78
+ Gruff::Renderer::Dot.new(renderer, :circle, color: box.color, opacity: @fill_opacity).render(center_x, outlier_y, @stroke_width * 2)
79
+ end
80
+
81
+ box.upper_outliers.each do |outlier|
82
+ outlier_y, = conversion.get_top_bottom_scaled(outlier)
83
+ Gruff::Renderer::Dot.new(renderer, :circle, color: box.color, opacity: @fill_opacity).render(center_x, outlier_y, @stroke_width * 2)
84
+ end
85
+
86
+ draw_label(center_x, index)
87
+ end
88
+ end
89
+
90
+ def normalized_boxes
91
+ @normalized_boxes ||= store.norm_data.map { |data| Gruff::BoxPlot::BoxData.new(data.label, data.points, data.color) }
92
+ end
93
+
94
+ def calculate_spacing
95
+ @scale * (column_count - 1)
96
+ end
97
+
98
+ # @private
99
+ class BoxData < Struct.new(:label, :points, :color)
100
+ def initialize(label, points, color)
101
+ super(label, points.sort, color)
102
+ end
103
+
104
+ def min
105
+ points.first || 0
106
+ end
107
+
108
+ def max
109
+ points.last || 0
110
+ end
111
+
112
+ def min_whisker
113
+ [min, first_quartile - (1.5 * interquartile_range)].max
114
+ end
115
+
116
+ def max_whisker
117
+ [max, third_quartile + (1.5 * interquartile_range)].min
118
+ end
119
+
120
+ def upper_whisker
121
+ max = max_whisker
122
+ points.select { |point| point <= max }.max
123
+ end
124
+
125
+ def lower_whisker
126
+ min = min_whisker
127
+ points.select { |point| point >= min }.min
128
+ end
129
+
130
+ def median
131
+ if points.size.zero?
132
+ 0
133
+ elsif points.size.odd?
134
+ points[points.size / 2]
135
+ else
136
+ (points[points.size / 2] + points[(points.size / 2) - 1]) / 2.0
137
+ end
138
+ end
139
+
140
+ def first_quartile
141
+ if points.size.zero?
142
+ 0
143
+ elsif points.size.odd?
144
+ points[points.size / 4]
145
+ else
146
+ (points[points.size / 4] + points[(points.size / 4) - 1]) / 2.0
147
+ end
148
+ end
149
+
150
+ def third_quartile
151
+ if points.size.zero?
152
+ 0
153
+ elsif points.size.odd?
154
+ points[(points.size * 3) / 4]
155
+ else
156
+ (points[(points.size * 3) / 4] + points[((points.size * 3) / 4) - 1]) / 2.0
157
+ end
158
+ end
159
+
160
+ def lower_outliers
161
+ min = lower_whisker
162
+ points.select { |point| point < min }
163
+ end
164
+
165
+ def upper_outliers
166
+ max = upper_whisker
167
+ points.select { |point| point > max }
168
+ end
169
+
170
+ def interquartile_range
171
+ third_quartile - first_quartile
172
+ end
173
+ end
174
+ end
data/lib/gruff/bullet.rb CHANGED
@@ -27,12 +27,13 @@ class Gruff::Bullet < Gruff::Base
27
27
  self.theme = Gruff::Themes::GREYSCALE
28
28
  end
29
29
 
30
- def initialize_ivars
30
+ def initialize_attributes
31
31
  super
32
32
 
33
- @title_font_size = 20
33
+ @title_font.size = 20
34
+ @title_font.bold = false
34
35
  end
35
- private :initialize_ivars
36
+ private :initialize_attributes
36
37
 
37
38
  def data(value, maximum_value, options = {})
38
39
  @value = value.to_f
@@ -51,7 +52,7 @@ class Gruff::Bullet < Gruff::Base
51
52
 
52
53
  draw_title
53
54
 
54
- title_width = calculate_width(@title_font_size, @title)
55
+ title_width = calculate_width(@title_font, @title)
55
56
  margin = 30.0
56
57
  thickness = @raw_rows / 6.0
57
58
  right_margin = margin
@@ -60,29 +61,29 @@ class Gruff::Bullet < Gruff::Base
60
61
  graph_height = thickness * 3.0
61
62
 
62
63
  # Background
63
- rect_renderer = Gruff::Renderer::Rectangle.new(color: @colors[0])
64
+ rect_renderer = Gruff::Renderer::Rectangle.new(renderer, color: @colors[0])
64
65
  rect_renderer.render(graph_left, 0, graph_left + graph_width, graph_height)
65
66
 
66
- [:high, :low].each_with_index do |indicator, index|
67
+ %i[high low].each_with_index do |indicator, index|
67
68
  next unless @options.key?(indicator)
68
69
 
69
- indicator_width_x = graph_left + graph_width * (@options[indicator] / maximum_value)
70
+ indicator_width_x = graph_left + (graph_width * (@options[indicator] / maximum_value))
70
71
 
71
- rect_renderer = Gruff::Renderer::Rectangle.new(color: @colors[index + 1])
72
+ rect_renderer = Gruff::Renderer::Rectangle.new(renderer, color: @colors[index + 1])
72
73
  rect_renderer.render(graph_left, 0, indicator_width_x, graph_height)
73
74
  end
74
75
 
75
76
  if @options.key?(:target)
76
- target_x = graph_left + graph_width * (@options[:target] / maximum_value)
77
+ target_x = graph_left + (graph_width * (@options[:target] / maximum_value))
77
78
  half_thickness = thickness / 2.0
78
79
 
79
- rect_renderer = Gruff::Renderer::Rectangle.new(color: @font_color)
80
- rect_renderer.render(target_x, half_thickness, target_x + half_thickness, thickness * 2 + half_thickness)
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
82
  end
82
83
 
83
84
  # Value
84
- rect_renderer = Gruff::Renderer::Rectangle.new(color: @font_color)
85
- rect_renderer.render(graph_left, thickness, graph_left + graph_width * (@value / maximum_value), thickness * 2)
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
87
  end
87
88
 
88
89
  private
@@ -90,9 +91,9 @@ private
90
91
  def draw_title
91
92
  return if hide_title?
92
93
 
93
- font_height = calculate_caps_height(scale_fontsize(@title_font_size))
94
+ font_height = calculate_caps_height(@title_font)
94
95
 
95
- text_renderer = Gruff::Renderer::Text.new(@title, font: @font, size: @title_font_size, color: @font_color)
96
- text_renderer.add_to_render_queue(1.0, 1.0, font_height / 2, font_height / 2, Magick::NorthWestGravity)
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.0, font_height / 2.0, Magick::NorthWestGravity)
97
98
  end
98
99
  end
@@ -0,0 +1,112 @@
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
+ def data(low:, high:, open:, close:)
42
+ super('', [low, high, open, close])
43
+ end
44
+
45
+ private
46
+
47
+ def initialize_attributes
48
+ super
49
+ @show_vertical_markers = false
50
+ @fill_opacity = 0.4
51
+ @spacing_factor = 0.9
52
+ @stroke_width = 2.0
53
+ @up_color = '#579773'
54
+ @down_color = '#eb5242'
55
+
56
+ @sort = false
57
+ @hide_legend = true
58
+ end
59
+
60
+ def draw_graph
61
+ # Setup the BarConversion Object
62
+ conversion = Gruff::BarConversion.new(
63
+ top: @graph_top, bottom: @graph_bottom,
64
+ minimum_value: minimum_value, maximum_value: maximum_value, spread: @spread
65
+ )
66
+
67
+ width = (@graph_width - calculate_spacing) / normalized_candlesticks.size
68
+ bar_width = width * @spacing_factor
69
+ padding = width - bar_width
70
+
71
+ normalized_candlesticks.each_with_index do |candlestick, index|
72
+ left_x = @graph_left + (width * index) + (padding / 2.0)
73
+ right_x = left_x + bar_width
74
+ center_x = (left_x + right_x) / 2.0
75
+ color = candlestick.close >= candlestick.open ? @up_color : @down_color
76
+
77
+ draw_label(center_x, index) do
78
+ break if @hide_line_markers
79
+ break unless @show_vertical_markers
80
+
81
+ Gruff::Renderer::Line.new(renderer, color: @marker_color).render(center_x, @graph_bottom, center_x, @graph_top)
82
+ if @marker_shadow_color
83
+ Gruff::Renderer::Line.new(renderer, color: @marker_shadow_color)
84
+ .render(center_x + 1, @graph_bottom, center_x + 1, @graph_top)
85
+ end
86
+ end
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
+ @candlesticks ||= store.norm_data.map { |data| Gruff::Candlestick::CandlestickData.new(*data.points) }
103
+ end
104
+
105
+ def calculate_spacing
106
+ @scale * (column_count - 1)
107
+ end
108
+
109
+ # @private
110
+ class CandlestickData < Struct.new(:low, :high, :open, :close)
111
+ end
112
+ end
data/lib/gruff/dot.rb CHANGED
@@ -14,52 +14,52 @@
14
14
  # g.write('dot.png')
15
15
  #
16
16
  class Gruff::Dot < Gruff::Base
17
- def draw
18
- @has_left_labels = true
17
+ def initialize(*)
19
18
  super
19
+ @has_left_labels = true
20
+ end
20
21
 
21
- return unless data_given?
22
+ private
22
23
 
24
+ def draw_graph
23
25
  # Setup spacing.
24
26
  #
25
27
  spacing_factor = 1.0
26
28
 
27
- items_width = @graph_height / column_count.to_f
29
+ items_width = @graph_height / column_count
28
30
  item_width = items_width * spacing_factor / store.length
29
31
  padding = (items_width * (1 - spacing_factor)) / 2
30
32
 
31
33
  store.norm_data.each_with_index do |data_row, row_index|
32
34
  data_row.points.each_with_index do |data_point, point_index|
33
35
  x_pos = @graph_left + (data_point * @graph_width)
34
- y_pos = @graph_top + (items_width * point_index) + padding + (items_width.to_f / 2.0).round
36
+ y_pos = @graph_top + (items_width * point_index) + padding + (items_width / 2.0)
35
37
 
36
38
  if row_index == 0
37
- Gruff::Renderer::Line.new(color: @marker_color).render(@graph_left, y_pos, @graph_left + @graph_width, y_pos)
39
+ Gruff::Renderer::Line.new(renderer, color: @marker_color).render(@graph_left, y_pos, @graph_left + @graph_width, y_pos)
38
40
  end
39
41
 
40
- Gruff::Renderer::Circle.new(color: data_row.color).render(x_pos, y_pos, x_pos + (item_width.to_f / 3.0).round, y_pos)
42
+ Gruff::Renderer::Circle.new(renderer, color: data_row.color).render(x_pos, y_pos, x_pos + (item_width / 3.0), y_pos)
41
43
 
42
44
  draw_label(y_pos, point_index)
43
45
  end
44
46
  end
45
47
  end
46
48
 
47
- protected
48
-
49
49
  # Instead of base class version, draws vertical background lines and label
50
50
  def draw_line_markers
51
51
  return if @hide_line_markers
52
52
 
53
53
  (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
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
56
 
57
- line_renderer = Gruff::Renderer::Line.new(color: @marker_color, shadow_color: @marker_shadow_color)
58
- line_renderer.render(x, @graph_bottom, x, @graph_bottom + 5)
57
+ Gruff::Renderer::Line.new(renderer, color: @marker_color).render(x, @graph_bottom, x, @graph_bottom + 5)
58
+ Gruff::Renderer::Line.new(renderer, color: @marker_shadow_color).render(x, @graph_bottom + 1, x, @graph_bottom + 6) if @marker_shadow_color
59
59
 
60
60
  unless @hide_line_numbers
61
61
  label = y_axis_label(marker_label, @increment)
62
- text_renderer = Gruff::Renderer::Text.new(label, font: @font, size: @marker_font_size, color: @font_color)
62
+ text_renderer = Gruff::Renderer::Text.new(renderer, label, font: @marker_font)
63
63
  text_renderer.add_to_render_queue(0, 0, x, @graph_bottom + (LABEL_MARGIN * 1.5), Magick::CenterGravity)
64
64
  end
65
65
  end
data/lib/gruff/font.rb ADDED
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Handle font setting to draw texts
4
+ class Gruff::Font
5
+ BOLD_PATH = File.expand_path(File.join(__FILE__, '../../../assets/fonts/Roboto-Bold.ttf')).freeze
6
+ private_constant :BOLD_PATH
7
+
8
+ REGULAR_PATH = File.expand_path(File.join(__FILE__, '../../../assets/fonts/Roboto-Regular.ttf')).freeze
9
+ private_constant :REGULAR_PATH
10
+
11
+ # Get/set font path.
12
+ attr_accessor :path
13
+
14
+ # Get/set font size.
15
+ attr_accessor :size
16
+
17
+ # Get/set font setting whether render bold text.
18
+ attr_accessor :bold
19
+
20
+ # Get/set font color.
21
+ attr_accessor :color
22
+
23
+ def initialize(path: nil, size: 20.0, bold: false, color: 'white')
24
+ @path = path
25
+ @bold = bold
26
+ @size = size
27
+ @color = color
28
+ end
29
+
30
+ # Get font weight.
31
+ # @return [Magick::WeightType] font weight
32
+ def weight
33
+ @bold ? Magick::BoldWeight : Magick::NormalWeight
34
+ end
35
+
36
+ # @private
37
+ def file_path
38
+ return @path if @path
39
+
40
+ @bold ? BOLD_PATH : REGULAR_PATH
41
+ end
42
+ end
@@ -41,17 +41,17 @@ class Gruff::BarConversion
41
41
  case @mode
42
42
  when 1
43
43
  # minimum value >= 0 ( only positive values )
44
- result[0] = @graph_top + @graph_height * (1 - data_point)
44
+ result[0] = @graph_top + (@graph_height * (1 - data_point))
45
45
  result[1] = @graph_top + @graph_height
46
46
  when 2
47
47
  # only negative values
48
48
  result[0] = @graph_top
49
- result[1] = @graph_top + @graph_height * (1 - data_point)
49
+ result[1] = @graph_top + (@graph_height * (1 - data_point))
50
50
  when 3
51
51
  # 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)
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
55
  else
56
56
  result[0] = 0.0
57
57
  result[1] = 0.0
@@ -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,31 +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
- if format.is_a?(Proc)
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 - 30 : left_y + 12
28
- 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
29
39
  end
30
40
  end
31
41
 
32
42
  # @private
33
43
  class SideBar < Base
34
- def prepare_rendering(format, bar_width = 0)
35
- left_x, _left_y, right_x, right_y = @coordinate
36
- if format.is_a?(Proc)
37
- val = format.call(@value)
38
- else
39
- val = sprintf(format || '%.2f', @value).commify
40
- 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)
41
47
 
42
- x = @value >= 0 ? right_x + 40 : left_x - 40
43
- yield x, right_y - bar_width / 2, val
48
+ x = @value >= 0 ? right_x + 10 : left_x - metrics.width - 10
49
+ yield x, left_y, val, metrics.width, metrics.height
44
50
  end
45
51
  end
46
52
 
@@ -56,12 +62,12 @@ module Gruff::BarValueLabel
56
62
  @bars[index] = bars
57
63
  end
58
64
 
59
- def prepare_rendering(format, bar_width = 0, &block)
65
+ def prepare_rendering(format, proc_text_metrics, &block)
60
66
  @bars.each do |bars|
61
67
  value = bars.sum(&:value)
62
68
  bar = bars.last
63
69
  bar_value_label = bar.class.new(bar.coordinate, value)
64
- bar_value_label.prepare_rendering(format, bar_width, &block)
70
+ bar_value_label.prepare_rendering(format, proc_text_metrics, &block)
65
71
  end
66
72
  end
67
73
  end
@@ -7,17 +7,18 @@ module Gruff::Base::StackedMixin
7
7
  # tsal: moved from Base 03 FEB 2007
8
8
  def calculate_maximum_by_stack
9
9
  # Get sum of each stack
10
- max_hash = {}
10
+ max_hash = Hash.new { |h, k| h[k] = 0.0 }
11
11
  store.data.each do |data_set|
12
12
  data_set.points.each_with_index do |data_point, i|
13
- max_hash[i] = 0.0 unless max_hash[i]
14
13
  max_hash[i] += data_point.to_f
15
14
  end
16
15
  end
17
16
 
18
17
  max_hash.each_key do |key|
19
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
20
20
  end
21
- self.minimum_value = 0
21
+
22
+ raise "Can't handle negative values in stacked graph" if minimum_value < 0
22
23
  end
23
24
  end
@@ -28,19 +28,21 @@ class Gruff::Histogram < Gruff::Bar
28
28
  @data = []
29
29
  end
30
30
 
31
- def initialize_ivars
31
+ def data(name, data_points = [], color = nil)
32
+ @data << [name, Array(data_points), color]
33
+ end
34
+
35
+ private
36
+
37
+ def initialize_attributes
32
38
  super
33
39
  @bin_width = 10
34
40
  @minimum_bin = nil
35
41
  @maximum_bin = nil
36
42
  end
37
- private :initialize_ivars
38
-
39
- def data(name, data_points = [], color = nil)
40
- @data << [name, data_points, color]
41
- end
43
+ private :initialize_attributes
42
44
 
43
- def draw
45
+ def setup_data
44
46
  @data.each do |(name, data_points, color)|
45
47
  bins, freqs = HistogramArray.new(data_points).histogram(bin_width: @bin_width, min: @minimum_bin, max: @maximum_bin)
46
48
  bins.each_with_index do |bin, index|