gruff 0.15.0-java → 0.18.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.
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 +9 -6
  47. data/lib/gruff/scene.rb +0 -208
  48. data/lib/gruff/store/custom_data.rb +0 -36
@@ -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
- self.minimum_value = 0
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
@@ -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
@@ -40,15 +40,18 @@ private
40
40
  @minimum_bin = nil
41
41
  @maximum_bin = nil
42
42
  end
43
- private :initialize_attributes
44
43
 
45
44
  def setup_data
46
45
  @data.each do |(name, data_points, color)|
47
- bins, freqs = HistogramArray.new(data_points).histogram(bin_width: @bin_width, min: @minimum_bin, max: @maximum_bin)
48
- bins.each_with_index do |bin, index|
49
- @labels[index] = bin
46
+ if data_points.empty?
47
+ store.add(name, [], color)
48
+ else
49
+ bins, freqs = HistogramArray.new(data_points).histogram(bin_width: @bin_width, min: @minimum_bin, max: @maximum_bin)
50
+ bins.each_with_index do |bin, index|
51
+ @labels[index] = bin
52
+ end
53
+ store.add(name, freqs, color)
50
54
  end
51
- store.add(name, freqs, color)
52
55
  end
53
56
 
54
57
  super
data/lib/gruff/line.rb CHANGED
@@ -25,7 +25,7 @@ class Gruff::Line < Gruff::Base
25
25
  attr_writer :line_width
26
26
  attr_writer :dot_radius
27
27
 
28
- # default is +'circle'+, other options include square.
28
+ # default is +'circle'+, other options include +square+ and +diamond+.
29
29
  attr_writer :dot_style
30
30
 
31
31
  # Hide parts of the graph to fit more data points, or for a different appearance.
@@ -40,6 +40,23 @@ class Gruff::Line < Gruff::Base
40
40
  # The number of vertical lines shown.
41
41
  attr_writer :marker_x_count
42
42
 
43
+ # Call with target pixel width of graph (+800+, +400+, +300+), and/or +false+ to omit lines (points only).
44
+ #
45
+ # g = Gruff::Line.new(400) # 400px wide with lines
46
+ # g = Gruff::Line.new(400, false) # 400px wide, no lines (for backwards compatibility)
47
+ # g = Gruff::Line.new(false) # Defaults to 800px wide, no lines (for backwards compatibility)
48
+ #
49
+ # The preferred way is to call {#hide_dots=} or {#hide_lines=} instead.
50
+ def initialize(*args)
51
+ raise ArgumentError, 'Wrong number of arguments' if args.length > 2
52
+
53
+ if args.empty? || (!args.first.is_a?(Numeric) && !args.first.is_a?(String))
54
+ super()
55
+ else
56
+ super args.shift
57
+ end
58
+ end
59
+
43
60
  # Get the value if somebody has defined it.
44
61
  def baseline_value
45
62
  if @reference_lines.key?(:baseline)
@@ -64,21 +81,28 @@ class Gruff::Line < Gruff::Base
64
81
  @reference_lines[:baseline][:color] = new_value
65
82
  end
66
83
 
67
- # Call with target pixel width of graph (+800+, +400+, +300+), and/or +false+ to omit lines (points only).
84
+ # Input the data in the graph.
68
85
  #
69
- # g = Gruff::Line.new(400) # 400px wide with lines
70
- # g = Gruff::Line.new(400, false) # 400px wide, no lines (for backwards compatibility)
71
- # g = Gruff::Line.new(false) # Defaults to 800px wide, no lines (for backwards compatibility)
86
+ # Parameters are an array where the first element is the name of the dataset
87
+ # and the value is an array of values to plot.
72
88
  #
73
- # The preferred way is to call {#hide_dots=} or {#hide_lines=} instead.
74
- def initialize(*args)
75
- raise ArgumentError, 'Wrong number of arguments' if args.length > 2
76
-
77
- if args.empty? || (!args.first.is_a?(Numeric) && !args.first.is_a?(String))
78
- super()
79
- else
80
- super args.shift
81
- end
89
+ # Can be called multiple times with different datasets for a multi-valued
90
+ # graph.
91
+ #
92
+ # If the color argument is nil, the next color from the default theme will
93
+ # be used.
94
+ #
95
+ # @param name [String, Symbol] The name of the dataset.
96
+ # @param data_points [Array] The array of dataset.
97
+ # @param color [String] The color for drawing graph of dataset.
98
+ #
99
+ # @note
100
+ # If you want to use a preset theme, you must set it before calling {#data}.
101
+ #
102
+ # @example
103
+ # data("Bart S.", [95, 45, 78, 89, 88, 76], '#ffcc00')
104
+ def data(name, data_points = [], color = nil)
105
+ store.add(name, nil, data_points, color)
82
106
  end
