gruff 0.14.0-java → 0.17.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +28 -12
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +20 -24
  5. data/CHANGELOG.md +52 -0
  6. data/README.md +10 -3
  7. data/gruff.gemspec +9 -10
  8. data/lib/gruff/accumulator_bar.rb +1 -1
  9. data/lib/gruff/area.rb +6 -4
  10. data/lib/gruff/bar.rb +53 -31
  11. data/lib/gruff/base.rb +292 -184
  12. data/lib/gruff/bezier.rb +4 -2
  13. data/lib/gruff/box_plot.rb +180 -0
  14. data/lib/gruff/bullet.rb +6 -6
  15. data/lib/gruff/candlestick.rb +120 -0
  16. data/lib/gruff/dot.rb +11 -12
  17. data/lib/gruff/font.rb +3 -0
  18. data/lib/gruff/helper/bar_conversion.rb +6 -10
  19. data/lib/gruff/helper/bar_mixin.rb +25 -0
  20. data/lib/gruff/helper/bar_value_label.rb +24 -40
  21. data/lib/gruff/helper/stacked_mixin.rb +19 -1
  22. data/lib/gruff/histogram.rb +9 -5
  23. data/lib/gruff/line.rb +49 -48
  24. data/lib/gruff/mini/legend.rb +11 -11
  25. data/lib/gruff/net.rb +23 -18
  26. data/lib/gruff/patch/rmagick.rb +0 -1
  27. data/lib/gruff/patch/string.rb +1 -0
  28. data/lib/gruff/pie.rb +26 -12
  29. data/lib/gruff/renderer/dash_line.rb +3 -2
  30. data/lib/gruff/renderer/dot.rb +28 -15
  31. data/lib/gruff/renderer/line.rb +1 -3
  32. data/lib/gruff/renderer/rectangle.rb +6 -2
  33. data/lib/gruff/renderer/renderer.rb +4 -8
  34. data/lib/gruff/renderer/text.rb +7 -1
  35. data/lib/gruff/scatter.rb +64 -56
  36. data/lib/gruff/side_bar.rb +64 -30
  37. data/lib/gruff/side_stacked_bar.rb +43 -54
  38. data/lib/gruff/spider.rb +52 -18
  39. data/lib/gruff/stacked_area.rb +18 -8
  40. data/lib/gruff/stacked_bar.rb +59 -29
  41. data/lib/gruff/store/xy_data.rb +2 -0
  42. data/lib/gruff/version.rb +1 -1
  43. data/lib/gruff.rb +67 -58
  44. metadata +17 -16
  45. data/.rubocop_todo.yml +0 -116
  46. data/lib/gruff/scene.rb +0 -200
  47. data/lib/gruff/store/custom_data.rb +0 -36
data/lib/gruff/base.rb CHANGED
@@ -45,22 +45,15 @@ module Gruff
45
45
  # Blank space below the legend. Default is +20+.
46
46
  attr_writer :legend_margin
47
47
 
48
- # A hash of names for the individual columns, where the key is the array
49
- # index for the column this label represents.
50
- #
51
- # Not all columns need to be named.
52
- #
53
- # @example
54
- # { 0 => 2005, 3 => 2006, 5 => 2007, 7 => 2008 }
55
- attr_writer :labels
48
+ # Truncates labels if longer than max specified.
49
+ attr_writer :label_max_size
56
50
 
57
- # Used internally for spacing.
51
+ # How truncated labels visually appear if they exceed {#label_max_size=}.
58
52
  #
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
53
+ # - +:absolute+ - does not show trailing dots to indicate truncation. This is the default.
54
+ # - +:trailing_dots+ - shows trailing dots to indicate truncation (note that {#label_max_size=}
55
+ # must be greater than 3).
56
+ attr_writer :label_truncation_style
64
57
 
65
58
  # Set a label for the bottom of the graph.
66
59
  attr_writer :x_axis_label
@@ -68,31 +61,21 @@ module Gruff
68
61
  # Set a label for the left side of the graph.
69
62
  attr_writer :y_axis_label
70
63
 
64
+ # Allow passing lambda to format labels for x axis.
65
+ attr_writer :x_axis_label_format
66
+
67
+ # Allow passing lambda to format labels for y axis.
68
+ attr_writer :y_axis_label_format
69
+
71
70
  # Set increment of the vertical marking lines.
72
71
  attr_writer :x_axis_increment
73
72
 
74
73
  # Set increment of the horizontal marking lines.
75
74
  attr_writer :y_axis_increment
76
75
 
77
- # Height of staggering between labels (Bar graph only).
78
- attr_writer :label_stagger_height
79
-
80
- # Truncates labels if longer than max specified.
81
- attr_writer :label_max_size
82
-
83
- # How truncated labels visually appear if they exceed {#label_max_size=}.
84
- #
85
- # - +:absolute+ - does not show trailing dots to indicate truncation. This is the default.
86
- # - +:trailing_dots+ - shows trailing dots to indicate truncation (note that {#label_max_size=}
87
- # must be greater than 3).
88
- attr_writer :label_truncation_style
89
-
90
76
  # Get or set the list of colors that will be used to draw the bars or lines.
