gruff 0.13.0-java → 0.16.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 (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 +15 -32
  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/base.rb CHANGED
@@ -54,20 +54,18 @@ module Gruff
54
54
  # { 0 => 2005, 3 => 2006, 5 => 2007, 7 => 2008 }
55
55
  attr_writer :labels
56
56
 
57
- # Used internally for spacing.
58
- #
59
- # By default, labels are centered over the point they represent.
60
- attr_writer :center_labels_over_point
61
-
62
- # Used internally for horizontal graph types. Default is +false+.
63
- attr_writer :has_left_labels
64
-
65
57
  # Set a label for the bottom of the graph.
66
58
  attr_writer :x_axis_label
67
59
 
68
60
  # Set a label for the left side of the graph.
69
61
  attr_writer :y_axis_label
70
62
 
63
+ # Allow passing lambda to format labels for x axis.
64
+ attr_writer :x_axis_label_format
65
+
66
+ # Allow passing lambda to format labels for y axis.
67
+ attr_writer :y_axis_label_format
68
+
71
69
  # Set increment of the vertical marking lines.
72
70
  attr_writer :x_axis_increment
73
71
 
@@ -93,15 +91,6 @@ module Gruff
93
91
  # Set the large title of the graph displayed at the top.
94
92
  attr_writer :title
95
93
 
96
- # Same as {#font=} but for the title.
97
- attr_writer :title_font
98
-
99
- # Specifies whether to draw the title bolded or not. Default is +true+.
100
- attr_writer :bold_title
101
-
102
- # Specifies the text color.
103
- attr_writer :font_color
104
-
105
94
  # Prevent drawing of line markers. Default is +false+.
106
95
  attr_writer :hide_line_markers
107
96
 
@@ -118,21 +107,9 @@ module Gruff
118
107
  # to +"No Data."+.
119
108
  attr_writer :no_data_message
120
109
 
121
- # Set the font size of the large title at the top of the graph. Default is +36+.
122
- attr_writer :title_font_size
123
-
124
- # Optionally set the size of the font. Based on an 800x600px graph.
125
- # Default is +20+.
126
- #
127
- # Will be scaled down if the graph is smaller than 800px wide.
128
- attr_writer :legend_font_size
129
-
130
110
  # Display the legend under the graph. Default is +false+.
131
111
  attr_writer :legend_at_bottom
132
112
 
133
- # The font size of the labels around the graph. Default is +21+.
134
- attr_writer :marker_font_size
135
-
136
113
  # Set the color of the auxiliary lines.
137
114
  attr_writer :marker_color
138
115
 
@@ -156,12 +133,6 @@ module Gruff
156
133
  # Will be scaled down if graph is smaller than 800px wide.
157
134
  attr_writer :legend_box_size
158
135
 
159
- # Allow passing lambdas to format labels for x axis.
160
- attr_writer :x_axis_label_format
161
-
162
- # Allow passing lambdas to format labels for y axis.
163
- attr_writer :y_axis_label_format
164
-
165
136
  # If one numerical argument is given, the graph is drawn at 4/3 ratio
166
137
  # according to the given width (+800+ results in 800x600, +400+ gives 400x300,
167
138
  # etc.).
@@ -180,8 +151,11 @@ module Gruff
180
151
  @columns.freeze
181
152
  @rows.freeze
182
153
 
154
+ @has_left_labels = false
155
+ @center_labels_over_point = true
156
+
183
157
  initialize_graph_scale
184
- initialize_ivars
158
+ initialize_attributes
185
159
  initialize_store
186
160
 
187
161
  self.theme = Themes::KEYNOTE
@@ -209,7 +183,7 @@ module Gruff
209
183
  #
210
184
  # This makes it possible to set defaults in a subclass but still allow
211
185
  # developers to change this values in their program.
212
- def initialize_ivars
186
+ def initialize_attributes
213
187
  @marker_count = nil
214
188
  @maximum_value = @minimum_value = nil
215
189
  @labels = {}
@@ -217,13 +191,9 @@ module Gruff
217
191
  @sorted_drawing = false
218
192
  @title = nil
219
193
 
220
- @title_font = nil
221
- @font = nil
222
- @bold_title = true
223
-
224
- @marker_font_size = 21.0
225
- @legend_font_size = 20.0
226
- @title_font_size = 36.0
194
+ @title_font = Gruff::Font.new(size: 36.0, bold: true)
195
+ @marker_font = Gruff::Font.new(size: 21.0)
196
+ @legend_font = Gruff::Font.new(size: 20.0)
227
197
 
228
198
  @top_margin = @bottom_margin = @left_margin = @right_margin = DEFAULT_MARGIN
229
199
  @legend_margin = LEGEND_MARGIN
@@ -234,8 +204,6 @@ module Gruff
234
204
  @no_data_message = 'No Data'
235
205
 
236
206
  @hide_line_markers = @hide_legend = @hide_title = @hide_line_numbers = @legend_at_bottom = false
237
- @center_labels_over_point = true
238
- @has_left_labels = false
239
207
  @label_stagger_height = 0
240
208
  @label_max_size = 0
241
209
  @label_truncation_style = :absolute
@@ -247,7 +215,7 @@ module Gruff
247
215
  @x_axis_label_format = nil
248
216
  @y_axis_label_format = nil
249
217
  end
250
- protected :initialize_ivars
218
+ protected :initialize_attributes
251
219
 
252
220
  # Sets the top, bottom, left and right margins to +margin+.
253
221
  #
@@ -262,8 +230,62 @@ module Gruff
262
230
  # @param font_path [String] The path to font.
263
231
  #
264
232
  def font=(font_path)
265
- @font = font_path
266
- Gruff::Renderer.font = @font
233
+ @title_font.path = font_path unless @title_font.path
234
+ @marker_font.path = font_path
235
+ @legend_font.path = font_path
236
+ end
237
+
238
+ # Same as {#font=} but for the title.
239
+ #
240
+ # @param font_path [String] The path to font.
241
+ #
242
+ def title_font=(font_path)
243
+ @title_font.path = font_path
244
+ end
245
+
246
+ # Set the font size of the large title at the top of the graph. Default is +36+.
247
+ #
248
+ # @param value [Numeric] title font size
249
+ #
250
+ def title_font_size=(value)
251
+ @title_font.size = value
252
+ end
253
+
254
+ # The font size of the labels around the graph. Default is +21+.
255
+ #
256
+ # @param value [Numeric] marker font size
257
+ #
258
+ def marker_font_size=(value)
259
+ @marker_font.size = value
260
+ end
261
+
262
+ # Optionally set the size of the font. Based on an 800x600px graph.
263
+ # Default is +20+.
264
+ #
265
+ # Will be scaled down if the graph is smaller than 800px wide.
266
+ #
267
+ # @param value [Numeric] legend font size
268
+ #
269
+ def legend_font_size=(value)
270
+ @legend_font.size = value
271
+ end
272
+
273
+ # Specifies whether to draw the title bolded or not. Default is +true+.
274
+ #
275
+ # @param value [Boolean] specifies whether to draw the title bolded or not.
276
+ #
277
+ def bold_title=(value)
278
+ @title_font.bold = value
279
+ end
280
+
281
+ # Specifies the text color.
282
+ #
283
+ # @param value [String] color
284
+ #
285
+ def font_color=(value)
286
+ @title_font.color = value
287
+ @marker_font.color = value
288
+ @legend_font.color = value
267
289
  end
268
290
 
269
291
  # Add a color to the list of available colors for lines.
@@ -332,12 +354,13 @@ module Gruff
332
354
  }
