gruff 0.12.2 → 0.13.0

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.
data/lib/gruff/base.rb CHANGED
@@ -156,9 +156,11 @@ module Gruff
156
156
  # Will be scaled down if graph is smaller than 800px wide.
157
157
  attr_writer :legend_box_size
158
158
 
159
- # With Side Bars use the data label for the marker value to the left of the bar.
160
- # Default is +false+.
161
- attr_writer :use_data_label
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
162
164
 
163
165
  # If one numerical argument is given, the graph is drawn at 4/3 ratio
164
166
  # according to the given width (+800+ results in 800x600, +400+ gives 400x300,
@@ -214,8 +216,8 @@ module Gruff
214
216
  @sort = false
215
217
  @sorted_drawing = false
216
218
  @title = nil
217
- @title_font = nil
218
219
 
220
+ @title_font = nil
219
221
  @font = nil
220
222
  @bold_title = true
221
223
 
@@ -238,10 +240,12 @@ module Gruff
238
240
  @label_max_size = 0
239
241
  @label_truncation_style = :absolute
240
242
 
241
- @use_data_label = false
242
243
  @x_axis_increment = nil
243
244
  @x_axis_label = @y_axis_label = nil
244
245
  @y_axis_increment = nil
246
+
247
+ @x_axis_label_format = nil
248
+ @y_axis_label_format = nil
245
249
  end
246
250
  protected :initialize_ivars
247
251
 
@@ -603,7 +607,7 @@ module Gruff
603
607
 
604
608
  unless @hide_line_numbers
605
609
  marker_label = BigDecimal(index.to_s) * BigDecimal(@increment.to_s) + BigDecimal(minimum_value.to_s)
606
- label = label(marker_label, @increment)
610
+ label = y_axis_label(marker_label, @increment)
607
611
  text_renderer = Gruff::Renderer::Text.new(label, font: @font, size: @marker_font_size, color: @font_color)
608
612
  text_renderer.add_to_render_queue(@graph_left - LABEL_MARGIN, 1.0, 0.0, y, Magick::EastGravity)
609
613
  end
@@ -627,7 +631,7 @@ module Gruff
627
631
  current_x_offset = center(label_widths.first.sum)
628
632
  current_y_offset = begin
629
633
  if @legend_at_bottom
630
- @graph_height + @title_margin
634
+ @graph_bottom + @legend_margin + @legend_caps_height + LABEL_MARGIN
631
635
  else
632
636
  hide_title? ? @top_margin + @title_margin : @top_margin + @title_margin + @title_caps_height
633
637
  end
@@ -659,8 +663,6 @@ module Gruff
659
663
  unless label_widths.empty?
660
664
  # Wrap to next line and shrink available graph dimensions
661
665
  current_y_offset += line_height
662
- @graph_top += line_height
663
- @graph_height = @graph_bottom - @graph_top
664
666
  end
665
667
  end
666
668
  end
@@ -674,7 +676,7 @@ module Gruff
674
676
  font_weight = @bold_title ? Magick::BoldWeight : Magick::NormalWeight
675
677
  font_size = @title_font_size
676
678
 
677
- metrics = Renderer::Text.metrics(@title, font_size, font_weight)
679
+ metrics = Renderer::Text.metrics(@title, font, font_size, font_weight)
678
680
  if metrics.width > @raw_columns
679
681
  font_size = font_size * (@raw_columns / metrics.width) * 0.95
680
682
  end
@@ -694,11 +696,8 @@ module Gruff
694
696
  # TODO: See if index.odd? is the best stragegy
695
697
  y_offset += @label_stagger_height if index.odd?
696
698
 
697
- label_text = truncate_label_text(@labels[index].to_s)
698
-
699
699
  if x_offset >= @graph_left && x_offset <= @graph_right
700
- text_renderer = Gruff::Renderer::Text.new(label_text, font: @font, size: @marker_font_size, color: @font_color)
701
- text_renderer.add_to_render_queue(1.0, 1.0, x_offset, y_offset, gravity)
700
+ draw_label_at(1.0, 1.0, x_offset, y_offset, @labels[index], gravity)
702
701
  end
703
702
  end
704
703
  end
@@ -713,6 +712,12 @@ module Gruff
713
712
  end
714
713
  end
715
714
 
