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