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
@@ -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