715
+ def draw_label_at(width, height, x, y, text, gravity = Magick::NorthGravity)
716
+ label_text = truncate_label_text(text)
717
+ text_renderer = Gruff::Renderer::Text.new(label_text, font: @font, size: @marker_font_size, color: @font_color)
718
+ text_renderer.add_to_render_queue(width, height, x, y, gravity)
719
+ end
720
+
716
721
  # Draws the data value over the data point in bar graphs
717
722
  def draw_value_label(x_offset, y_offset, data_point, bar_value = false)
718
723
  return if @hide_line_markers && !bar_value
@@ -818,10 +823,10 @@ module Gruff
818
823
  if @has_left_labels
819
824
  @labels.values.reduce('') { |value, memo| (value.to_s.length > memo.to_s.length) ? value : memo }
820
825
  else
821
- label(maximum_value.to_f, @increment)
826
+ y_axis_label(maximum_value.to_f, @increment)
822
827
  end
823
828
  end
824
- longest_left_label_width = calculate_width(@marker_font_size, text)
829
+ longest_left_label_width = calculate_width(@marker_font_size, truncate_label_text(text))
825
830
  longest_left_label_width *= 1.25 if @has_left_labels
826
831
 
827
832
  # Shift graph if left line numbers are hidden
@@ -831,17 +836,16 @@ module Gruff
831
836
  end
832
837
 
833
838
  def setup_top_margin
834
- return @top_margin if @legend_at_bottom
835
-
836
839
  # When @hide title, leave a title_margin space for aesthetics.
837
840
  # Same with @hide_legend
838
841
  @top_margin +
839
842
  (hide_title? ? @title_margin : @title_caps_height + @title_margin) +
840
- (@hide_legend ? @legend_margin : @legend_caps_height + @legend_margin)
843
+ ((@hide_legend || @legend_at_bottom) ? @legend_margin : calculate_legend_height + @legend_margin)
841
844
  end
842
845
 
843
846
  def setup_bottom_margin
844
847
  graph_bottom_margin = hide_bottom_label_area? ? @bottom_margin : @bottom_margin + @marker_caps_height + LABEL_MARGIN
848
+ graph_bottom_margin += (calculate_legend_height + @legend_margin) if @legend_at_bottom
845
849
 
846
850
  x_axis_label_height = @x_axis_label.nil? ? 0.0 : @marker_caps_height + LABEL_MARGIN
847
851
  # FIXME: Consider chart types other than bar
@@ -849,6 +853,7 @@ module Gruff
849
853
  end
850
854
 
851
855
  def truncate_label_text(text)
856
+ text = text.to_s
852
857
  return text if text.size <= @label_max_size
853
858
 
854
859
  if @label_truncation_style == :trailing_dots
@@ -892,6 +897,22 @@ module Gruff
892
897
  parts.join('.')
893
898
  end
894
899
 
900
+ def x_axis_label(value, increment)
901
+ if @x_axis_label_format
902
+ @x_axis_label_format.call(value)
903
+ else
904
+ label(value, increment)
905
+ end
906
+ end
907
+
908
+ def y_axis_label(value, increment)
909
+ if @y_axis_label_format
910
+ @y_axis_label_format.call(value)
911
+ else
912
+ label(value, increment)
913
+ end
914
+ end
915
+
895
916
  def calculate_legend_label_widths_for_each_line(legend_labels, legend_square_width)
896
917
  # May fix legend drawing problem at small sizes
897
918
  label_widths = [[]] # Used to calculate line wrap
@@ -908,13 +929,38 @@ module Gruff
908
929
  label_widths
909
930
  end
910
931
 
932
+ def calculate_legend_height
933
+ return 0.0 if @hide_legend
934
+
935
+ 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
953
+
954
+ legend_height + @legend_caps_height
955
+ end
956
+
911
957
  # Returns the height of the capital letter 'X' for the current font and
912
958
  # size.
913
959
  #
914
960
  # Not scaled since it deals with dimensions that the regular scaling will
915
961
  # handle.
916
962
  def calculate_caps_height(font_size)
917
- metrics = Renderer::Text.metrics('X', font_size)
963
+ metrics = Renderer::Text.metrics('X', @font, font_size)
918
964
  metrics.height
919
965
  end
920
966
 
@@ -926,7 +972,7 @@ module Gruff
926
972
  text = text.to_s
