gruff 0.2.4 → 0.2.5

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/line.rb CHANGED
@@ -41,12 +41,7 @@ class Gruff::Line < Gruff::Base
41
41
 
42
42
  # Check to see if more than one datapoint was given. NaN can result otherwise.
43
43
  @x_increment = (@column_count > 1) ? (@graph_width / (@column_count - 1).to_f) : @graph_width
44
-
45
- circle_radius = clip_value_if_greater_than(@columns / (@norm_data.first[1].size * 2.5), 5.0)
46
-
47
- @d = @d.stroke_opacity 1.0
48
- @d = @d.stroke_width clip_value_if_greater_than(@columns / (@norm_data.first[1].size * 4), 5.0)
49
-
44
+
50
45
  if (defined?(@norm_baseline)) then
51
46
  level = @graph_top + (@graph_height - @norm_baseline * @graph_height)
52
47
  @d = @d.push
@@ -58,10 +53,8 @@ class Gruff::Line < Gruff::Base
58
53
  @d = @d.pop
59
54
  end
60
55
 
61
- @norm_data.each do |data_row|
56
+ @norm_data.each do |data_row|
62
57
  prev_x = prev_y = nil
63
- @d = @d.stroke data_row[DATA_COLOR_INDEX]
64
- @d = @d.fill data_row[DATA_COLOR_INDEX]
65
58
 
66
59
  data_row[1].each_with_index do |data_point, index|
67
60
  new_x = @graph_left + (@x_increment * index)
@@ -71,9 +64,16 @@ class Gruff::Line < Gruff::Base
71
64
 
72
65
  new_y = @graph_top + (@graph_height - data_point * @graph_height)
73
66
 
74
- if !@hide_lines and !prev_x.nil? and !prev_y.nil? then
67
+ # Reset each time to avoid thin-line errors
68
+ @d = @d.stroke data_row[DATA_COLOR_INDEX]
69
+ @d = @d.fill data_row[DATA_COLOR_INDEX]
70
+ @d = @d.stroke_opacity 1.0
71
+ @d = @d.stroke_width clip_value_if_greater_than(@columns / (@norm_data.first[1].size * 4), 5.0)
72
+
73
+ if !@hide_lines and !prev_x.nil? and !prev_y.nil? then
75
74
  @d = @d.line(prev_x, prev_y, new_x, new_y)
76
75
  end
76
+ circle_radius = clip_value_if_greater_than(@columns / (@norm_data.first[1].size * 2.5), 5.0)
77
77
  @d = @d.circle(new_x, new_y, new_x - circle_radius, new_y) unless @hide_dots
78
78
 
79
79
  prev_x = new_x
@@ -86,13 +86,9 @@ class Gruff::Line < Gruff::Base
86
86
  end
87
87
 
88
88
  def normalize
89
- @maximum_value = max(@maximum_value.to_f, @baseline_value.to_f)
89
+ @maximum_value = [@maximum_value.to_f, @baseline_value.to_f].max
90
90
  super
91
91
  @norm_baseline = (@baseline_value.to_f / @maximum_value.to_f) if @baseline_value
92
92
  end
93
93
 
94
- def max(a, b)
95
- (a < b ? b : a)
96
- end
97
-
98
94
  end