83
107
 
84
108
  # This method allows one to plot a dataset with both X and Y data.
@@ -131,7 +155,7 @@ class Gruff::Line < Gruff::Base
131
155
  raise ArgumentError, 'x_data_points.length != y_data_points.length!' if x_data_points.length != y_data_points.length
132
156
 
133
157
  # call the existing data routine for the x/y data.
134
- store.add(name, y_data_points, color, x_data_points)
158
+ store.add(name, x_data_points, y_data_points, color)
135
159
  end
136
160
 
137
161
  private
@@ -165,7 +189,7 @@ private
165
189
  end
166
190
 
167
191
  def draw_horizontal_reference_line(reference_line)
168
- level = @graph_top + (@graph_height - reference_line[:norm_value] * @graph_height)
192
+ level = @graph_top + (@graph_height - (reference_line[:norm_value] * @graph_height))
169
193
  draw_reference_line(reference_line, @graph_left, @graph_left + @graph_width, level, level)
170
194
  end
171
195
 
@@ -176,7 +200,8 @@ private
176
200
 
177
201
  def draw_graph
178
202
  # Check to see if more than one datapoint was given. NaN can result otherwise.
179
- @x_increment = column_count > 1 ? (@graph_width / (column_count - 1).to_f) : @graph_width
203
+ @x_increment = column_count > 1 ? @graph_width / (column_count - 1) : @graph_width
204
+ @x_increment = @x_increment.to_f
180
205
 
181
206
  @reference_lines.each_value do |curr_reference_line|
182
207
  draw_horizontal_reference_line(curr_reference_line) if curr_reference_line.key?(:norm_value)
@@ -189,22 +214,22 @@ private
189
214
  one_point = contains_one_point_only?(data_row)
190
215
 
191
216
  data_row.coordinates.each_with_index do |(x_data, y_data), index|
192
- if x_data.nil?
193
- # use the old method: equally spaced points along the x-axis
194
- new_x = @graph_left + (@x_increment * index)
195
- draw_label(new_x, index)
196
- else
197
- new_x = get_x_coord(x_data, @graph_width, @graph_left)
198
- @labels.each do |label_pos, _|
199
- draw_label(@graph_left + ((label_pos - @minimum_x_value) * @graph_width) / (@maximum_x_value - @minimum_x_value), label_pos)
217
+ new_x = begin
218
+ if x_data.nil?
219
+ # use the old method: equally spaced points along the x-axis
220
+ @graph_left + (@x_increment * index)
221
+ else
222
+ @graph_left + (x_data * @graph_width)
200
223
  end
201
224
  end
225
+ draw_label_for_x_data(x_data, new_x, index)
226
+
202
227
  unless y_data # we can't draw a line for a null data point, we can still label the axis though
203
228
  prev_x = prev_y = nil
204
229
  next
205
230
  end
206
231
 
207
- new_y = @graph_top + (@graph_height - y_data * @graph_height)
232
+ new_y = @graph_top + (@graph_height - (y_data * @graph_height))
208
233
 
209
234
  # Reset each time to avoid thin-line errors
210
235
  stroke_width = @line_width || clip_value_if_greater_than(@columns / (store.norm_data.first.y_points.size * 4), 5.0)
@@ -227,12 +252,12 @@ private
227
252
 
228
253
  def setup_data
229
254
  # Update the global min/max values for the x data
230
- @maximum_x_value ||= store.max_x
231
- @minimum_x_value ||= store.min_x
255
+ @maximum_x_value = (@maximum_x_value || store.max_x).to_f
256
+ @minimum_x_value = (@minimum_x_value || store.min_x).to_f
232
257
 
233
258
  # Deal with horizontal reference line values that exceed the existing minimum & maximum values.
234
- possible_maximums = [maximum_value.to_f]
235
- possible_minimums = [minimum_value.to_f]
259
+ possible_maximums = [maximum_value]
260
+ possible_minimums = [minimum_value]
236
261
 
