gruff 0.12.2 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
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