333
355
  @theme_options = defaults.merge options
334
356
 
357
+ self.marker_color = @theme_options[:marker_color]
358
+ self.font_color = @theme_options[:font_color] || @marker_color
359
+
335
360
  @colors = @theme_options[:colors]
336
- @marker_color = @theme_options[:marker_color]
337
361
  @marker_shadow_color = @theme_options[:marker_shadow_color]
338
- @font_color = @theme_options[:font_color] || @marker_color
339
362
 
340
- Gruff::Renderer.setup(@columns, @rows, @font, @scale, @theme_options)
363
+ @renderer = Gruff::Renderer.new(@columns, @rows, @scale, @theme_options)
341
364
  end
342
365
 
343
366
  # Apply Apple's keynote theme.
@@ -399,7 +422,7 @@ module Gruff
399
422
  #
400
423
  # Set it after you have given all your data to the graph object.
401
424
  def minimum_value
402
- @minimum_value || store.min
425
+ (@minimum_value || store.min).to_f
403
426
  end
404
427
  attr_writer :minimum_value
405
428
 
@@ -409,7 +432,7 @@ module Gruff
409
432
  # If you use this, you must set it after you have given all your data to
410
433
  # the graph object.
411
434
  def maximum_value
412
- @maximum_value || store.max
435
+ (@maximum_value || store.max).to_f
413
436
  end
414
437
  attr_writer :maximum_value
415
438
 
@@ -439,8 +462,8 @@ module Gruff
439
462
  def to_image
440
463
  @to_image ||= begin
441
464
  draw
442
- Gruff::Renderer.finish
443
- Gruff::Renderer.instance.image
465
+ renderer.finish
466
+ renderer.image
444
467
  end