927
973
  return 0 if text.empty?
928
974
 
929
- metrics = Renderer::Text.metrics(text, font_size)
975
+ metrics = Renderer::Text.metrics(text, @font, font_size)
930
976
  metrics.width
931
977
  end
932
978
 
data/lib/gruff/dot.rb CHANGED
@@ -51,14 +51,14 @@ protected
51
51
  return if @hide_line_markers
52
52
 
53
53
  (0..marker_count).each do |index|
54
- marker_label = minimum_value + index * @increment
54
+ marker_label = BigDecimal(index.to_s) * BigDecimal(@increment.to_s) + BigDecimal(minimum_value.to_s)
55
55
  x = @graph_left + (marker_label - minimum_value) * @graph_width / @spread
56
56
 
57
57
  line_renderer = Gruff::Renderer::Line.new(color: @marker_color, shadow_color: @marker_shadow_color)
58
58
  line_renderer.render(x, @graph_bottom, x, @graph_bottom + 5)
59
59
 
60
60
  unless @hide_line_numbers
61
- label = label(marker_label, @increment)
61
+ label = y_axis_label(marker_label, @increment)
62
62
  text_renderer = Gruff::Renderer::Text.new(label, font: @font, size: @marker_font_size, color: @font_color)
63
63
  text_renderer.add_to_render_queue(0, 0, x, @graph_bottom + (LABEL_MARGIN * 1.5), Magick::CenterGravity)
64
64
  end
@@ -70,8 +70,7 @@ protected
70
70
 
71
71
  def draw_label(y_offset, index)
72
72
  draw_unique_label(index) do
73
- text_renderer = Gruff::Renderer::Text.new(@labels[index], font: @font, size: @marker_font_size, color: @font_color)
74
- text_renderer.add_to_render_queue(@graph_left - LABEL_MARGIN, 1.0, 0.0, y_offset, Magick::EastGravity)
73
+ draw_label_at(@graph_left - LABEL_MARGIN, 1.0, 0.0, y_offset, @labels[index], Magick::EastGravity)
75
74
  end
76
75
  end
77
76
  end
@@ -14,29 +14,44 @@
14
14
  # @private
15
15
  class Gruff::BarConversion
16
16
  attr_writer :mode
17
- attr_writer :zero
18
- attr_writer :graph_top
19
- attr_writer :graph_height
20
- attr_writer :minimum_value
21
- attr_writer :spread
22
17
 
23
- def get_left_y_right_y_scaled(data_point)
18
+ def initialize(top:, bottom:, minimum_value:, maximum_value:, spread:)
19
+ @graph_top = top
20
+ @graph_height = bottom - top
21
+ @spread = spread
22
+ @minimum_value = minimum_value
23
+ @maximum_value = maximum_value
24
+
25
+ if minimum_value >= 0
26
+ # all bars go from zero to positive
27
+ @mode = 1
28
+ elsif maximum_value <= 0
29
+ # all bars go from 0 to negative
30
+ @mode = 2
31
+ else
32
+ # bars either go from zero to negative or to positive
33
+ @mode = 3
34
+ @zero = -minimum_value / @spread
35
+ end
36
+ end
37
+
38
+ def get_top_bottom_scaled(data_point)
24
39
  result = []
25
40
 
26
41
  case @mode
27
42
  when 1
28
43
  # minimum value >= 0 ( only positive values )
29
- result[0] = @graph_top + @graph_height * (1 - data_point) + 1
30
- result[1] = @graph_top + @graph_height - 1
44
+ result[0] = @graph_top + @graph_height * (1 - data_point)
45
+ result[1] = @graph_top + @graph_height
31
46
  when 2
32
47
  # only negative values
33
- result[0] = @graph_top + 1
34
- result[1] = @graph_top + @graph_height * (1 - data_point) - 1
48
+ result[0] = @graph_top
49
+ result[1] = @graph_top + @graph_height * (1 - data_point)
35
50
  when 3
36
51
  # positive and negative values
37
52
  val = data_point - @minimum_value / @spread
38
- result[0] = @graph_top + @graph_height * (1 - (val - @zero)) + 1
39
- result[1] = @graph_top + @graph_height * (1 - @zero) - 1
53
+ result[0] = @graph_top + @graph_height * (1 - (val - @zero))
54
+ result[1] = @graph_top + @graph_height * (1 - @zero)
40
55
  else