91
77
  attr_accessor :colors
92
78
 
93
- # Set the large title of the graph displayed at the top.
94
- attr_writer :title
95
-
96
79
  # Prevent drawing of line markers. Default is +false+.
97
80
  attr_writer :hide_line_markers
98
81
 
@@ -109,9 +92,6 @@ module Gruff
109
92
  # to +"No Data."+.
110
93
  attr_writer :no_data_message
111
94
 
112
- # Display the legend under the graph. Default is +false+.
113
- attr_writer :legend_at_bottom
114
-
115
95
  # Set the color of the auxiliary lines.
116
96
  attr_writer :marker_color
117
97
 
@@ -129,18 +109,15 @@ module Gruff
129
109
  # first. This does not affect the legend. Default is +false+.
130
110
  attr_writer :sorted_drawing
131
111
 
112
+ # Display the legend under the graph. Default is +false+.
113
+ attr_writer :legend_at_bottom
114
+
132
115
  # Optionally set the size of the colored box by each item in the legend.
133
116
  # Default is +20.0+.
134
117
  #
135
118
  # Will be scaled down if graph is smaller than 800px wide.
136
119
  attr_writer :legend_box_size
137
120
 
138
- # Allow passing lambdas to format labels for x axis.
139
- attr_writer :x_axis_label_format
140
-
141
- # Allow passing lambdas to format labels for y axis.
142
- attr_writer :y_axis_label_format
143
-
144
121
  # If one numerical argument is given, the graph is drawn at 4/3 ratio
145
122
  # according to the given width (+800+ results in 800x600, +400+ gives 400x300,
146
123
  # etc.).
@@ -159,6 +136,9 @@ module Gruff
159
136
  @columns.freeze
160
137
  @rows.freeze
161
138
 
139
+ @has_left_labels = false
140
+ @center_labels_over_point = true
141
+
162
142
  initialize_graph_scale
163
143
  initialize_attributes
164
144
  initialize_store
@@ -209,11 +189,9 @@ module Gruff
209
189
  @no_data_message = 'No Data'
210
190
 
211
191
  @hide_line_markers = @hide_legend = @hide_title = @hide_line_numbers = @legend_at_bottom = false
212
- @center_labels_over_point = true
213
- @has_left_labels = false
214
- @label_stagger_height = 0
215
192
  @label_max_size = 0
216
193
  @label_truncation_style = :absolute
194
+ @label_rotation = 0
217
195
 
218
196
  @x_axis_increment = nil
219
197
  @x_axis_label = @y_axis_label = nil
@@ -224,6 +202,67 @@ module Gruff
224
202
  end
225
203
  protected :initialize_attributes
226
204
 
205
+ # A hash of names for the individual columns, where the key is the array
206
+ # index for the column this label represents.
207
+ # Not all columns need to be named with hash.
208
+ #
209
+ # Or, an array corresponding to the data values.
210
+ #
211
+ # @param labels [Hash, Array] the labels.
212
+ #
213
+ # @example
214
+ # g = Gruff::Bar.new
215
+ # g.labels = { 0 => 2005, 3 => 2006, 5 => 2007, 7 => 2008 }
216
+ #
217
+ # g = Gruff::Bar.new
218
+ # g.labels = ['2005', nil, nil, '2006', nil, nil, '2007', nil, nil, '2008'] # same labels for columns
219
+ def labels=(labels)
220
+ if labels.is_a?(Array)
221
+ labels = labels.each_with_index.each_with_object({}) do |(label, index), hash|
222
+ hash[index] = label
223
+ end
224
+ end
225
+
226
+ @labels = labels
227
+ end
228
+
229
+ # Set a rotation for labels. You can use Default is +0+.
230
+ # You can use a rotation between +0.0+ and +45.0+, or between +0.0+ and +-45.0+.
231
+ #
232
+ # @param rotation [Numeric] the rotation.
233
+ #
234
+ def label_rotation=(rotation)
235
+ raise ArgumentError, 'rotation must be between 0.0 and 45.0 or between 0.0 and -45.0' if rotation > 45.0 || rotation < -45.0
236
+
237
+ @label_rotation = rotation.to_f
238
+ end
239
+
240
+ # Height of staggering between labels.
241
+ # @deprecated
242
+ def label_stagger_height=(_value)
243
+ warn '#label_stagger_height= is deprecated. It is no longer effective.'
244
+ end
245
+
246
+ # Set the large title of the graph displayed at the top.
247
+ # You can draw a multi-line title by putting a line break in the string
248
+ # or by setting an array as argument.
249
+ #
250
+ # @param title [String, Array] the title.
251
+ #
252
+ # @example
253
+ # g = Gruff::Bar.new
254
+ # g.title = "The graph title"
255
+ #
256
+ # g = Gruff::Bar.new
257
+ # g.title = ['The first line of title', 'The second line of title']
258
+ def title=(title)
259
+ if title.is_a?(Array)
260
+ title = title.join("\n")
261
+ end
262
+
263
+ @title = title
264
+ end
265
+
227
266
  # Sets the top, bottom, left and right margins to +margin+.
228
267
  #