445
468
  end
446
469
 
@@ -456,32 +479,34 @@ module Gruff
456
479
  end
457
480
  end
458
481
 
459
- protected
460
-
461
- # Overridden by subclasses to do the actual plotting of the graph.
462
- #
463
- # Subclasses should start by calling super() for this method.
482
+ # Draw a graph.
464
483
  def draw
484
+ setup_data
485
+
465
486
  # Maybe should be done in one of the following functions for more granularity.
466
487
  unless data_given?
467
488
  draw_no_data
468
489
  return
469
490
  end
470
491
 
471
- setup_data
472
492
  setup_drawing
473
493
 
474
494
  draw_legend
475
495
  draw_line_markers
476
496
  draw_axis_labels
477
497
  draw_title
498
+ draw_graph
478
499
  end
479
500
 
501
+ protected
502
+
503
+ attr_reader :renderer
504
+
480
505
  # Perform data manipulation before calculating chart measurements
481
506
  def setup_data # :nodoc:
482
507
  if @y_axis_increment && !@hide_line_markers
483
- self.maximum_value = [@y_axis_increment, maximum_value, (maximum_value.to_f / @y_axis_increment).round * @y_axis_increment].max
484
- self.minimum_value = [minimum_value, (minimum_value.to_f / @y_axis_increment).round * @y_axis_increment].min
508
+ self.maximum_value = [@y_axis_increment, maximum_value, (maximum_value / @y_axis_increment).round * @y_axis_increment].max
509
+ self.minimum_value = [minimum_value, (minimum_value / @y_axis_increment).round * @y_axis_increment].min
485
510
  end
486
511
  end
487
512
 
@@ -520,7 +545,7 @@ module Gruff
520
545
  @marker_count ||= begin
521
546
  count = nil
522
547
  (3..7).each do |lines|
523
- if @spread.to_f % lines == 0.0
548
+ if @spread % lines == 0.0
524
549
  count = lines and break
525
550
  end
526
551
  end
@@ -533,8 +558,8 @@ module Gruff
533
558
  store.normalize(minimum: minimum_value, spread: @spread)
534
559
  end
535
560
 
536
- def calculate_spread # :nodoc:
537
- @spread = maximum_value.to_f - minimum_value.to_f
561
+ def calculate_spread
562
+ @spread = maximum_value - minimum_value
538
563
  @spread = @spread > 0 ? @spread : 1
539
564
  end
540
565
 
@@ -547,28 +572,24 @@ module Gruff
547
572
  end
548
573
 
549
574
  def hide_left_label_area?
550
- @hide_line_markers
575
+ @hide_line_markers && @y_axis_label.nil?
551
576
  end
552
577
 
553
578
  def hide_bottom_label_area?
554
- @hide_line_markers
579
+ @hide_line_markers && @x_axis_label.nil?
555
580
  end
556
581
 
557
582
  ##
558
583
  # Calculates size of drawable area, general font dimensions, etc.
559
584
 
560
585
  def setup_graph_measurements
561
- @marker_caps_height = setup_marker_caps_height
562
- @title_caps_height = setup_title_caps_height
563
- @legend_caps_height = setup_legend_caps_height
564
-
565
586
  margin_on_right = graph_right_margin
566
587
  @graph_right = @raw_columns - margin_on_right
567
588
  @graph_left = setup_left_margin
568
589
  @graph_top = setup_top_margin
569
590
  @graph_bottom = setup_bottom_margin
570
591
 
571
- @graph_width = @raw_columns - @graph_left - margin_on_right
592
+ @graph_width = @graph_right - @graph_left
572
593
  @graph_height = @graph_bottom - @graph_top
573
594
  end
574
595
 
@@ -578,17 +599,16 @@ module Gruff
578
599
  # X Axis
579
600
  # Centered vertically and horizontally by setting the
580
601
  # height to 1.0 and the width to the width of the graph.
581
- x_axis_label_y_coordinate = @graph_bottom + LABEL_MARGIN + @marker_caps_height
602
+ x_axis_label_y_coordinate = @graph_bottom + (LABEL_MARGIN * 2) + marker_caps_height
582
603
 
583
- # TODO: Center between graph area
584
- text_renderer = Gruff::Renderer::Text.new(@x_axis_label, font: @font, size: @marker_font_size, color: @font_color)
604
+ text_renderer = Gruff::Renderer::Text.new(renderer, @x_axis_label, font: @marker_font)
585
605
  text_renderer.add_to_render_queue(@raw_columns, 1.0, 0.0, x_axis_label_y_coordinate)
586
606
  end
587
607
 