237
262
  @reference_lines.each_value do |curr_reference_line|
238
263
  if curr_reference_line.key?(:value)
@@ -255,14 +280,14 @@ private
255
280
  def normalize
256
281
  return unless data_given?
257
282
 
258
- spread_x = @maximum_x_value.to_f - @minimum_x_value.to_f
283
+ spread_x = @maximum_x_value - @minimum_x_value
259
284
  store.normalize(minimum_x: @minimum_x_value, spread_x: spread_x, minimum_y: minimum_value, spread_y: @spread)
260
285
 
261
286
  @reference_lines.each_value do |curr_reference_line|
262
287
  # We only care about horizontal markers ... for normalization.
263
288
  # Vertical markers won't have a :value, they will have an :index
264
289
 
265
- curr_reference_line[:norm_value] = ((curr_reference_line[:value].to_f - minimum_value) / @spread.to_f) if curr_reference_line.key?(:value)
290
+ curr_reference_line[:norm_value] = ((curr_reference_line[:value].to_f - minimum_value) / @spread) if curr_reference_line.key?(:value)
266
291
  end
267
292
  end
268
293
 
@@ -270,22 +295,21 @@ private
270
295
  # do all of the stuff for the horizontal lines on the y-axis
271
296
  super
272
297
  return if @hide_line_markers
298
+ return unless @show_vertical_markers
273
299
 
274
300
  (0..@marker_x_count).each do |index|
275
- if @show_vertical_markers
276
- x = @graph_left + @graph_width - index * @graph_width / @marker_x_count
277
-
278
- Gruff::Renderer::Line.new(renderer, color: @marker_color).render(x, @graph_bottom, x, @graph_top)
279
- # If the user specified a marker shadow color, draw a shadow just below it
280
- if @marker_shadow_color
281
- Gruff::Renderer::Line.new(renderer, color: @marker_shadow_color).render(x + 1, @graph_bottom, x + 1, @graph_top)
282
- end
283
- end
301
+ draw_marker_vertical_line(@graph_left + @graph_width - (index * @graph_width / @marker_x_count))
284
302
  end
285
303
  end
286
304
 
287
- def get_x_coord(x_data_point, width, offset)
288
- x_data_point * width + offset
305
+ def draw_label_for_x_data(x_data, new_x, index)
306
+ if x_data.nil?
307
+ draw_label(new_x, index)
308
+ else
309
+ @labels.each do |label_pos, _|
310
+ draw_label(@graph_left + (((label_pos - @minimum_x_value) * @graph_width) / (@maximum_x_value - @minimum_x_value)), label_pos)
311
+ end
312
+ end
289
313
  end
290
314
 
291
315
  def contains_one_point_only?(data_row)
@@ -21,7 +21,7 @@ module Gruff
21
21
 
22
22
  @legend_labels = store.data.map(&:label)
23
23
 
24
- legend_height = scale_fontsize(store.length * calculate_line_height + @top_margin + @bottom_margin)
24
+ legend_height = scale((store.length * calculate_line_height) + @top_margin + @bottom_margin)
25
25
 
26
26
  @original_rows = @raw_rows
27
27
  @original_columns = @raw_columns
@@ -32,7 +32,7 @@ module Gruff
32
32
  @columns += calculate_legend_width + @left_margin
33
33
  else
34
34
  font = @legend_font.dup
35
- font.size = scale_fontsize(font.size)
35
+ font.size = scale(font.size)
36
36
  @rows += store.length * calculate_caps_height(font) * 1.7
37
37
  end
38
38
 
@@ -45,7 +45,7 @@ module Gruff
45
45
 
46
46
  def calculate_legend_width
47
47
  width = @legend_labels.map { |label| calculate_width(@legend_font, label) }.max
48
- scale_fontsize(width + 40 * 1.7)
48
+ scale(width + (40 * 1.7))
49
49
  end
50
50
 
51
51
  ##
@@ -69,17 +69,17 @@ module Gruff
69
69
 
70
70
  @legend_labels.each_with_index do |legend_label, index|
71
71
  # Draw label
72
- label = truncate_legend_label(legend_label)
73
- text_renderer = Gruff::Renderer::Text.new(renderer, label, font: @legend_font)
74
72
  x_offset = current_x_offset + (legend_square_width * 1.7)