41
56
  result[0] = 0.0
42
57
  result[1] = 0.0
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @private
4
+ module Gruff::BarValueLabel
5
+ using String::GruffCommify
6
+
7
+ # @private
8
+ class Base
9
+ attr_reader :coordinate, :value
10
+
11
+ def initialize(coordinate, value)
12
+ @coordinate = coordinate
13
+ @value = value
14
+ end
15
+ end
16
+
17
+ # @private
18
+ class Bar < Base
19
+ def prepare_rendering(format, _bar_width = 0)
20
+ left_x, left_y, right_x, _right_y = @coordinate
21
+ if format.is_a?(Proc)
22
+ val = format.call(@value)
23
+ else
24
+ val = sprintf(format || '%.2f', @value).commify
25
+ end
26
+
27
+ y = @value >= 0 ? left_y - 30 : left_y + 12
28
+ yield left_x + (right_x - left_x) / 2, y, val
29
+ end
30
+ end
31
+
32
+ # @private
33
+ class SideBar < Base
34
+ def prepare_rendering(format, bar_width = 0)
35
+ left_x, _left_y, right_x, right_y = @coordinate
36
+ if format.is_a?(Proc)
37
+ val = format.call(@value)
38
+ else
39
+ val = sprintf(format || '%.2f', @value).commify
40
+ end
41
+
42
+ x = @value >= 0 ? right_x + 40 : left_x - 40
43
+ yield x, right_y - bar_width / 2, val
44
+ end
45
+ end
46
+
47
+ # @private
48
+ class StackedBar
49
+ def initialize
50
+ @bars = []
51
+ end
52
+
53
+ def add(bar, index)
54
+ bars = @bars[index] || []
55
+ bars << bar
56
+ @bars[index] = bars
57
+ end
58
+
59
+ def prepare_rendering(format, bar_width = 0, &block)
60
+ @bars.each do |bars|
61
+ value = bars.sum(&:value)
62
+ bar = bars.last
63
+ bar_value_label = bar.class.new(bar.coordinate, value)
64
+ bar_value_label.prepare_rendering(format, bar_width, &block)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -34,7 +34,7 @@ module Gruff
34
34
  draw.rotation = @rotation if @rotation
35
35
  draw.fill = @font_color
36
36
  draw.stroke = 'transparent'
37
- draw.font = @font if @font
37
+ draw.font = @font || Renderer::Text.default_font(@font_weight)
38
38
  draw.font_weight = @font_weight
39
39
  draw.pointsize = @font_size * scale
40
40
  draw.gravity = gravity
@@ -45,10 +45,11 @@ module Gruff
45
45
  draw.rotation = -@rotation if @rotation
46
46
  end
47
47
 
48
- def self.metrics(text, size, font_weight = Magick::NormalWeight)
48
+ def self.metrics(text, font, size, font_weight = Magick::NormalWeight)
49
49
  draw = Renderer.instance.draw
50
50
  image = Renderer.instance.image
51
51
 
52
+ draw.font = font || Renderer::Text.default_font(font_weight)
52
53
  draw.font_weight = font_weight
53
54
  draw.pointsize = size
54
55
 
@@ -60,5 +61,12 @@ module Gruff
60
61
 
61
62
  draw.get_type_metrics(image, text)
62
63
  end
64
+
65
+ FONT_BOLD = File.expand_path(File.join(__FILE__, '../../../../assets/fonts/Roboto-Bold.ttf'))
66
+ FONT_REGULAR = File.expand_path(File.join(__FILE__, '../../../../assets/fonts/Roboto-Regular.ttf'))
67
+
68
+ def self.default_font(font_weight)
69
+ (font_weight == Magick::BoldWeight) ? FONT_BOLD : FONT_REGULAR
70
+ end
63
71
  end
64
72
  end
data/lib/gruff/scatter.rb CHANGED
@@ -28,17 +28,13 @@ class Gruff::Scatter < Gruff::Base
28
28
  # This is useful when working with a small range of high values (for example, a date range of months, while seconds as units).
29
29
  attr_writer :disable_significant_rounding_x_axis
30
30
 
31
- # Allow enabling vertical lines. When you have a lot of data, they can work great.
32
- attr_writer :enable_vertical_line_markers
31
+ # Allow for vertical marker lines.
32
+ attr_writer :show_vertical_markers
33
33
 