588
608
  if @y_axis_label
589
609
  # Y Axis, rotated vertically
590
- text_renderer = Gruff::Renderer::Text.new(@y_axis_label, font: @font, size: @marker_font_size, color: @font_color, rotation: -90)
591
- text_renderer.add_to_render_queue(1.0, @raw_rows, @left_margin + @marker_caps_height / 2.0, 0.0, Magick::CenterGravity)
610
+ text_renderer = Gruff::Renderer::Text.new(renderer, @y_axis_label, font: @marker_font, rotation: -90)
611
+ text_renderer.add_to_render_queue(1.0, @raw_rows, @left_margin + (marker_caps_height / 2.0), 0.0, Magick::CenterGravity)
592
612
  end
593
613
  end
594
614
 
@@ -596,19 +616,19 @@ module Gruff
596
616
  def draw_line_markers
597
617
  return if @hide_line_markers
598
618
 
599
- increment_scaled = @graph_height.to_f / (@spread / @increment)
619
+ increment_scaled = @graph_height / (@spread / @increment)
600
620
 
601
621
  # Draw horizontal line markers and annotate with numbers
602
622
  (0..marker_count).each do |index|
603
- y = @graph_top + @graph_height - index.to_f * increment_scaled
623
+ y = @graph_top + @graph_height - (index * increment_scaled)
604
624
 
605
- line_renderer = Gruff::Renderer::Line.new(color: @marker_color, shadow_color: @marker_shadow_color)
606
- line_renderer.render(@graph_left, y, @graph_right, y)
625
+ Gruff::Renderer::Line.new(renderer, color: @marker_color).render(@graph_left, y, @graph_right, y)
626
+ Gruff::Renderer::Line.new(renderer, color: @marker_shadow_color).render(@graph_left, y + 1, @graph_right, y + 1) if @marker_shadow_color
607
627
 
608
628
  unless @hide_line_numbers
609
- marker_label = BigDecimal(index.to_s) * BigDecimal(@increment.to_s) + BigDecimal(minimum_value.to_s)
629
+ marker_label = (BigDecimal(index.to_s) * BigDecimal(@increment.to_s)) + BigDecimal(minimum_value.to_s)
610
630
  label = y_axis_label(marker_label, @increment)
611
- text_renderer = Gruff::Renderer::Text.new(label, font: @font, size: @marker_font_size, color: @font_color)
631
+ text_renderer = Gruff::Renderer::Text.new(renderer, label, font: @marker_font)
612
632
  text_renderer.add_to_render_queue(@graph_left - LABEL_MARGIN, 1.0, 0.0, y, Magick::EastGravity)
613
633
  end
614
634
  end
@@ -626,45 +646,46 @@ module Gruff
626
646
 
627
647
  legend_labels = store.data.map(&:label)
628
648
  legend_square_width = @legend_box_size # small square with color of this item
629
- label_widths = calculate_legend_label_widths_for_each_line(legend_labels, legend_square_width)
649
+ legend_label_lines = calculate_legend_label_widths_for_each_line(legend_labels, legend_square_width)
650
+ line_height = [legend_caps_height, legend_square_width].max + @legend_margin
630
651
 
631
- current_x_offset = center(label_widths.first.sum)
632
652
  current_y_offset = begin
633
653
  if @legend_at_bottom
634
- @graph_bottom + @legend_margin + @legend_caps_height + LABEL_MARGIN
654
+ @graph_bottom + @legend_margin + legend_caps_height + LABEL_MARGIN + (@x_axis_label ? (LABEL_MARGIN * 2) + marker_caps_height : 0)
635
655
  else
636
- hide_title? ? @top_margin + @title_margin : @top_margin + @title_margin + @title_caps_height
656
+ hide_title? ? @top_margin + @title_margin : @top_margin + @title_margin + title_caps_height
637
657
  end
638
658
  end
639
659
 