73
+ label = truncate_legend_label(legend_label, x_offset)
74
+ text_renderer = Gruff::Renderer::Text.new(renderer, label, font: @legend_font)
75
75
  text_renderer.add_to_render_queue(@raw_columns, 1.0, x_offset, current_y_offset, Magick::WestGravity)
76
76
 
77
77
  # Now draw box with color of this dataset
78
78
  rect_renderer = Gruff::Renderer::Rectangle.new(renderer, color: store.data[index].color)
79
79
  rect_renderer.render(current_x_offset,
80
- current_y_offset - legend_square_width / 2.0,
80
+ current_y_offset - (legend_square_width / 2.0),
81
81
  current_x_offset + legend_square_width,
82
- current_y_offset + legend_square_width / 2.0)
82
+ current_y_offset + (legend_square_width / 2.0))
83
83
 
84
84
  current_y_offset += calculate_line_height
85
85
  end
@@ -90,17 +90,21 @@ module Gruff
90
90
  #
91
91
  # Department of Hu...
92
92
 
93
- def truncate_legend_label(label)
93
+ def truncate_legend_label(label, x_offset)
94
94
  truncated_label = label.to_s
95
95
 
96
96
  font = @legend_font.dup
97
- font.size = scale_fontsize(font.size)
98
- max_width = @columns - @legend_left_margin - @right_margin
99
- while calculate_width(font, truncated_label) > max_width && truncated_label.length > 1
97
+ font.size = scale(font.size)
98
+ max_width = @columns - scale(x_offset) - @right_margin
99
+ while calculate_width(font, "#{truncated_label}...") > max_width && truncated_label.length > 1
100
100
  truncated_label = truncated_label[0..truncated_label.length - 2]
101
101
  end
102
102
  truncated_label + (truncated_label.length < label.to_s.length ? '...' : '')
103
103
  end
104
+
105
+ def scale(value)
106
+ value * @scale
107
+ end
104
108
  end
105
109
  end
106
110
  end
data/lib/gruff/net.rb CHANGED
@@ -43,6 +43,21 @@ private
43
43
  @marker_font.bold = true
44
44
  end
45
45
 
46
+ def setup_drawing
47
+ @center_labels_over_point = false
48
+ super
49
+ end
50
+
51
+ def setup_graph_measurements
52
+ super
53
+
54
+ @radius = @graph_height / 2.0
55
+ @circle_radius = @dot_radius || clip_value_if_greater_than(@columns / (store.norm_data.first.points.size * 2.5), 5.0)
56
+ @stroke_width = @line_width || clip_value_if_greater_than(@columns / (store.norm_data.first.points.size * 4.0), 5.0)
57
+ @center_x = @graph_left + (@graph_width / 2.0)
58
+ @center_y = @graph_top + (@graph_height / 2.0) + 10
59
+ end
60
+
46
61
  def draw_graph
47
62
  store.norm_data.each do |data_row|
48
63
  data_row.points.each_with_index do |data_point, index|
@@ -50,15 +65,15 @@ private
50
65
 
51
66
  rad_pos = index * Math::PI * 2 / column_count
52
67
  point_distance = data_point * @radius
53
- start_x = @center_x + Math.sin(rad_pos) * point_distance
54
- start_y = @center_y - Math.cos(rad_pos) * point_distance
68
+ start_x = @center_x + (Math.sin(rad_pos) * point_distance)
69
+ start_y = @center_y - (Math.cos(rad_pos) * point_distance)
55
70
 
56
71
  next_index = index + 1 < data_row.points.length ? index + 1 : 0
57
72
 
58
73
  next_rad_pos = next_index * Math::PI * 2 / column_count
59
74
  next_point_distance = data_row.points[next_index] * @radius
60
- end_x = @center_x + Math.sin(next_rad_pos) * next_point_distance
61
- end_y = @center_y - Math.cos(next_rad_pos) * next_point_distance
75
+ end_x = @center_x + (Math.sin(next_rad_pos) * next_point_distance)
76
+ end_y = @center_y - (Math.cos(next_rad_pos) * next_point_distance)
62
77
 
63
78
  Gruff::Renderer::Line.new(renderer, color: data_row.color, width: @stroke_width).render(start_x, start_y, end_x, end_y)
64
79
 
@@ -70,16 +85,6 @@ private
70
85
  end
71
86
  end
72
87
 