229
268
  # @param margin [Numeric] The margin size.
@@ -327,7 +366,8 @@ module Gruff
327
366
  # You can set a theme manually. Assign a hash to this method before you
328
367
  # send your data.
329
368
  #
330
- # graph.theme = {
369
+ # g = Gruff::Bar.new
370
+ # g.theme = {
331
371
  # colors: %w(orange purple green white red),
332
372
  # marker_color: 'blue',
333
373
  # background_colors: ['black', 'grey'],
@@ -364,7 +404,7 @@ module Gruff
364
404
  self.marker_color = @theme_options[:marker_color]
365
405
  self.font_color = @theme_options[:font_color] || @marker_color
366
406
 
367
- @colors = @theme_options[:colors]
407
+ @colors = @theme_options[:colors].dup
368
408
  @marker_shadow_color = @theme_options[:marker_shadow_color]
369
409
 
370
410
  @renderer = Gruff::Renderer.new(@columns, @rows, @scale, @theme_options)
@@ -429,7 +469,7 @@ module Gruff
429
469
  #
430
470
  # Set it after you have given all your data to the graph object.
431
471
  def minimum_value
432
- @minimum_value || store.min
472
+ (@minimum_value || store.min).to_f
433
473
  end
434
474
  attr_writer :minimum_value
435
475
 
@@ -439,7 +479,7 @@ module Gruff
439
479
  # If you use this, you must set it after you have given all your data to
440
480
  # the graph object.
441
481
  def maximum_value
442
- @maximum_value || store.max
482
+ (@maximum_value || store.max).to_f
443
483
  end
444
484
  attr_writer :maximum_value
445
485
 
@@ -510,11 +550,13 @@ module Gruff
510
550
  attr_reader :renderer
511
551
 
512
552
  # Perform data manipulation before calculating chart measurements
513
- def setup_data # :nodoc:
553
+ def setup_data
514
554
  if @y_axis_increment && !@hide_line_markers
515
- self.maximum_value = [@y_axis_increment, maximum_value, (maximum_value.to_f / @y_axis_increment).round * @y_axis_increment].max
516
- self.minimum_value = [minimum_value, (minimum_value.to_f / @y_axis_increment).round * @y_axis_increment].min
555
+ self.maximum_value = [@y_axis_increment, maximum_value, (maximum_value / @y_axis_increment).round * @y_axis_increment].max
556
+ self.minimum_value = [minimum_value, (minimum_value / @y_axis_increment).round * @y_axis_increment].min
517
557
  end
558
+
559
+ sort_data if @sort # Sort data with avg largest values set first (for display)
518
560
  end
519
561
 
520
562
  # Calculates size of drawable area and generates normalized data.
@@ -525,7 +567,6 @@ module Gruff
525
567
  def setup_drawing
526
568
  calculate_spread
527
569
  calculate_increment
528
- sort_data if @sort # Sort data with avg largest values set first (for display)
529
570
  set_colors
530
571
  normalize
531
572
  setup_graph_measurements
@@ -552,7 +593,7 @@ module Gruff
552
593
  @marker_count ||= begin
553
594
  count = nil
554
595
  (3..7).each do |lines|
555
- if @spread.to_f % lines == 0.0
596
+ if @spread % lines == 0.0
556
597
  count = lines and break
557
598
  end
558
599
  end
@@ -565,8 +606,8 @@ module Gruff
565
606
  store.normalize(minimum: minimum_value, spread: @spread)
566
607
  end
567
608
 
568
- def calculate_spread # :nodoc:
569
- @spread = maximum_value.to_f - minimum_value.to_f
609
+ def calculate_spread
610
+ @spread = maximum_value - minimum_value
570
611
  @spread = @spread > 0 ? @spread : 1
571
612
  end
572
613
 
@@ -579,28 +620,23 @@ module Gruff
579
620
  end
580
621
 
581
622
  def hide_left_label_area?
582
- @hide_line_markers
623
+ @hide_line_markers && @y_axis_label.nil?
583
624
  end
584
625
 
585
626
  def hide_bottom_label_area?
586
- @hide_line_markers
627
+ @hide_line_markers && @x_axis_label.nil?
587
628
  end
588
629
 
589
630
  ##
590
631
  # Calculates size of drawable area, general font dimensions, etc.
591
632
 
592
633
  def setup_graph_measurements
593
- @marker_caps_height = setup_marker_caps_height
594
- @title_caps_height = setup_title_caps_height
595
- @legend_caps_height = setup_legend_caps_height
596
-
597
- margin_on_right = graph_right_margin
598
- @graph_right = @raw_columns - margin_on_right
634
+ @graph_right = setup_right_margin
599
635
  @graph_left = setup_left_margin
600
636
  @graph_top = setup_top_margin
601
637
  @graph_bottom = setup_bottom_margin
602
638
 
603
- @graph_width = @raw_columns - @graph_left - margin_on_right
639
+ @graph_width = @graph_right - @graph_left
604
640
  @graph_height = @graph_bottom - @graph_top
605
641
  end
606
642
 
@@ -610,9 +646,8 @@ module Gruff
610
646
  # X Axis