640
- legend_labels.each_with_index do |legend_label, index|
641
- next if legend_label.empty?
642
-
643
- # Draw label
644
- text_renderer = Gruff::Renderer::Text.new(legend_label, font: @font, size: @legend_font_size, color: @font_color)
645
- text_renderer.add_to_render_queue(@raw_columns, 1.0, current_x_offset + (legend_square_width * 1.7), current_y_offset, Magick::WestGravity)
646
-
647
- # Now draw box with color of this dataset
648
- rect_renderer = Gruff::Renderer::Rectangle.new(color: store.data[index].color)
649
- rect_renderer.render(current_x_offset,
650
- current_y_offset - legend_square_width / 2.0,
651
- current_x_offset + legend_square_width,
652
- current_y_offset + legend_square_width / 2.0)
653
-
654
- width = calculate_width(@legend_font_size, legend_label)
655
- current_x_offset += width + (legend_square_width * 2.7)
656
- label_widths.first.shift
657
-
658
- # Handle wrapping
659
- if label_widths.first.empty?
660
- label_widths.shift
661
- current_x_offset = center(label_widths.first.sum) unless label_widths.empty?
662
- line_height = [@legend_caps_height, legend_square_width].max + @legend_margin
663
- unless label_widths.empty?
664
- # Wrap to next line and shrink available graph dimensions
665
- current_y_offset += line_height
660
+ index = 0
661
+ legend_label_lines.each do |(legend_labels_width, legend_labels_line)|
662
+ current_x_offset = center(legend_labels_width)
663
+
664
+ legend_labels_line.each do |legend_label|
665
+ unless legend_label.empty?
666
+ legend_label_width = calculate_width(@legend_font, legend_label)
667
+
668
+ # Draw label
669
+ text_renderer = Gruff::Renderer::Text.new(renderer, legend_label, font: @legend_font)
670
+ text_renderer.add_to_render_queue(legend_label_width,
671
+ legend_square_width,
672
+ current_x_offset + (legend_square_width * 1.7),
673
+ current_y_offset,
674
+ Magick::CenterGravity)
675
+
676
+ # Now draw box with color of this dataset
677
+ rect_renderer = Gruff::Renderer::Rectangle.new(renderer, color: store.data[index].color)
678
+ rect_renderer.render(current_x_offset,
679
+ current_y_offset,
680
+ current_x_offset + legend_square_width,
681
+ current_y_offset + legend_square_width)
682
+
683
+ current_x_offset += legend_label_width + (legend_square_width * 2.7)
666
684
  end
685
+ index += 1
667
686
  end
687
+
688
+ current_y_offset += line_height
668
689
  end
669
690
  end
670
691
 
@@ -672,32 +693,24 @@ module Gruff
672
693
  def draw_title
673
694
  return if hide_title?
674
695
 
675
- font = @title_font || @font
676
- font_weight = @bold_title ? Magick::BoldWeight : Magick::NormalWeight
677
- font_size = @title_font_size
678
-
679
- metrics = Renderer::Text.metrics(@title, font, font_size, font_weight)
696
+ metrics = text_metrics(@title_font, @title)
680
697
  if metrics.width > @raw_columns
681
- font_size = font_size * (@raw_columns / metrics.width) * 0.95
698
+ @title_font.size = @title_font.size * (@raw_columns / metrics.width) * 0.95
682
699
  end
683
- text_renderer = Gruff::Renderer::Text.new(@title, font: font, size: font_size, color: @font_color, weight: font_weight)
700
+
701
+ text_renderer = Gruff::Renderer::Text.new(renderer, @title, font: @title_font)
684
702
  text_renderer.add_to_render_queue(@raw_columns, 1.0, 0, @top_margin)
685
703
  end
686
704
 
687
705
  # Draws column labels below graph, centered over x_offset
688
- #--
689
- # TODO Allow WestGravity as an option
690
- def draw_label(x_offset, index, gravity = Magick::NorthGravity)
706
+ def draw_label(x_offset, index, gravity = Magick::NorthGravity, &block)
691
707
  draw_unique_label(index) do
692
708
  y_offset = @graph_bottom + LABEL_MARGIN
693
-
694
- # TESTME
695
- # FIXME: Consider chart types other than bar
696
- # TODO: See if index.odd? is the best stragegy
697
709
  y_offset += @label_stagger_height if index.odd?
698
710
 
699
711
  if x_offset >= @graph_left && x_offset <= @graph_right
700
712
  draw_label_at(1.0, 1.0, x_offset, y_offset, @labels[index], gravity)
713
+ yield if block
701
714
  end
702
715
  end
703
716
  end
@@ -714,24 +727,30 @@ module Gruff
714
727
 
715
728
  def draw_label_at(width, height, x, y, text, gravity = Magick::NorthGravity)
716
729
  label_text = truncate_label_text(text)
717
- text_renderer = Gruff::Renderer::Text.new(label_text, font: @font, size: @marker_font_size, color: @font_color)
730
+ text_renderer = Gruff::Renderer::Text.new(renderer, label_text, font: @marker_font)
718
731
  text_renderer.add_to_render_queue(width, height, x, y, gravity)
719
732
  end
720
733
 
721
734
  # Draws the data value over the data point in bar graphs
722
- def draw_value_label(x_offset, y_offset, data_point, bar_value = false)
723
- return if @hide_line_markers && !bar_value
735
+ def draw_value_label(width, height, x_offset, y_offset, data_point, gravity = Magick::CenterGravity)
736
+ return if @hide_line_markers
724
737
 