73
- def setup_drawing
74
- super
75
-
76
- @radius = @graph_height / 2.0
77
- @circle_radius = @dot_radius || clip_value_if_greater_than(@columns / (store.norm_data.first.points.size * 2.5), 5.0)
78
- @stroke_width = @line_width || clip_value_if_greater_than(@columns / (store.norm_data.first.points.size * 4), 5.0)
79
- @center_x = @graph_left + (@graph_width / 2.0)
80
- @center_y = @graph_top + (@graph_height / 2.0) + 10
81
- end
82
-
83
88
  # the lines connecting in the center, with the first line vertical
84
89
  def draw_line_markers
85
90
  return if @hide_line_markers
@@ -89,7 +94,7 @@ private
89
94
  rad_pos = index * Math::PI * 2 / column_count
90
95
 
91
96
  Gruff::Renderer::Line.new(renderer, color: @marker_color)
92
- .render(@center_x, @center_y, @center_x + Math.sin(rad_pos) * @radius, @center_y - Math.cos(rad_pos) * @radius)
97
+ .render(@center_x, @center_y, @center_x + (Math.sin(rad_pos) * @radius), @center_y - (Math.cos(rad_pos) * @radius))
93
98
 
94
99
  marker_label = @labels[index] ? @labels[index].to_s : '000'
95
100
  draw_label(@center_x, @center_y, rad_pos * 360 / (2 * Math::PI), @radius + @circle_radius, marker_label)
@@ -99,9 +104,9 @@ private
99
104
  def draw_label(center_x, center_y, angle, radius, amount)
100
105
  x_offset = center_x # + 15 # The label points need to be tweaked slightly
101
106
  y_offset = center_y # + 0 # This one doesn't though
102
- x = x_offset + (radius + LABEL_MARGIN) * Math.sin(deg2rad(angle))
103
- y = y_offset - (radius + LABEL_MARGIN) * Math.cos(deg2rad(angle))
107
+ x = x_offset + ((radius + LABEL_MARGIN) * Math.sin(deg2rad(angle)))
108
+ y = y_offset - ((radius + LABEL_MARGIN) * Math.cos(deg2rad(angle)))
104
109
 
105
- draw_label_at(1.0, 1.0, x, y, amount, Magick::CenterGravity)
110
+ draw_label_at(1.0, 1.0, x, y, amount, gravity: Magick::CenterGravity)
106
111
  end
107
112
  end
@@ -3,6 +3,7 @@
3
3
  # @private
4
4
  module String::GruffCommify
5
5
  THOUSAND_SEPARATOR = ','
6
+ private_constant :THOUSAND_SEPARATOR
6
7
 
7
8
  refine String do
8
9
  # Taken from http://codesnippets.joyent.com/posts/show/330
data/lib/gruff/pie.rb CHANGED
@@ -9,14 +9,14 @@
9
9
  # g.data 'Hamburgers', 50
10
10
  # g.write("pie_keynote.png")
11
11
  #
12
- # To control where the pie chart starts creating slices, use {#zero_degree=}.
12
+ # To control where the pie chart starts creating slices, use {#start_degree=}.
13
13
  #
14
14
  class Gruff::Pie < Gruff::Base
15
- DEFAULT_TEXT_OFFSET_PERCENTAGE = 0.15
15
+ DEFAULT_TEXT_OFFSET_PERCENTAGE = 0.1
16
16
 
17
17
  # Can be used to make the pie start cutting slices at the top (-90.0)
18
- # or at another angle. Default is +0.0+, which starts at 3 o'clock.
19
- attr_writer :zero_degree
18
+ # or at another angle. Default is +-90.0+, which starts at 3 o'clock.
19
+ attr_writer :start_degree
20
20
 
21
21
  # Set the number output format lambda.
22
22
  attr_writer :label_formatting
@@ -26,25 +26,34 @@ class Gruff::Pie < Gruff::Base
26
26
  attr_writer :hide_labels_less_than
27
27
 
28
28
  # Affect the distance between the percentages and the pie chart.
29
- # Defaults to +0.15+.
29
+ # Defaults to +0.1+.
30
30
  attr_writer :text_offset_percentage
31
31
 
32
32
  ## Use values instead of percentages.
33
33
  attr_writer :show_values_as_labels
34
34
 
35
- private
35
+ # Set to +true+ if you want the data sets sorted with largest avg values drawn
36
+ # first. Default is +true+.
37
+ attr_writer :sort
36
38
 