611
647
  # Centered vertically and horizontally by setting the
612
648
  # height to 1.0 and the width to the width of the graph.
613
- x_axis_label_y_coordinate = @graph_bottom + LABEL_MARGIN + @marker_caps_height
649
+ x_axis_label_y_coordinate = @graph_bottom + (LABEL_MARGIN * 2) + labels_caps_height
614
650
 
615
- # TODO: Center between graph area
616
651
  text_renderer = Gruff::Renderer::Text.new(renderer, @x_axis_label, font: @marker_font)
617
652
  text_renderer.add_to_render_queue(@raw_columns, 1.0, 0.0, x_axis_label_y_coordinate)
618
653
  end
@@ -620,7 +655,7 @@ module Gruff
620
655
  if @y_axis_label
621
656
  # Y Axis, rotated vertically
622
657
  text_renderer = Gruff::Renderer::Text.new(renderer, @y_axis_label, font: @marker_font, rotation: -90)
623
- text_renderer.add_to_render_queue(1.0, @raw_rows, @left_margin + @marker_caps_height / 2.0, 0.0, Magick::CenterGravity)
658
+ text_renderer.add_to_render_queue(1.0, @raw_rows, @left_margin + (marker_caps_height / 2.0), 0.0, Magick::CenterGravity)
624
659
  end
625
660
  end
626
661
 
@@ -628,17 +663,15 @@ module Gruff
628
663
  def draw_line_markers
629
664
  return if @hide_line_markers
630
665
 
631
- increment_scaled = @graph_height.to_f / (@spread / @increment)
666
+ increment_scaled = @graph_height / (@spread / @increment)
632
667
 
633
668
  # Draw horizontal line markers and annotate with numbers
634
669
  (0..marker_count).each do |index|
635
- y = @graph_top + @graph_height - index.to_f * increment_scaled
636
-
637
- line_renderer = Gruff::Renderer::Line.new(renderer, color: @marker_color, shadow_color: @marker_shadow_color)
638
- line_renderer.render(@graph_left, y, @graph_right, y)
670
+ y = @graph_top + @graph_height - (index * increment_scaled)
671
+ draw_marker_horizontal_line(y)
639
672
 
640
673
  unless @hide_line_numbers
641
- marker_label = BigDecimal(index.to_s) * BigDecimal(@increment.to_s) + BigDecimal(minimum_value.to_s)
674
+ marker_label = (BigDecimal(index.to_s) * BigDecimal(@increment.to_s)) + BigDecimal(minimum_value.to_s)
642
675
  label = y_axis_label(marker_label, @increment)
643
676
  text_renderer = Gruff::Renderer::Text.new(renderer, label, font: @marker_font)
644
677
  text_renderer.add_to_render_queue(@graph_left - LABEL_MARGIN, 1.0, 0.0, y, Magick::EastGravity)
@@ -646,6 +679,25 @@ module Gruff
646
679
  end
647
680
  end
648
681
 
682
+ def draw_marker_horizontal_line(y)
683
+ Gruff::Renderer::Line.new(renderer, color: @marker_color).render(@graph_left, y, @graph_right, y)
684
+ Gruff::Renderer::Line.new(renderer, color: @marker_shadow_color).render(@graph_left, y + 1, @graph_right, y + 1) if @marker_shadow_color
685
+ end
686
+
687
+ def draw_marker_vertical_line(x, tick_mark_mode: false)
688
+ if tick_mark_mode
689
+ Gruff::Renderer::Line.new(renderer, color: @marker_color).render(x, @graph_bottom, x, @graph_bottom + 5)
690
+ if @marker_shadow_color
691
+ Gruff::Renderer::Line.new(renderer, color: @marker_shadow_color).render(x + 1, @graph_bottom, x + 1, @graph_bottom + 5)
692
+ end
693
+ else
694
+ Gruff::Renderer::Line.new(renderer, color: @marker_color).render(x, @graph_bottom, x, @graph_top)
695
+ if @marker_shadow_color
696
+ Gruff::Renderer::Line.new(renderer, color: @marker_shadow_color).render(x + 1, @graph_bottom, x + 1, @graph_top)
697
+ end
698
+ end
699
+ end
700
+
649
701
  # Return a calculation of center
650
702
  def center(size)
651
703
  (@raw_columns - size) / 2
@@ -658,45 +710,46 @@ module Gruff
658
710
 
659
711
  legend_labels = store.data.map(&:label)
660
712
  legend_square_width = @legend_box_size # small square with color of this item
661
- label_widths = calculate_legend_label_widths_for_each_line(legend_labels, legend_square_width)
713
+ legend_label_lines = calculate_legend_label_widths_for_each_line(legend_labels, legend_square_width)
714
+ line_height = [legend_caps_height, legend_square_width].max + @legend_margin
662
715
 
663
- current_x_offset = center(label_widths.first.sum)
664
716
  current_y_offset = begin
665
717
  if @legend_at_bottom
666
- @graph_bottom + @legend_margin + @legend_caps_height + LABEL_MARGIN
718
+ @graph_bottom + @legend_margin + labels_caps_height + LABEL_MARGIN + (@x_axis_label ? (LABEL_MARGIN * 2) + marker_caps_height : 0)
667
719
  else