725
- text_renderer = Gruff::Renderer::Text.new(data_point, font: @font, size: @marker_font_size, color: @font_color)
726
- text_renderer.add_to_render_queue(1.0, 1.0, x_offset, y_offset)
738
+ draw_label_at(width, height, x_offset, y_offset, data_point, gravity)
727
739
  end
728
740
 
729
741
  # Shows an error message because you have no data.
730
742
  def draw_no_data
731
- text_renderer = Gruff::Renderer::Text.new(@no_data_message, font: @font, size: 80, color: @font_color)
743
+ font = @title_font.dup
744
+ font.size = 80
745
+ font.bold = false
746
+ text_renderer = Gruff::Renderer::Text.new(renderer, @no_data_message, font: font)
732
747
  text_renderer.render(@raw_columns, @raw_rows, 0, 0, Magick::CenterGravity)
733
748
  end
734
749
 
750
+ def draw_graph
751
+ raise 'Should implement this method at inherited class.'
752
+ end
753
+
735
754
  # Resets everything to defaults (except data).
736
755
  def reset_themes
737
756
  @theme_options = {}
@@ -747,7 +766,7 @@ module Gruff
747
766
  end
748
767
 
749
768
  def clip_value_if_greater_than(value, max_value) # :nodoc:
750
- (value > max_value) ? max_value : value
769
+ value > max_value ? max_value : value
751
770
  end
752
771
 
753
772
  def significant(i) # :nodoc:
@@ -793,16 +812,16 @@ module Gruff
793
812
 
794
813
  private
795
814
 
796
- def setup_marker_caps_height
797
- hide_bottom_label_area? ? 0 : calculate_caps_height(@marker_font_size)
815
+ def marker_caps_height
816
+ hide_bottom_label_area? ? 0 : calculate_caps_height(@marker_font)
798
817
  end
799
818
 
800
- def setup_title_caps_height
801
- hide_title? ? 0 : calculate_caps_height(@title_font_size) * @title.lines.to_a.size
819
+ def title_caps_height
820
+ hide_title? ? 0 : calculate_caps_height(@title_font) * @title.lines.to_a.size
802
821
  end
803
822
 
804
- def setup_legend_caps_height
805
- @hide_legend ? 0 : calculate_caps_height(@legend_font_size)
823
+ def legend_caps_height
824
+ @hide_legend ? 0 : calculate_caps_height(@legend_font)
806
825
  end
807
826
 
808
827
  def graph_right_margin
@@ -813,7 +832,11 @@ module Gruff
813
832
  # Make space for half the width of the rightmost column label.
814
833
  # Might be greater than the number of columns if between-style bar markers are used.
815
834
  last_label = @labels.keys.max.to_i
816
- (last_label >= (column_count - 1) && @center_labels_over_point) ? calculate_width(@marker_font_size, @labels[last_label]) / 2.0 : 0
835
+ if last_label >= (column_count - 1) && @center_labels_over_point
836
+ calculate_width(@marker_font, truncate_label_text(@labels[last_label])) / 2.0
837
+ else
838
+ 0
839
+ end
817
840
  end
818
841
 
819
842
  def setup_left_margin
@@ -821,34 +844,32 @@ module Gruff
821
844
 
822
845
  text = begin
823
846
  if @has_left_labels
824
- @labels.values.reduce('') { |value, memo| (value.to_s.length > memo.to_s.length) ? value : memo }
847
+ @labels.values.reduce('') { |value, memo| value.to_s.length > memo.to_s.length ? value : memo }
825
848
  else
826
- y_axis_label(maximum_value.to_f, @increment)
849
+ y_axis_label(maximum_value, @increment)
827
850
  end
828
851
  end
829
- longest_left_label_width = calculate_width(@marker_font_size, truncate_label_text(text))
830
- longest_left_label_width *= 1.25 if @has_left_labels
852
+ longest_left_label_width = calculate_width(@marker_font, truncate_label_text(text))
831
853
 
832
854
  # Shift graph if left line numbers are hidden
833
- line_number_width = @hide_line_numbers && !@has_left_labels ? 0.0 : (longest_left_label_width + LABEL_MARGIN * 2)
855
+ line_number_width = !@has_left_labels && (@hide_line_markers || @hide_line_numbers) ? 0.0 : (longest_left_label_width + LABEL_MARGIN)
834
856
 
835
- @left_margin + line_number_width + (@y_axis_label.nil? ? 0.0 : @marker_caps_height + LABEL_MARGIN * 2)
857
+ @left_margin + line_number_width + (@y_axis_label.nil? ? 0.0 : marker_caps_height + (LABEL_MARGIN * 2))
836
858
  end