34
34
  # Allow using vertical labels in the X axis (and setting the label margin).
35
35
  attr_writer :x_label_margin
36
36
  attr_writer :use_vertical_x_labels
37
37
 
38
- # Allow passing lambdas to format labels.
39
- attr_writer :y_axis_label_format
40
- attr_writer :x_axis_label_format
41
-
42
38
  def initialize_store
43
39
  @store = Gruff::Store.new(Gruff::Store::XYData)
44
40
  end
@@ -51,17 +47,22 @@ class Gruff::Scatter < Gruff::Base
51
47
  @baseline_x_value = @baseline_y_value = nil
52
48
  @circle_radius = nil
53
49
  @disable_significant_rounding_x_axis = false
54
- @enable_vertical_line_markers = false
50
+ @show_vertical_markers = false
55
51
  @marker_x_count = nil
56
52
  @maximum_x_value = @minimum_x_value = nil
57
53
  @stroke_width = nil
58
54
  @use_vertical_x_labels = false
59
- @x_axis_label_format = nil
60
55
  @x_label_margin = nil
61
- @y_axis_label_format = nil
62
56
  end
63
57
  private :initialize_ivars
64
58
 
59
+ # Allow enabling vertical lines. When you have a lot of data, they can work great.
60
+ # @deprecated Please use +show_vertical_markers+ attribute instead.
61
+ def enable_vertical_line_markers=(value)
62
+ warn '#enable_vertical_line_markers= is deprecated. Please use `show_vertical_markers` attribute instead'
63
+ @show_vertical_markers = value
64
+ end
65
+
65
66
  def draw
66
67
  super
67
68
  return unless data_given?
@@ -191,7 +192,7 @@ private
191
192
  calculate_spread
192
193
  normalize
193
194
 
194
- self.marker_count = (@x_spread / @x_axis_increment).to_i
195
+ @marker_x_count = (@x_spread / @x_axis_increment).to_i
195
196
  @x_increment = @x_axis_increment
196
197
  end
197
198
  increment_x_scaled = @graph_width.to_f / (@x_spread / @x_increment)
@@ -199,7 +200,7 @@ private
199
200
  # Draw vertical line markers and annotate with numbers
200
201
  (0..@marker_x_count).each do |index|
201
202
  # TODO: Fix the vertical lines, and enable them by default. Not pretty when they don't match up with top y-axis line
202
- if @enable_vertical_line_markers
203
+ if @show_vertical_markers
203
204
  x = @graph_left + @graph_width - index.to_f * increment_x_scaled
204
205
 
205
206
  line_renderer = Gruff::Renderer::Line.new(color: @marker_color, shadow_color: @marker_shadow_color)
@@ -207,11 +208,11 @@ private
207
208
  end
208
209
 
209
210
  unless @hide_line_numbers
210
- marker_label = index * @x_increment + @minimum_x_value.to_f
211
+ marker_label = BigDecimal(index.to_s) * BigDecimal(@x_increment.to_s) + BigDecimal(@minimum_x_value.to_s)
211
212
  y_offset = @graph_bottom + (@x_label_margin || LABEL_MARGIN)
212
213
  x_offset = get_x_coord(index.to_f, increment_x_scaled, @graph_left)
213
214
 
214
- label = vertical_label(marker_label, @x_increment)
215
+ label = x_axis_label(marker_label, @x_increment)
215
216
  rotation = -90.0 if @use_vertical_x_labels
216
217
  text_renderer = Gruff::Renderer::Text.new(label, font: @font, size: @marker_font_size, color: @font_color, rotation: rotation)
217
218
  text_renderer.add_to_render_queue(1.0, 1.0, x_offset, y_offset)
@@ -219,22 +220,6 @@ private
219
220
  end
220
221
  end
221
222
 
222
- def label(value, increment)
223
- if @y_axis_label_format
224
- @y_axis_label_format.call(value)
225
- else
226
- super
227
- end
228
- end
229
-
230
- def vertical_label(value, increment)
231
- if @x_axis_label_format
232
- @x_axis_label_format.call(value)
233
- else
234
- label(value, increment)
235
- end
236
- end
237
-
238
223
  def get_x_coord(x_data_point, width, offset) #:nodoc:
239
224
  x_data_point * width + offset
240
225
  end