668
- hide_title? ? @top_margin + @title_margin : @top_margin + @title_margin + @title_caps_height
720
+ hide_title? ? @top_margin + @title_margin : @top_margin + @title_margin + title_caps_height
669
721
  end
670
722
  end
671
723
 
672
- legend_labels.each_with_index do |legend_label, index|
673
- next if legend_label.empty?
674
-
675
- # Draw label
676
- text_renderer = Gruff::Renderer::Text.new(renderer, legend_label, font: @legend_font)
677
- text_renderer.add_to_render_queue(@raw_columns, 1.0, current_x_offset + (legend_square_width * 1.7), current_y_offset, Magick::WestGravity)
678
-
679
- # Now draw box with color of this dataset
680
- rect_renderer = Gruff::Renderer::Rectangle.new(renderer, color: store.data[index].color)
681
- rect_renderer.render(current_x_offset,
682
- current_y_offset - legend_square_width / 2.0,
683
- current_x_offset + legend_square_width,
684
- current_y_offset + legend_square_width / 2.0)
685
-
686
- width = calculate_width(@legend_font, legend_label)
687
- current_x_offset += width + (legend_square_width * 2.7)
688
- label_widths.first.shift
689
-
690
- # Handle wrapping
691
- if label_widths.first.empty?
692
- label_widths.shift
693
- current_x_offset = center(label_widths.first.sum) unless label_widths.empty?
694
- line_height = [@legend_caps_height, legend_square_width].max + @legend_margin
695
- unless label_widths.empty?
696
- # Wrap to next line and shrink available graph dimensions
697
- current_y_offset += line_height
724
+ index = 0
725
+ legend_label_lines.each do |(legend_labels_width, legend_labels_line)|
726
+ current_x_offset = center(legend_labels_width)
727
+
728
+ legend_labels_line.each do |legend_label|
729
+ unless legend_label.empty?
730
+ legend_label_width = calculate_width(@legend_font, legend_label)
731
+
732
+ # Draw label
733
+ text_renderer = Gruff::Renderer::Text.new(renderer, legend_label, font: @legend_font)
734
+ text_renderer.add_to_render_queue(legend_label_width,
735
+ legend_square_width,
736
+ current_x_offset + (legend_square_width * 1.7),
737
+ current_y_offset,
738
+ Magick::CenterGravity)
739
+
740
+ # Now draw box with color of this dataset
741
+ rect_renderer = Gruff::Renderer::Rectangle.new(renderer, color: store.data[index].color)
742
+ rect_renderer.render(current_x_offset,
743
+ current_y_offset,
744
+ current_x_offset + legend_square_width,
745
+ current_y_offset + legend_square_width)
746
+
747
+ current_x_offset += legend_label_width + (legend_square_width * 2.7)
698
748
  end
749
+ index += 1
699
750
  end
751
+
752
+ current_y_offset += line_height
700
753
  end
701
754
  end
702
755
 
@@ -704,7 +757,7 @@ module Gruff
704
757
  def draw_title
705
758
  return if hide_title?
706
759
 
707
- metrics = Gruff::Renderer::Text.new(renderer, @title, font: @title_font).metrics
760
+ metrics = text_metrics(@title_font, @title)
708
761
  if metrics.width > @raw_columns
709
762
  @title_font.size = @title_font.size * (@raw_columns / metrics.width) * 0.95
710
763
  end
@@ -714,19 +767,25 @@ module Gruff
714
767
  end
715
768
 
716
769
  # Draws column labels below graph, centered over x_offset
717
- #--
718
- # TODO Allow WestGravity as an option
719
- def draw_label(x_offset, index, gravity = Magick::NorthGravity)
770
+ def draw_label(x_offset, index, gravity = Magick::NorthGravity, &block)
720
771
  draw_unique_label(index) do
721
- y_offset = @graph_bottom + LABEL_MARGIN
722
-
723
- # TESTME
724
- # FIXME: Consider chart types other than bar
725
- # TODO: See if index.odd? is the best stragegy
726
- y_offset += @label_stagger_height if index.odd?
772
+ y_offset = @graph_bottom
727
773
 
728
774
  if x_offset >= @graph_left && x_offset <= @graph_right
729
- draw_label_at(1.0, 1.0, x_offset, y_offset, @labels[index], gravity)
775
+ width = calculate_width(@marker_font, @labels[index], rotation: @label_rotation)
776
+ height = calculate_height(@marker_font, @labels[index], rotation: @label_rotation)
777
+ case @label_rotation
778
+ when 0
779
+ x_offset
780
+ when 0..45
781
+ x_offset += (width / 2.0)
782
+ when -45..0
783
+ x_offset -= (width / 2.0)
784
+ end
785
+ y_offset += (height / 2.0) > LABEL_MARGIN ? (height / 2.0) : LABEL_MARGIN
786
+
787
+ draw_label_at(1.0, 1.0, x_offset, y_offset, @labels[index], gravity: gravity, rotation: @label_rotation)
788
+ yield if block
730
789
  end
731
790
  end
732
791
  end