837
859
 
838
860
  def setup_top_margin
839
861
  # When @hide title, leave a title_margin space for aesthetics.
840
862
  # Same with @hide_legend
841
863
  @top_margin +
842
- (hide_title? ? @title_margin : @title_caps_height + @title_margin) +
843
- ((@hide_legend || @legend_at_bottom) ? @legend_margin : calculate_legend_height + @legend_margin)
864
+ (hide_title? ? @title_margin : title_caps_height + @title_margin) +
865
+ (@hide_legend || @legend_at_bottom ? @legend_margin : calculate_legend_height + @legend_margin)
844
866
  end
845
867
 
846
868
  def setup_bottom_margin
847
- graph_bottom_margin = hide_bottom_label_area? ? @bottom_margin : @bottom_margin + @marker_caps_height + LABEL_MARGIN
869
+ graph_bottom_margin = hide_bottom_label_area? ? @bottom_margin : @bottom_margin + marker_caps_height + LABEL_MARGIN
848
870
  graph_bottom_margin += (calculate_legend_height + @legend_margin) if @legend_at_bottom
849
871
 
850
- x_axis_label_height = @x_axis_label.nil? ? 0.0 : @marker_caps_height + LABEL_MARGIN
851
- # FIXME: Consider chart types other than bar
872
+ x_axis_label_height = @x_axis_label.nil? ? 0.0 : marker_caps_height + (LABEL_MARGIN * 2)
852
873
  @raw_rows - graph_bottom_margin - x_axis_label_height - @label_stagger_height
853
874
  end
854
875
 
@@ -868,29 +889,31 @@ module Gruff
868
889
  # Return a formatted string representing a number value that should be
869
890
  # printed as a label.
870
891
  def label(value, increment)
871
- label = if increment
872
- if increment >= 10 || (increment * 1) == (increment * 1).to_i.to_f
873
- sprintf('%0i', value)
874
- elsif increment >= 1.0 || (increment * 10) == (increment * 10).to_i.to_f
875
- sprintf('%0.1f', value)
876
- elsif increment >= 0.1 || (increment * 100) == (increment * 100).to_i.to_f
877
- sprintf('%0.2f', value)
878
- elsif increment >= 0.01 || (increment * 1000) == (increment * 1000).to_i.to_f
879
- sprintf('%0.3f', value)
880
- elsif increment >= 0.001 || (increment * 10000) == (increment * 10000).to_i.to_f
881
- sprintf('%0.4f', value)
882
- else
883
- value.to_s
884
- end
885
- elsif (@spread.to_f % (marker_count.to_f == 0 ? 1 : marker_count.to_f) == 0) || !@y_axis_increment.nil?
886
- value.to_i.to_s
887
- elsif @spread > 10.0
888
- sprintf('%0i', value)
889
- elsif @spread >= 3.0
890
- sprintf('%0.2f', value)
891
- else
892
- value.to_s
893
- end
892
+ label = begin
893
+ if increment
894
+ if increment >= 10 || (increment * 1) == (increment * 1).to_i.to_f
895
+ sprintf('%0i', value)
896
+ elsif increment >= 1.0 || (increment * 10) == (increment * 10).to_i.to_f
897
+ sprintf('%0.1f', value)
898
+ elsif increment >= 0.1 || (increment * 100) == (increment * 100).to_i.to_f
899
+ sprintf('%0.2f', value)
900
+ elsif increment >= 0.01 || (increment * 1000) == (increment * 1000).to_i.to_f
901
+ sprintf('%0.3f', value)
902
+ elsif increment >= 0.001 || (increment * 10_000) == (increment * 10_000).to_i.to_f
903
+ sprintf('%0.4f', value)
904
+ else
905
+ value.to_s
906
+ end
907
+ elsif (@spread % (marker_count == 0 ? 1 : marker_count) == 0) || !@y_axis_increment.nil?
908
+ value.to_i.to_s
909
+ elsif @spread > 10.0
910
+ sprintf('%0i', value)
911
+ elsif @spread >= 3.0
912
+ sprintf('%0.2f', value)
913
+ else
914
+ value.to_s
915
+ end
916
+ end
894
917
 
895
918
  parts = label.split('.')
896
919
  parts[0] = parts[0].commify
@@ -914,44 +937,31 @@ module Gruff
914
937
  end
915
938
 
916
939
  def calculate_legend_label_widths_for_each_line(legend_labels, legend_square_width)