37
- def initialize_store
38
- @store = Gruff::Store.new(Gruff::Store::CustomData)
39
+ # Can be used to make the pie start cutting slices at the top (-90.0)
40
+ # or at another angle. Default is +-90.0+, which starts at 3 o'clock.
41
+ # @deprecated Please use {#start_degree=} instead.
42
+ def zero_degree=(value)
43
+ warn '#zero_degree= is deprecated. Please use `start_degree` attribute instead'
44
+ @start_degree = value
39
45
  end
40
46
 
47
+ private
48
+
41
49
  def initialize_attributes
42
50
  super
43
- @zero_degree = 0.0
51
+ @start_degree = -90.0
44
52
  @hide_labels_less_than = 0.0
45
53
  @text_offset_percentage = DEFAULT_TEXT_OFFSET_PERCENTAGE
46
54
  @show_values_as_labels = false
47
55
  @marker_font.bold = true
56
+ @sort = true
48
57
 
49
58
  @hide_line_markers = true
50
59
  @hide_line_markers.freeze
@@ -52,6 +61,11 @@ private
52
61
  @label_formatting = ->(value, percentage) { @show_values_as_labels ? value.to_s : "#{percentage}%" }
53
62
  end
54
63
 
64
+ def setup_drawing
65
+ @center_labels_over_point = false
66
+ super
67
+ end
68
+
55
69
  def draw_graph
56
70
  slices.each do |slice|
57
71
  if slice.value > 0
@@ -83,7 +97,7 @@ private
83
97
  # Spatial Value-Related Methods
84
98
 
85
99
  def chart_degrees
86
- @chart_degrees ||= @zero_degree
100
+ @chart_degrees ||= @start_degree
87
101
  end
88
102
 
89
103
  attr_reader :graph_height
@@ -127,12 +141,12 @@ private
127
141
  if slice.percentage >= @hide_labels_less_than
128
142
  x, y = label_coordinates_for slice
129
143
  label = @label_formatting.call(slice.value, slice.percentage)
130
- draw_label_at(1.0, 1.0, x, y, label, Magick::CenterGravity)
144
+ draw_label_at(1.0, 1.0, x, y, label, gravity: Magick::CenterGravity)
131
145
  end
132
146
  end
133
147
 
134
148
  def label_coordinates_for(slice)
135
- angle = chart_degrees + slice.degrees / 2
149
+ angle = chart_degrees + (slice.degrees / 2.0)
136
150
 
137
151
  [x_label_coordinate(angle), y_label_coordinate(angle)]
138
152
  end
@@ -3,15 +3,17 @@
3
3
  module Gruff
4
4
  # @private
5
5
  class Renderer::Circle
6
- def initialize(renderer, color:, width: 1.0)
6
+ def initialize(renderer, color:, width: 1.0, opacity: 1.0)
7
7
  @renderer = renderer
8
8
  @color = color
9
9
  @width = width
10
+ @opacity = opacity
10
11
  end
11
12
 
12
13
  def render(origin_x, origin_y, perim_x, perim_y)
13
14
  @renderer.draw.push
14
15
  @renderer.draw.fill(@color)
16
+ @renderer.draw.fill_opacity(@opacity)
15
17
  @renderer.draw.stroke(@color)
16
18
  @renderer.draw.stroke_width(@width)
17
19
  @renderer.draw.circle(origin_x, origin_y, perim_x, perim_y)
@@ -3,17 +3,18 @@
3
3
  module Gruff
4
4
  # @private
5
5
  class Renderer::DashLine
6
- def initialize(renderer, color:, width:)
6
+ def initialize(renderer, color:, width:, dasharray: [10, 20])
7
7
  @renderer = renderer
8
8
  @color = color
9
9
  @width = width
10
+ @dasharray = dasharray
10
11
  end
11
12
 
12
13
  def render(start_x, start_y, end_x, end_y)
13
14
  @renderer.draw.push
14
15
  @renderer.draw.stroke_color(@color)
15
16
  @renderer.draw.fill_opacity(0.0)
16
- @renderer.draw.stroke_dasharray(10, 20)
17
+ @renderer.draw.stroke_dasharray(*@dasharray)
17
18
  @renderer.draw.stroke_width(@width)
18
19
  @renderer.draw.line(start_x, start_y, end_x, end_y)
19
20
  @renderer.draw.pop
@@ -3,37 +3,50 @@
3
3
  module Gruff