@@ -741,18 +800,17 @@ module Gruff
741
800
  end
742
801
  end
743
802
 
744
- def draw_label_at(width, height, x, y, text, gravity = Magick::NorthGravity)
803
+ def draw_label_at(width, height, x, y, text, gravity: Magick::NorthGravity, rotation: 0)
745
804
  label_text = truncate_label_text(text)
746
- text_renderer = Gruff::Renderer::Text.new(renderer, label_text, font: @marker_font)
805
+ text_renderer = Gruff::Renderer::Text.new(renderer, label_text, font: @marker_font, rotation: rotation)
747
806
  text_renderer.add_to_render_queue(width, height, x, y, gravity)
748
807
  end
749
808
 
750
809
  # Draws the data value over the data point in bar graphs
751
- def draw_value_label(x_offset, y_offset, data_point)
810
+ def draw_value_label(width, height, x_offset, y_offset, data_point, gravity: Magick::CenterGravity)
752
811
  return if @hide_line_markers
753
812
 
754
- text_renderer = Gruff::Renderer::Text.new(renderer, data_point, font: @marker_font)
755
- text_renderer.add_to_render_queue(1.0, 1.0, x_offset, y_offset)
813
+ draw_label_at(width, height, x_offset, y_offset, data_point, gravity: gravity)
756
814
  end
757
815
 
758
816
  # Shows an error message because you have no data.
@@ -773,20 +831,15 @@ module Gruff
773
831
  @theme_options = {}
774
832
  end
775
833
 
776
- def scale(value) # :nodoc:
834
+ def scale(value)
777
835
  value * @scale
778
836
  end
779
837
 
780
- # Return a comparable fontsize for the current graph.
781
- def scale_fontsize(value)
782
- value * @scale
838
+ def clip_value_if_greater_than(value, max_value)
839
+ value > max_value ? max_value : value
783
840
  end
784
841
 
785
- def clip_value_if_greater_than(value, max_value) # :nodoc:
786
- (value > max_value) ? max_value : value
787
- end
788
-
789
- def significant(i) # :nodoc:
842
+ def significant(i)
790
843
  return 1.0 if i == 0 # Keep from going into infinite loop
791
844
 
792
845
  inc = BigDecimal(i.to_s)
@@ -829,27 +882,20 @@ module Gruff
829
882
 
830
883
  private
831
884
 
832
- def setup_marker_caps_height
885
+ def marker_caps_height
833
886
  hide_bottom_label_area? ? 0 : calculate_caps_height(@marker_font)
834
887
  end
835
888
 
836
- def setup_title_caps_height
837
- hide_title? ? 0 : calculate_caps_height(@title_font) * @title.lines.to_a.size
838
- end
839
-
840
- def setup_legend_caps_height
841
- @hide_legend ? 0 : calculate_caps_height(@legend_font)
889
+ def labels_caps_height
890
+ hide_bottom_label_area? ? 0 : calculate_labels_height(@marker_font)
842
891
  end
843
892
 
844
- def graph_right_margin
845
- @hide_line_markers ? @right_margin : @right_margin + extra_room_for_long_label
893
+ def title_caps_height
894
+ hide_title? ? 0 : calculate_caps_height(@title_font) * @title.lines.to_a.size
846
895
  end
847
896
 
848
- def extra_room_for_long_label
849
- # Make space for half the width of the rightmost column label.
850
- # Might be greater than the number of columns if between-style bar markers are used.
851
- last_label = @labels.keys.max.to_i
852
- (last_label >= (column_count - 1) && @center_labels_over_point) ? calculate_width(@marker_font, @labels[last_label]) / 2.0 : 0
897
+ def legend_caps_height
898
+ @hide_legend ? 0 : calculate_caps_height(@legend_font)
853
899
  end
854
900
 
855
901
  def setup_left_margin
@@ -857,35 +903,85 @@ module Gruff
857
903
 
858
904
  text = begin
859
905
  if @has_left_labels
860
- @labels.values.reduce('') { |value, memo| (value.to_s.length > memo.to_s.length) ? value : memo }
906
+ @labels.values.reduce('') { |value, memo| value.to_s.length > memo.to_s.length ? value : memo }
861
907
  else
862
- y_axis_label(maximum_value.to_f, @increment)
908
+ y_axis_label(maximum_value, @increment)
863
909
  end
864
910
  end
865
911
  longest_left_label_width = calculate_width(@marker_font, truncate_label_text(text))
866
- longest_left_label_width *= 1.25 if @has_left_labels
867
912
 
868
- # Shift graph if left line numbers are hidden
869
- line_number_width = @hide_line_numbers && !@has_left_labels ? 0.0 : (longest_left_label_width + LABEL_MARGIN * 2)
913
+ line_number_width = begin
914
+ if !@has_left_labels && (@hide_line_markers || @hide_line_numbers)
915
+ 0.0
916
+ else
917
+ longest_left_label_width + LABEL_MARGIN
918
+ end
919
+ end
920
+ y_axis_label_width = @y_axis_label.nil? ? 0.0 : marker_caps_height + (LABEL_MARGIN * 2)
870
921
 