@@ -0,0 +1,32 @@
1
+ ##
2
+ #
3
+ # Makes a small bar graph suitable for display at 200px or even smaller.
4
+ #
5
+ module Gruff
6
+ module Mini
7
+
8
+ class Bar < Gruff::Bar
9
+
10
+ include Gruff::Mini::Legend
11
+
12
+ def draw
13
+ @hide_legend = true
14
+ @hide_title = true
15
+ @hide_line_numbers = true
16
+
17
+ @marker_font_size = 40.0
18
+ @minimum_value = 0.0
19
+ @legend_font_size = 60.0
20
+
21
+ expand_canvas_for_vertical_legend
22
+
23
+ super
24
+
25
+ draw_vertical_legend
26
+ @d.draw(@base_image)
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,77 @@
1
+ module Gruff
2
+ module Mini
3
+ module Legend
4
+
5
+ ##
6
+ # The canvas needs to be bigger so we can put the legend beneath it.
7
+
8
+ def expand_canvas_for_vertical_legend
9
+ @original_rows = @raw_rows
10
+ @rows += @data.length * calculate_caps_height(scale_fontsize(@legend_font_size)) * 1.7
11
+ render_background
12
+ end
13
+
14
+ ##
15
+ # Draw the legend beneath the existing graph.
16
+
17
+ def draw_vertical_legend
18
+
19
+ @legend_labels = @data.collect {|item| item[Gruff::Base::DATA_LABEL_INDEX] }
20
+
21
+ legend_square_width = 40.0 # small square with color of this item
22
+ legend_square_margin = 10.0
23
+ @legend_left_margin = 40.0
24
+ legend_top_margin = 40.0
25
+
26
+ # May fix legend drawing problem at small sizes
27
+ @d.font = @font if @font
28
+ @d.pointsize = @legend_font_size
29
+
30
+ current_x_offset = @graph_left + @legend_left_margin
31
+ current_y_offset = @original_rows + legend_top_margin
32
+
33
+ debug { @d.line 0.0, current_y_offset, @raw_columns, current_y_offset }
34
+
35
+ @legend_labels.each_with_index do |legend_label, index|
36
+
37
+ # Draw label
38
+ @d.fill = @font_color
39
+ @d.font = @font if @font
40
+ @d.pointsize = scale_fontsize(@legend_font_size)
41
+ @d.stroke = 'transparent'
42
+ @d.font_weight = Magick::NormalWeight
43
+ @d.gravity = Magick::WestGravity
44
+ @d = @d.annotate_scaled( @base_image,
45
+ @raw_columns, 1.0,
46
+ current_x_offset + (legend_square_width * 1.7), current_y_offset,
47
+ truncate_legend_label(legend_label), @scale)
48
+
49
+ # Now draw box with color of this dataset
50
+ @d = @d.stroke 'transparent'
51
+ @d = @d.fill @data[index][Gruff::Base::DATA_COLOR_INDEX]
52
+ @d = @d.rectangle(current_x_offset,
53
+ current_y_offset - legend_square_width / 2.0,
54
+ current_x_offset + legend_square_width,
55
+ current_y_offset + legend_square_width / 2.0)
56
+
57
+ current_y_offset += calculate_caps_height(@legend_font_size) * 1.7
58
+ end
59
+ @color_index = 0
60
+ end
61
+
62
+ ##
63
+ # Shorten long labels so they will fit on the canvas.
64
+ #
65
+ # Department of Hu...
66
+
67
+ def truncate_legend_label(label)
68
+ truncated_label = label.to_s
69
+ while calculate_width(scale_fontsize(@legend_font_size), truncated_label) > (@columns - @legend_left_margin - Gruff::Base::RIGHT_MARGIN) && (truncated_label.length > 1)
70
+ truncated_label = truncated_label[0..truncated_label.length-2]
71
+ end
72
+ truncated_label + (truncated_label.length < label.to_s.length ? "…" : '')
73
+ end
74
+
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,32 @@
1
+ ##
2
+ #
3
+ # Makes a small pie graph suitable for display at 200px or even smaller.
4
+ #
5
+ module Gruff
6
+ module Mini
7
+
8
+ class Pie < Gruff::Pie
9
+
10
+ include Gruff::Mini::Legend
11
+
12
+ def draw
13
+ @hide_legend = true
14
+ @hide_title = true
15
+ @hide_line_numbers = true
16
+
17
+ @marker_font_size = 40.0
18
+ @legend_font_size = 60.0
19
+
20
+ expand_canvas_for_vertical_legend
21
+
22
+ super
23
+
24
+ draw_vertical_legend
25
+
26
+ @d.draw(@base_image)
27
+ end # def draw
28
+
29
+ end # class Pie
30
+
31
+ end
32
+ end
@@ -0,0 +1,22 @@
1
+ ##
2
+ #
3
+ # Makes a small pie graph suitable for display at 200px or even smaller.
4
+ #
5
+ module Gruff
6
+ module Mini
7
+
8
+ class SideBar < Gruff::Base
9
+
10
+ def draw
11
+ @hide_legend = true
12
+ @hide_title = true
13
+ @hide_line_numbers = true
14
+
15
+ @marker_font_size = 40.0
16
+ super
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+ end
data/lib/gruff/pie.rb CHANGED
@@ -3,6 +3,8 @@ require File.dirname(__FILE__) + '/base'
3
3
 
4
4
  class Gruff::Pie < Gruff::Base
5
5
 
6
+ TEXT_OFFSET_PERCENTAGE = 0.15
7
+
6
8
  def draw
7
9
  @hide_line_markers = true
8
10
 
@@ -19,13 +21,13 @@ class Gruff::Pie < Gruff::Base
19
21
  prev_degrees = 0.0
20
22
 
21
23
  # Use full data since we can easily calculate percentages
22
- @data.each do |data_row|
23
- if data_row[1][0] > 0
24
+ @data.sort{ |a, b| a[DATA_VALUES_INDEX][0] <=> b[DATA_VALUES_INDEX][0] }.each do |data_row|
25
+ if data_row[DATA_VALUES_INDEX][0] > 0
24
26
  @d = @d.stroke data_row[DATA_COLOR_INDEX]