4
4
  # @private
5
5
  class Renderer::Dot
6
- def initialize(renderer, style, color:, width: 1.0)
6
+ def initialize(renderer, style, color:, width: 1.0, opacity: 1.0)
7
7
  @renderer = renderer
8
8
  @style = style
9
9
  @color = color
10
10
  @width = width
11
+ @opacity = opacity
11
12
  end
12
13
 
13
- def render(new_x, new_y, circle_radius)
14
- # @renderer.draw.push # TODO
14
+ def render(new_x, new_y, radius)
15
+ @renderer.draw.push
15
16
  @renderer.draw.stroke_width(@width)
16
17
  @renderer.draw.stroke(@color)
17
18
  @renderer.draw.fill(@color)
18
- if @style.to_s == 'square'
19
- square(new_x, new_y, circle_radius)
19
+ @renderer.draw.fill_opacity(@opacity)
20
+ case @style.to_sym
21
+ when :square
22
+ square(new_x, new_y, radius)
23
+ when :diamond
24
+ diamond(new_x, new_y, radius)
20
25
  else
21
- circle(new_x, new_y, circle_radius)
26
+ circle(new_x, new_y, radius)
22
27
  end
23
- # @renderer.draw.pop # TODO
28
+ @renderer.draw.pop
24
29
  end
25
30
 
26
- def circle(new_x, new_y, circle_radius)
27
- @renderer.draw.circle(new_x, new_y, new_x - circle_radius, new_y)
31
+ def circle(new_x, new_y, radius)
32
+ @renderer.draw.circle(new_x, new_y, new_x - radius, new_y)
28
33
  end
29
34
 
30
- def square(new_x, new_y, circle_radius)
31
- offset = (circle_radius * 0.8).to_i
32
- corner1 = new_x - offset
33
- corner2 = new_y - offset
34
- corner3 = new_x + offset
35
- corner4 = new_y + offset
35
+ def square(new_x, new_y, radius)
36
+ corner1 = new_x - radius
37
+ corner2 = new_y - radius
38
+ corner3 = new_x + radius
39
+ corner4 = new_y + radius
36
40
  @renderer.draw.rectangle(corner1, corner2, corner3, corner4)
37
41
  end
42
+
43
+ def diamond(new_x, new_y, radius)
44
+ polygon = []
45
+ polygon += [new_x - radius, new_y]
46
+ polygon += [new_x, new_y + radius]
47
+ polygon += [new_x + radius, new_y]
48
+ polygon += [new_x, new_y - radius]
49
+ @renderer.draw.polygon(*polygon)
50
+ end
38
51
  end
39
52
  end
@@ -5,16 +5,14 @@ module Gruff
5
5
  class Renderer::Line
6
6
  EPSILON = 0.001
7
7
 
8
- def initialize(renderer, color:, width: nil, shadow_color: nil)
8
+ def initialize(renderer, color:, width: nil)
9
9
  @renderer = renderer
10
10
  @color = color
11
11
  @width = width
12
- @shadow_color = shadow_color
13
12
  end
14
13
 
15
14
  def render(start_x, start_y, end_x, end_y)
16
15
  render_line(start_x, start_y, end_x, end_y, @color)
17
- render_line(start_x, start_y + 1, end_x, end_y + 1, @shadow_color) if @shadow_color
18
16
  end
19
17
 
20
18
  private
@@ -3,14 +3,18 @@
3
3
  module Gruff
4
4
  # @private
5
5
  class Renderer::Rectangle
6
- def initialize(renderer, color: nil)
6
+ def initialize(renderer, color: nil, width: 1.0, opacity: 1.0)
7
7
  @renderer = renderer
8
8
  @color = color
9
+ @width = width
10
+ @opacity = opacity
9
11
  end
10
12
 
11
13
  def render(upper_left_x, upper_left_y, lower_right_x, lower_right_y)
12
14
  @renderer.draw.push
13
- @renderer.draw.stroke('transparent')
15
+ @renderer.draw.stroke_width(@width)
16
+ @renderer.draw.stroke(@color) if @width > 1.0
17
+ @renderer.draw.fill_opacity(@opacity)
14
18
  @renderer.draw.fill(@color) if @color
15
19
  @renderer.draw.rectangle(upper_left_x, upper_left_y, lower_right_x, lower_right_y)
16
20
  @renderer.draw.pop