871
- @left_margin + line_number_width + (@y_axis_label.nil? ? 0.0 : @marker_caps_height + LABEL_MARGIN * 2)
922
+ bottom_label_width = extra_left_room_for_long_label
923
+
924
+ margin = line_number_width + y_axis_label_width
925
+ @left_margin + (margin > bottom_label_width ? margin : bottom_label_width)
926
+ end
927
+
928
+ def setup_right_margin
929
+ @raw_columns - (@hide_line_markers ? @right_margin : @right_margin + extra_right_room_for_long_label)
930
+ end
931
+
932
+ def extra_left_room_for_long_label
933
+ if require_extra_side_margin?
934
+ width = calculate_width(@marker_font, truncate_label_text(@labels[0]), rotation: @label_rotation)
935
+ case @label_rotation
936
+ when 0
937
+ width / 2.0
938
+ when 0..45
939
+ 0
940
+ when -45..0
941
+ width
942
+ end
943
+ else
944
+ 0
945
+ end
946
+ end
947
+
948
+ def extra_right_room_for_long_label
949
+ # Make space for half the width of the rightmost column label.
950
+ # Might be greater than the number of columns if between-style bar markers are used.
951
+ last_label = @labels.keys.max.to_i
952
+ if last_label >= (column_count - 1) && require_extra_side_margin?
953
+ width = calculate_width(@marker_font, truncate_label_text(@labels[last_label]), rotation: @label_rotation)
954
+ case @label_rotation
955
+ when 0
956
+ width / 2.0
957
+ when 0..45
958
+ width
959
+ when -45..0
960
+ 0
961
+ end
962
+ else
963
+ 0
964
+ end
965
+ end
966
+
967
+ def require_extra_side_margin?
968
+ !hide_bottom_label_area? && @center_labels_over_point
872
969
  end
873
970
 
874
971
  def setup_top_margin
875
972
  # When @hide title, leave a title_margin space for aesthetics.
876
973
  # Same with @hide_legend
877
974
  @top_margin +
878
- (hide_title? ? @title_margin : @title_caps_height + @title_margin) +
879
- ((@hide_legend || @legend_at_bottom) ? @legend_margin : calculate_legend_height + @legend_margin)
975
+ (hide_title? ? @title_margin : title_caps_height + @title_margin) +
976
+ (@hide_legend || @legend_at_bottom ? @legend_margin : calculate_legend_height + @legend_margin)
880
977
  end
881
978
 
882
979
  def setup_bottom_margin
883
- graph_bottom_margin = hide_bottom_label_area? ? @bottom_margin : @bottom_margin + @marker_caps_height + LABEL_MARGIN
980
+ graph_bottom_margin = hide_bottom_label_area? ? @bottom_margin : @bottom_margin + labels_caps_height + LABEL_MARGIN
884
981
  graph_bottom_margin += (calculate_legend_height + @legend_margin) if @legend_at_bottom
885
982
 
886
- x_axis_label_height = @x_axis_label.nil? ? 0.0 : @marker_caps_height + LABEL_MARGIN
887
- # FIXME: Consider chart types other than bar
888
- @raw_rows - graph_bottom_margin - x_axis_label_height - @label_stagger_height
983
+ x_axis_label_height = @x_axis_label.nil? ? 0.0 : marker_caps_height + (LABEL_MARGIN * 2)
984
+ @raw_rows - graph_bottom_margin - x_axis_label_height
889
985
  end
890
986
 
891
987
  def truncate_label_text(text)
@@ -914,12 +1010,12 @@ module Gruff
914
1010
  sprintf('%0.2f', value)
915
1011
  elsif increment >= 0.01 || (increment * 1000) == (increment * 1000).to_i.to_f
916
1012
  sprintf('%0.3f', value)
917
- elsif increment >= 0.001 || (increment * 10000) == (increment * 10000).to_i.to_f
1013
+ elsif increment >= 0.001 || (increment * 10_000) == (increment * 10_000).to_i.to_f
918
1014
  sprintf('%0.4f', value)
919
1015
  else
920
1016
  value.to_s
921
1017
  end
922
- elsif (@spread.to_f % (marker_count.to_f == 0 ? 1 : marker_count.to_f) == 0) || !@y_axis_increment.nil?
1018
+ elsif (@spread % (marker_count == 0 ? 1 : marker_count) == 0) || !@y_axis_increment.nil?
923
1019
  value.to_i.to_s
924
1020
  elsif @spread > 10.0
925
1021
  sprintf('%0i', value)
@@ -952,44 +1048,31 @@ module Gruff
952
1048
  end
953
1049
 
954
1050
  def calculate_legend_label_widths_for_each_line(legend_labels, legend_square_width)
955
- # May fix legend drawing problem at small sizes
956
- label_widths = [[]] # Used to calculate line wrap
1051
+ label_widths = [[]]
1052
+ label_lines = [[]]
957
1053
  legend_labels.each do |label|
958
1054
  width = calculate_width(@legend_font, label)
959
- label_width = width + legend_square_width * 2.7
1055
+ label_width = width + (legend_square_width * 2.7)
960
1056
  label_widths.last.push label_width
