gruff 0.2.4 → 0.2.5

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