25
27
  @d = @d.fill 'transparent'
26
28
  @d.stroke_width(radius) # stroke width should be equal to radius. we'll draw centered on (radius / 2)
27
29
 
28
- current_degrees = (data_row[1][0] / total_sum) * 360.0
30
+ current_degrees = (data_row[DATA_VALUES_INDEX][0] / total_sum) * 360.0
29
31
 
30
32
  # ellipse will draw the the stroke centered on the first two parameters offset by the second two.
31
33
  # therefore, in order to draw a circle of the proper diameter we must center the stroke at
@@ -35,33 +37,42 @@ class Gruff::Pie < Gruff::Base
35
37
  prev_degrees, prev_degrees + current_degrees + 0.5) # <= +0.5 'fudge factor' gets rid of the ugly gaps
36
38
 
37
39
  half_angle = prev_degrees + ((prev_degrees + current_degrees) - prev_degrees) / 2
38
-
39
40
 
41
+ # End the string with %% to escape the single %.
42
+ # RMagick must use sprintf with the string and % has special significance.
43
+ label_string = ((data_row[DATA_VALUES_INDEX][0] / total_sum) * 100.0).round.to_s + '%%'
40
44
  @d = draw_label(center_x,center_y,
41
- half_angle,
42
- radius,
43
- ((data_row[1][0] / total_sum) * 100).round.to_s + '% ')
45
+ half_angle, radius + (radius * TEXT_OFFSET_PERCENTAGE),
46
+ label_string)
44
47
 
45
48
  prev_degrees += current_degrees
46
49
  end
47
50
  end
48
51
 
52
+ # TODO debug a circle where the text is drawn...
53
+
49
54
  @d.draw(@base_image)
50
55
  end
51
56
 
52
57
  private
53
58
 
59
+ ##
60
+ # Labels are drawn around a slightly wider ellipse to give room for
61
+ # labels on the left and right.
54
62
  def draw_label(center_x, center_y, angle, radius, amount)
55
- r_offset = 30 # The distance out from the center of the pie to get point
56
- x_offset = center_x + 15 # The label points need to be tweaked slightly
57
- y_offset = center_y + 0 # This one doesn't though
58
- x = x_offset + ((radius + r_offset) * Math.cos(angle.deg2rad))
59
- y = y_offset + ((radius + r_offset) * Math.sin(angle.deg2rad))
63
+ # TODO Don't use so many hard-coded numbers
64
+ r_offset = 5.0 # The distance out from the center of the pie to get point
65
+ x_offset = center_x # + 15.0 # The label points need to be tweaked slightly
66
+ y_offset = center_y # This one doesn't though
67
+ radius_offset = (radius + r_offset)
68
+ ellipse_factor = radius_offset * 0.10
69
+ x = x_offset + ((radius_offset + ellipse_factor) * Math.cos(angle.deg2rad))
70
+ y = y_offset + (radius_offset * Math.sin(angle.deg2rad))
60
71
 
61
72
  # Draw label
62
- @d.fill = @marker_color
73
+ @d.fill = @font_color
63
74
  @d.font = @font if @font
64
- @d.pointsize = scale_fontsize(20)
75
+ @d.pointsize = scale_fontsize(@marker_font_size)
65
76
  @d.stroke = 'transparent'
66
77
  @d.font_weight = BoldWeight
67
78
  @d.gravity = CenterGravity
@@ -73,7 +84,7 @@ private
73
84
 
74
85
  def sums_for_pie
75
86
  total_sum = 0.0
76
- @data.collect {|data_row| total_sum += data_row[1][0] }
87
+ @data.collect {|data_row| total_sum += data_row[DATA_VALUES_INDEX][0] }
77
88
  total_sum
78
89
  end
79
90
 
@@ -59,6 +59,7 @@ class Gruff::SideStackedBar < Gruff::Base
59
59
  end
60
60
 
61
61
  def draw
62
+ @has_left_labels = true
62
63
  get_maximum_by_stack
63
64
  super
64
65
 
@@ -2,9 +2,54 @@ $:.unshift(File.dirname(__FILE__) + "/../lib/")
2
2
 
3
3
  require 'test/unit'
4
4
  require 'gruff'
5
+ require 'test_timer'
5
6
 
6
7
  class GruffTestCase < Test::Unit::TestCase
7
8
 