1057
+ label_lines.last.push label
961
1058
 
962
1059
  if label_widths.last.sum > (@raw_columns * 0.9)
963
1060
  label_widths.push [label_widths.last.pop]
1061
+ label_lines.push [label_lines.last.pop]
964
1062
  end
965
1063
  end
966
1064
 
967
- label_widths
1065
+ label_widths.map(&:sum).zip(label_lines)
968
1066
  end
969
1067
 
970
1068
  def calculate_legend_height
971
1069
  return 0.0 if @hide_legend
972
1070
 
973
1071
  legend_labels = store.data.map(&:label)
974
- legend_square_width = @legend_box_size
975
- label_widths = calculate_legend_label_widths_for_each_line(legend_labels, legend_square_width)
976
- legend_height = 0.0
977
-
978
- legend_labels.each_with_index do |legend_label, _index|
979
- next if legend_label.empty?
980
-
981
- label_widths.first.shift
982
- if label_widths.first.empty?
983
- label_widths.shift
984
- line_height = [@legend_caps_height, legend_square_width].max + @legend_margin
985
- unless label_widths.empty?
986
- # Wrap to next line and shrink available graph dimensions
987
- legend_height += line_height
988
- end
989
- end
990
- end
1072
+ legend_label_lines = calculate_legend_label_widths_for_each_line(legend_labels, @legend_box_size)
1073
+ line_height = [legend_caps_height, @legend_box_size].max
991
1074
 
992
- legend_height + @legend_caps_height
1075
+ (line_height * legend_label_lines.count) + (@legend_margin * (legend_label_lines.count - 1))
993
1076
  end
994
1077
 
995
1078
  # Returns the height of the capital letter 'X' for the current font and
@@ -998,20 +1081,41 @@ module Gruff
998
1081
  # Not scaled since it deals with dimensions that the regular scaling will
999
1082
  # handle.
1000
1083
  def calculate_caps_height(font)
1001
- metrics = Gruff::Renderer::Text.new(renderer, 'X', font: font).metrics
1002
- metrics.height
1084
+ calculate_height(font, 'X')
1085
+ end
1086
+
1087
+ def calculate_labels_height(font)
1088
+ @labels.values.map { |label| calculate_height(font, label, rotation: @label_rotation) }.max || marker_caps_height
1089
+ end
1090
+
1091
+ # Returns the height of a string at this point size.
1092
+ #
1093
+ # Not scaled since it deals with dimensions that the regular scaling will
1094
+ # handle.
1095
+ def calculate_height(font, text, rotation: 0)
1096
+ text = text.to_s
1097
+ return 0 if text.empty?
1098
+
1099
+ metrics = text_metrics(font, text, rotation: rotation)
1100
+ # Calculate manually because it does not return the height after rotation.
1101
+ (metrics.width * Math.sin(deg2rad(rotation))).abs + (metrics.height * Math.cos(deg2rad(rotation))).abs
1003
1102
  end
1004
1103
 
1005
1104
  # Returns the width of a string at this point size.
1006
1105
  #
1007
1106
  # Not scaled since it deals with dimensions that the regular
1008
1107
  # scaling will handle.
1009
- def calculate_width(font, text)
1108
+ def calculate_width(font, text, rotation: 0)
1010
1109
  text = text.to_s
1011
1110
  return 0 if text.empty?
1012
1111
 
1013
- metrics = Gruff::Renderer::Text.new(renderer, text, font: font).metrics
1014
- metrics.width
1112
+ metrics = text_metrics(font, text, rotation: rotation)
1113
+ # Calculate manually because it does not return the width after rotation.
1114
+ (metrics.width * Math.cos(deg2rad(rotation))).abs - (metrics.height * Math.sin(deg2rad(rotation))).abs
1115
+ end
1116
+
1117
+ def text_metrics(font, text, rotation: 0)
1118
+ Gruff::Renderer::Text.new(renderer, text, font: font, rotation: rotation).metrics
1015
1119
  end
1016
1120
 
1017
1121
  def calculate_increment
@@ -1019,7 +1123,7 @@ module Gruff
1019
1123
  # Try to use a number of horizontal lines that will come out even.
1020
1124
  #
1021
1125
  # TODO Do the same for larger numbers...100, 75, 50, 25
1022
- @increment = (@spread > 0 && marker_count > 0) ? significant(@spread / marker_count) : 1
1126
+ @increment = @spread > 0 && marker_count > 0 ? significant(@spread / marker_count) : 1
1023
1127
  else
1024
1128
  # TODO: Make this work for negative values
1025
1129
  self.marker_count = (@spread / @y_axis_increment).to_i
@@ -1027,9 +1131,13 @@ module Gruff
1027
1131
  end
1028
1132
  end
1029
1133
 
1030
- # Used for degree => radian conversions
1134
+ # Used for degree <=> radian conversions
1031
1135
  def deg2rad(angle)
1032
- angle * (Math::PI / 180.0)
1136
+ (angle * Math::PI) / 180.0
1137
+ end
1138
+
1139
+ def rad2deg(angle)
1140
+ (angle / Math::PI) * 180.0
1033
1141
  end
1034
1142
  end
1035
1143