917
- # May fix legend drawing problem at small sizes
918
- label_widths = [[]] # Used to calculate line wrap
940
+ label_widths = [[]]
941
+ label_lines = [[]]
919
942
  legend_labels.each do |label|
920
- width = calculate_width(@legend_font_size, label)
921
- label_width = width + legend_square_width * 2.7
943
+ width = calculate_width(@legend_font, label)
944
+ label_width = width + (legend_square_width * 2.7)
922
945
  label_widths.last.push label_width
946
+ label_lines.last.push label
923
947
 
924
948
  if label_widths.last.sum > (@raw_columns * 0.9)
925
949
  label_widths.push [label_widths.last.pop]
950
+ label_lines.push [label_lines.last.pop]
926
951
  end
927
952
  end
928
953
 
929
- label_widths
954
+ label_widths.map(&:sum).zip(label_lines)
930
955
  end
931
956
 
932
957
  def calculate_legend_height
933
958
  return 0.0 if @hide_legend
934
959
 
935
960
  legend_labels = store.data.map(&:label)
936
- legend_square_width = @legend_box_size
937
- label_widths = calculate_legend_label_widths_for_each_line(legend_labels, legend_square_width)
938
- legend_height = 0.0
939
-
940
- legend_labels.each_with_index do |legend_label, _index|
941
- next if legend_label.empty?
942
-
943
- label_widths.first.shift
944
- if label_widths.first.empty?
945
- label_widths.shift
946
- line_height = [@legend_caps_height, legend_square_width].max + @legend_margin
947
- unless label_widths.empty?
948
- # Wrap to next line and shrink available graph dimensions
949
- legend_height += line_height
950
- end
951
- end
952
- end
961
+ legend_label_lines = calculate_legend_label_widths_for_each_line(legend_labels, @legend_box_size)
962
+ line_height = [legend_caps_height, @legend_box_size].max
953
963
 
954
- legend_height + @legend_caps_height
964
+ (line_height * legend_label_lines.count) + (@legend_margin * (legend_label_lines.count - 1))
955
965
  end
956
966
 
957
967
  # Returns the height of the capital letter 'X' for the current font and
@@ -959,8 +969,19 @@ module Gruff
959
969
  #
960
970
  # Not scaled since it deals with dimensions that the regular scaling will
961
971
  # handle.
962
- def calculate_caps_height(font_size)
963
- metrics = Renderer::Text.metrics('X', @font, font_size)
972
+ def calculate_caps_height(font)
973
+ calculate_height(font, 'X')
974
+ end
975
+
976
+ # Returns the height of a string at this point size.
977
+ #
978
+ # Not scaled since it deals with dimensions that the regular scaling will
979
+ # handle.
980
+ def calculate_height(font, text)
981
+ text = text.to_s
982
+ return 0 if text.empty?
983
+
984
+ metrics = text_metrics(font, text)
964
985
  metrics.height
965
986
  end
966
987
 
@@ -968,20 +989,24 @@ module Gruff
968
989
  #
969
990
  # Not scaled since it deals with dimensions that the regular
970
991
  # scaling will handle.
971
- def calculate_width(font_size, text)
992
+ def calculate_width(font, text)
972
993
  text = text.to_s
973
994
  return 0 if text.empty?
974
995
 
975
- metrics = Renderer::Text.metrics(text, @font, font_size)
996
+ metrics = text_metrics(font, text)
976
997
  metrics.width
977
998
  end
978
999
 
1000
+ def text_metrics(font, text)
1001
+ Gruff::Renderer::Text.new(renderer, text, font: font).metrics
1002
+ end
1003
+
979
1004
  def calculate_increment
980
1005
  if @y_axis_increment.nil?
981
1006
  # Try to use a number of horizontal lines that will come out even.
982
1007
  #
983
1008
  # TODO Do the same for larger numbers...100, 75, 50, 25
984
- @increment = (@spread > 0 && marker_count > 0) ? significant(@spread / marker_count) : 1
1009
+ @increment = @spread > 0 && marker_count > 0 ? significant(@spread / marker_count) : 1
985
1010
  else
986
1011
  # TODO: Make this work for negative values
987
1012
  self.marker_count = (@spread / @y_axis_increment).to_i
@@ -989,9 +1014,13 @@ module Gruff
989
1014
  end
990
1015
  end
991
1016
 
992
- # Used for degree => radian conversions
1017
+ # Used for degree <=> radian conversions
993
1018
  def deg2rad(angle)
994
- angle * (Math::PI / 180.0)
1019
+ (angle * Math::PI) / 180.0
1020
+ end
1021
+
1022
+ def rad2deg(angle)
1023
+ (angle / Math::PI) * 180.0
995
1024
  end
996
1025
  end
997
1026