9
+ def setup
10
+ @datasets = [
11
+ [:Jimmy, [25, 36, 86, 39, 25, 31, 79, 88]],
12
+ [:Charles, [80, 54, 67, 54, 68, 70, 90, 95]],
13
+ [:Julie, [22, 29, 35, 38, 36, 40, 46, 57]],
14
+ [:Jane, [95, 95, 95, 90, 85, 80, 88, 100]],
15
+ [:Philip, [90, 34, 23, 12, 78, 89, 98, 88]],
16
+ ["Arthur", [5, 10, 13, 11, 6, 16, 22, 32]],
17
+ ]
18
+
19
+ @labels = {
20
+ 0 => '5/6',
21
+ 1 => '5/15',
22
+ 2 => '5/24',
23
+ 3 => '5/30',
24
+ 4 => '6/4',
25
+ 5 => '6/12',
26
+ 6 => '6/21',
27
+ 7 => '6/28',
28
+ }
29
+ end
30
+
31
+ def setup_single_dataset
32
+ @datasets = [
33
+ [:Jimmy, [25, 36, 86]]
34
+ ]
35
+
36
+ @labels = {
37
+ 0 => 'You',
38
+ 1 => 'Average',
39
+ 2 => 'Lifetime'
40
+ }
41
+ end
42
+
43
+ def setup_wide_dataset
44
+ @datasets = [
45
+ ["Auto", 25],
46
+ ["Food", 5],
47
+ ["Entertainment", 15]
48
+ ]
49
+
50
+ @labels = { 0 => 'This Month' }
51
+ end
52
+
8
53
  def test_dummy
9
54
  assert true
10
55
  end
@@ -19,12 +64,13 @@ protected
19
64
  # g.data('students', [1, 2, 3, 4])
20
65
  # end
21
66
  #
22
- def graph_sized(filename, &block)
67
+ def graph_sized(filename, sizes=['', 400])
23
68
  class_name = self.class.name.gsub(/^TestGruff/, '')
24
- ['', 400].each do |size|
69
+ Array(sizes).each do |size|
25
70
  g = instance_eval("Gruff::#{class_name}.new #{size}")
26
- block.call g
27
- write_test_file g, "#{filename}#{size}.png"
71
+ g.title = "#{class_name} Graph"
72
+ yield g
73
+ write_test_file g, "#{filename}_#{size}.png"
28
74
  end
29
75
  end
30
76
 
@@ -32,4 +78,40 @@ protected
32
78
  graph.write(File.dirname(__FILE__) + "/output/#{filename}")
33
79
  end
34
80
 
81
+ ##
82
+ # Example:
83
+ #
84
+ # setup_basic_graph Gruff::Pie, 400
85
+ #
86
+ def setup_basic_graph(*args)
87
+ klass, size = Gruff::Bar, 400
88
+ # Allow args to be klass, size or just klass or just size.
89
+ #
90
+ # TODO Refactor
91
+ case args.length
92
+ when 1
93
+ case args[0]
94
+ when Fixnum
95
+ size = args[0]
96
+ klass = eval("Gruff::#{self.class.name.gsub(/^TestGruff/, '')}")
97
+ when String
98
+ size = args[0]
99
+ klass = eval("Gruff::#{self.class.name.gsub(/^TestGruff/, '')}")
100
+ else
101
+ klass = args[0]
102
+ end
103
+ when 2
104
+ klass, size = args[0], args[1]
105
+ end
106
+
107
+ g = klass.new(size)
108
+ g.title = "My Bar Graph"
109
+ g.labels = @labels
110
+
111
+ @datasets.each do |data|
112
+ g.data(data[0], data[1])
113
+ end
114
+ g
115
+ end
116
+
35
117
  end
data/test/test_bar.rb CHANGED
@@ -120,6 +120,32 @@ class TestGruffBar < GruffTestCase
120
120
  g.write("test/output/bar_x_y_labels.png")
121
121
  end
122
122
 
123
+ def test_fireball
124
+ g = Gruff::Bar.new
125
+ g.hide_title = true
126
+ g.hide_line_numbers = true
127
+
128
+ running_totals = []
129
+ new_additions = []
130
+
131
+ (0..19).each do |i|
132
+ add = rand(10)
133
+ case i
134
+ when 0
135
+ running_totals << 0
136
+ new_additions << add
137
+ else
138
+ running_totals << new_additions[i-1]
139
+ new_additions << running_totals[i-1] + add
140
+ end
141
+ end
142
+
143
+ g.data '1', running_totals
144
+ g.data '2', new_additions
145
+
146
+ g.write("test/output/bar_fireball.png")
147
+ end
148
+
123
149
 
124
150
  def test_wide_graph
125
151
  g = setup_basic_graph('800x400')
@@ -221,6 +247,7 @@ class TestGruffBar < GruffTestCase
221
247
  g.theme = {
222
248
  :colors => %w(#efd250 #666699 #e5573f #9595e2),
223
249
  :marker_color => 'white',
250
+ :font_color => 'blue',
224
251
  :background_image => "assets/pc306715.jpg"
225
252
  }
226
253
  g.labels = {