gruff 0.3.4 → 0.3.6

Sign up to get free protection for your applications and to get access to all the features.
data/lib/gruff/dot.rb ADDED
@@ -0,0 +1,113 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+
3
+ ##
4
+ # Graph with dots and labels along a vertical access
5
+ # see: 'Creating More Effective Graphs' by Robbins
6
+
7
+ class Gruff::Dot < Gruff::Base
8
+
9
+ def draw
10
+ @has_left_labels = true
11
+ super
12
+
13
+ return unless @has_data
14
+
15
+ # Setup spacing.
16
+ #
17
+ spacing_factor = 1.0
18
+
19
+ @items_width = @graph_height / @column_count.to_f
20
+ @item_width = @items_width * spacing_factor / @norm_data.size
21
+ @d = @d.stroke_opacity 0.0
22
+ height = Array.new(@column_count, 0)
23
+ length = Array.new(@column_count, @graph_left)
24
+ padding = (@items_width * (1 - spacing_factor)) / 2
25
+
26
+ @norm_data.each_with_index do |data_row, row_index|
27
+ data_row[DATA_VALUES_INDEX].each_with_index do |data_point, point_index|
28
+
29
+ x_pos = @graph_left + (data_point * @graph_width) - (@item_width.to_f/6.0).round
30
+ y_pos = @graph_top + (@items_width * point_index) + padding + (@item_width.to_f/2.0).round
31
+
32
+ if row_index == 0
33
+ @d = @d.stroke(@marker_color)
34
+ @d = @d.stroke_width 1.0
35
+ @d = @d.opacity 0.1
36
+ @d = @d.line(@graph_left, y_pos, @graph_left + @graph_width, y_pos)
37
+ end
38
+
39
+ @d = @d.fill data_row[DATA_COLOR_INDEX]
40
+ @d = @d.stroke('transparent')
41
+ @d = @d.circle(x_pos, y_pos, x_pos + (@item_width.to_f/3.0).round, y_pos)
42
+
43
+ # Calculate center based on item_width and current row
44
+ label_center = @graph_top + (@items_width * point_index + @items_width / 2) + padding
45
+ draw_label(label_center, point_index)
46
+ end
47
+
48
+ end
49
+
50
+ @d.draw(@base_image)
51
+ end
52
+
53
+ protected
54
+
55
+ # Instead of base class version, draws vertical background lines and label
56
+ def draw_line_markers
57
+
58
+ return if @hide_line_markers
59
+
60
+ @d = @d.stroke_antialias false
61
+
62
+ # Draw horizontal line markers and annotate with numbers
63
+ @d = @d.stroke(@marker_color)
64
+ @d = @d.stroke_width 1
65
+ number_of_lines = 5
66
+
67
+ # TODO Round maximum marker value to a round number like 100, 0.1, 0.5, etc.
68
+ increment = significant(@maximum_value.to_f / number_of_lines)
69
+ (0..number_of_lines).each do |index|
70
+
71
+ line_diff = (@graph_right - @graph_left) / number_of_lines
72
+ x = @graph_right - (line_diff * index) - 1
73
+ @d = @d.line(x, @graph_bottom, x, @graph_bottom + 0.5 * LABEL_MARGIN)
74
+ diff = index - number_of_lines
75
+ marker_label = diff.abs * increment
76
+
77
+ unless @hide_line_numbers
78
+ @d.fill = @font_color
79
+ @d.font = @font if @font
80
+ @d.stroke = 'transparent'
81
+ @d.pointsize = scale_fontsize(@marker_font_size)
82
+ @d.gravity = CenterGravity
83
+ # TODO Center text over line
84
+ @d = @d.annotate_scaled( @base_image,
85
+ 0, 0, # Width of box to draw text in
86
+ x, @graph_bottom + (LABEL_MARGIN * 2.0), # Coordinates of text
87
+ marker_label.to_s, @scale)
88
+ end # unless
89
+ @d = @d.stroke_antialias true
90
+ end
91
+ end
92
+
93
+ ##
94
+ # Draw on the Y axis instead of the X
95
+
96
+ def draw_label(y_offset, index)
97
+ if !@labels[index].nil? && @labels_seen[index].nil?
98
+ @d.fill = @font_color
99
+ @d.font = @font if @font
100
+ @d.stroke = 'transparent'
101
+ @d.font_weight = NormalWeight
102
+ @d.pointsize = scale_fontsize(@marker_font_size)
103
+ @d.gravity = EastGravity
104
+ @d = @d.annotate_scaled(@base_image,
105
+ 1, 1,
106
+ -@graph_left + LABEL_MARGIN * 2.0, y_offset,
107
+ @labels[index], @scale)
108
+ @labels_seen[index] = 1
109
+ end
110
+ end
111
+
112
+ end
113
+
data/lib/gruff/line.rb CHANGED
@@ -16,10 +16,14 @@ class Gruff::Line < Gruff::Base
16
16
 
17
17
  # Draw a dashed line at the given value
18
18
  attr_accessor :baseline_value
19
-
19
+
20
20
  # Color of the baseline
21
21
  attr_accessor :baseline_color
22
22
 
23
+ # Dimensions of lines and dots; calculated based on dataset size if left unspecified
24
+ attr_accessor :line_width
25
+ attr_accessor :dot_radius
26
+
23
27
  # Hide parts of the graph to fit more datapoints, or for a different appearance.
24
28
  attr_accessor :hide_dots, :hide_lines
25
29
 
@@ -30,7 +34,7 @@ class Gruff::Line < Gruff::Base
30
34
  # g = Gruff::Line.new(400, false) # 400px wide, no lines (for backwards compatibility)
31
35
  #
32
36
  # g = Gruff::Line.new(false) # Defaults to 800px wide, no lines (for backwards compatibility)
33
- #
37
+ #
34
38
  # The preferred way is to call hide_dots or hide_lines instead.
35
39
  def initialize(*args)
36
40
  raise ArgumentError, "Wrong number of arguments" if args.length > 2
@@ -39,7 +43,7 @@ class Gruff::Line < Gruff::Base
39
43
  else
40
44
  super args.shift
41
45
  end
42
-
46
+
43
47
  @hide_dots = @hide_lines = false
44
48
  @baseline_color = 'red'
45
49
  @baseline_value = nil
@@ -49,10 +53,10 @@ class Gruff::Line < Gruff::Base
49
53
  super
50
54
 
51
55
  return unless @has_data
52
-
53
- # Check to see if more than one datapoint was given. NaN can result otherwise.
56
+
57
+ # Check to see if more than one datapoint was given. NaN can result otherwise.
54
58
  @x_increment = (@column_count > 1) ? (@graph_width / (@column_count - 1).to_f) : @graph_width
55
-
59
+
56
60
  if (defined?(@norm_baseline)) then
57
61
  level = @graph_top + (@graph_height - @norm_baseline * @graph_height)
58
62
  @d = @d.push
@@ -64,9 +68,11 @@ class Gruff::Line < Gruff::Base
64
68
  @d = @d.pop
65
69
  end
66
70
 
67
- @norm_data.each do |data_row|
71
+ @norm_data.each do |data_row|
68
72
  prev_x = prev_y = nil
69
73
 
74
+ @one_point = contains_one_point_only?(data_row)
75
+
70
76
  data_row[DATA_VALUES_INDEX].each_with_index do |data_point, index|
71
77
  new_x = @graph_left + (@x_increment * index)
72
78
  next if data_point.nil?
@@ -79,12 +85,19 @@ class Gruff::Line < Gruff::Base
79
85
  @d = @d.stroke data_row[DATA_COLOR_INDEX]
80
86
  @d = @d.fill data_row[DATA_COLOR_INDEX]
81
87
  @d = @d.stroke_opacity 1.0
82
- @d = @d.stroke_width clip_value_if_greater_than(@columns / (@norm_data.first[DATA_VALUES_INDEX].size * 4), 5.0)
88
+ @d = @d.stroke_width line_width ||
89
+ clip_value_if_greater_than(@columns / (@norm_data.first[DATA_VALUES_INDEX].size * 4), 5.0)
90
+
83
91
 
84
- if !@hide_lines and !prev_x.nil? and !prev_y.nil? then
92
+ circle_radius = dot_radius ||
93
+ clip_value_if_greater_than(@columns / (@norm_data.first[DATA_VALUES_INDEX].size * 2.5), 5.0)
94
+
95
+ if !@hide_lines and !prev_x.nil? and !prev_y.nil? then
85
96
  @d = @d.line(prev_x, prev_y, new_x, new_y)
97
+ elsif @one_point
98
+ # Show a circle if there's just one_point
99
+ @d = @d.circle(new_x, new_y, new_x - circle_radius, new_y)
86
100
  end
87
- circle_radius = clip_value_if_greater_than(@columns / (@norm_data.first[DATA_VALUES_INDEX].size * 2.5), 5.0)
88
101
  @d = @d.circle(new_x, new_y, new_x - circle_radius, new_y) unless @hide_dots
89
102
 
90
103
  prev_x = new_x
@@ -101,5 +114,22 @@ class Gruff::Line < Gruff::Base
101
114
  super
102
115
  @norm_baseline = (@baseline_value.to_f / @maximum_value.to_f) if @baseline_value
103
116
  end
104
-
117
+
118
+ def contains_one_point_only?(data_row)
119
+ # Spin through data to determine if there is just one_value present.
120
+ one_point = false
121
+ data_row[DATA_VALUES_INDEX].each do |data_point|
122
+ if !data_point.nil?
123
+ if one_point
124
+ # more than one point, bail
125
+ return false
126
+ else
127
+ # there is at least one data point
128
+ return true
129
+ end
130
+ end
131
+ end
132
+ return one_point
133
+ end
134
+
105
135
  end
@@ -9,15 +9,20 @@ module Gruff
9
9
 
10
10
  include Gruff::Mini::Legend
11
11
 
12
- def draw
12
+ def initialize_ivars
13
+ super
14
+
13
15
  @hide_legend = true
14
16
  @hide_title = true
15
17
  @hide_line_numbers = true
16
18
 
17
19
  @marker_font_size = 50.0
18
20
  @minimum_value = 0.0
21
+ @maximum_value = 0.0
19
22
  @legend_font_size = 60.0
23
+ end
20
24
 
25
+ def draw
21
26
  expand_canvas_for_vertical_legend
22
27
 
23
28
  super
@@ -2,10 +2,14 @@ module Gruff
2
2
  module Mini
3
3
  module Legend
4
4
 
5
+ attr_accessor :hide_mini_legend
6
+
5
7
  ##
6
8
  # The canvas needs to be bigger so we can put the legend beneath it.
7
9
 
8
10
  def expand_canvas_for_vertical_legend
11
+ return if @hide_mini_legend
12
+
9
13
  @original_rows = @raw_rows
10
14
  @rows += @data.length * calculate_caps_height(scale_fontsize(@legend_font_size)) * 1.7
11
15
  render_background
@@ -15,7 +19,8 @@ module Gruff
15
19
  # Draw the legend beneath the existing graph.
16
20
 
17
21
  def draw_vertical_legend
18
-
22
+ return if @hide_mini_legend
23
+
19
24
  @legend_labels = @data.collect {|item| item[Gruff::Base::DATA_LABEL_INDEX] }
20
25
 
21
26
  legend_square_width = 40.0 # small square with color of this item
@@ -69,7 +74,7 @@ module Gruff
69
74
  while calculate_width(scale_fontsize(@legend_font_size), truncated_label) > (@columns - @legend_left_margin - @right_margin) && (truncated_label.length > 1)
70
75
  truncated_label = truncated_label[0..truncated_label.length-2]
71
76
  end
72
- truncated_label + (truncated_label.length < label.to_s.length ? "" : '')
77
+ truncated_label + (truncated_label.length < label.to_s.length ? "..." : '')
73
78
  end
74
79
 
75
80
  end
data/lib/gruff/net.rb CHANGED
@@ -6,11 +6,16 @@ class Gruff::Net < Gruff::Base
6
6
 
7
7
  # Hide parts of the graph to fit more datapoints, or for a different appearance.
8
8
  attr_accessor :hide_dots
9
+
10
+ # Dimensions of lines and dots; calculated based on dataset size if left unspecified
11
+ attr_accessor :line_width
12
+ attr_accessor :dot_radius
9
13
 
10
14
  def initialize(*args)
11
15
  super
12
16
 
13
17
  @hide_dots = false
18
+ @hide_line_numbers = true
14
19
  end
15
20
 
16
21
  def draw
@@ -24,10 +29,12 @@ class Gruff::Net < Gruff::Base
24
29
  @center_y = @graph_top + (@graph_height / 2.0) - 10 # Move graph up a bit
25
30
 
26
31
  @x_increment = @graph_width / (@column_count - 1).to_f
27
- circle_radius = clip_value_if_greater_than(@columns / (@norm_data.first[DATA_VALUES_INDEX].size * 2.5), 5.0)
32
+ circle_radius = dot_radius ||
33
+ clip_value_if_greater_than(@columns / (@norm_data.first[DATA_VALUES_INDEX].size * 2.5), 5.0)
28
34
 
29
35
  @d = @d.stroke_opacity 1.0
30
- @d = @d.stroke_width clip_value_if_greater_than(@columns / (@norm_data.first[DATA_VALUES_INDEX].size * 4), 5.0)
36
+ @d = @d.stroke_width line_width ||
37
+ clip_value_if_greater_than(@columns / (@norm_data.first[DATA_VALUES_INDEX].size * 4), 5.0)
31
38
 
32
39
  if (defined?(@norm_baseline)) then
33
40
  level = @graph_top + (@graph_height - @norm_baseline * @graph_height)
@@ -115,16 +122,7 @@ private
115
122
  @d.pointsize = scale_fontsize(20)
116
123
  @d.stroke = 'transparent'
117
124
  @d.font_weight = BoldWeight
118
- s = angle.deg2rad / (2*Math::PI)
119
- @d.gravity = SouthGravity if s >= 0.96 or s < 0.04
120
- @d.gravity = SouthWestGravity if s >= 0.04 or s < 0.21
121
- @d.gravity = WestGravity if s >= 0.21 or s < 0.29
122
- @d.gravity = NorthWestGravity if s >= 0.29 or s < 0.46
123
- @d.gravity = NorthGravity if s >= 0.46 or s < 0.54
124
- @d.gravity = NorthEastGravity if s >= 0.54 or s < 0.71
125
- @d.gravity = EastGravity if s >= 0.71 or s < 0.79
126
- @d.gravity = SouthEastGravity if s >= 0.79 or s < 0.96
127
- # @d.gravity = NorthGravity
125
+ @d.gravity = CenterGravity
128
126
  @d.annotate_scaled(@base_image, 0, 0, x, y, amount, @scale)
129
127
  end
130
128
 
@@ -13,13 +13,14 @@ class Gruff::SideBar < Gruff::Base
13
13
 
14
14
  # Setup spacing.
15
15
  #
16
- spacing_factor = 0.9
16
+ @bar_spacing ||= 0.9
17
17
 
18
18
  @bars_width = @graph_height / @column_count.to_f
19
- @bar_width = @bars_width * spacing_factor / @norm_data.size
19
+ @bar_width = @bars_width * @bar_spacing / @norm_data.size
20
20
  @d = @d.stroke_opacity 0.0
21
21
  height = Array.new(@column_count, 0)
22
22
  length = Array.new(@column_count, @graph_left)
23
+ padding = (@bars_width * (1 - @bar_spacing)) / 2
23
24
 
24
25
  @norm_data.each_with_index do |data_row, row_index|
25
26
  @d = @d.fill data_row[DATA_COLOR_INDEX]
@@ -34,7 +35,7 @@ class Gruff::SideBar < Gruff::Base
34
35
  difference = temp2 - temp1
35
36
 
36
37
  left_x = length[point_index] - 1
37
- left_y = @graph_top + (@bars_width * point_index) + (@bar_width * row_index)
38
+ left_y = @graph_top + (@bars_width * point_index) + (@bar_width * row_index) + padding
38
39
  right_x = left_x + difference
39
40
  right_y = left_y + @bar_width
40
41
 
@@ -21,12 +21,13 @@ class Gruff::SideStackedBar < Gruff::SideBar
21
21
  # Setup spacing.
22
22
  #
23
23
  # Columns sit stacked.
24
- spacing_factor = 0.9
24
+ @bar_spacing ||= 0.9
25
25
 
26
26
  @bar_width = @graph_height / @column_count.to_f
27
27
  @d = @d.stroke_opacity 0.0
28
28
  height = Array.new(@column_count, 0)
29
29
  length = Array.new(@column_count, @graph_left)
30
+ padding = (@bar_width * (1 - @bar_spacing)) / 2
30
31
 
31
32
  @norm_data.each_with_index do |data_row, row_index|
32
33
  @d = @d.fill data_row[DATA_COLOR_INDEX]
@@ -42,16 +43,16 @@ class Gruff::SideStackedBar < Gruff::SideBar
42
43
  difference = temp2 - temp1
43
44
 
44
45
  left_x = length[point_index] #+ 1
45
- left_y = @graph_top + (@bar_width * point_index)
46
+ left_y = @graph_top + (@bar_width * point_index) + padding
46
47
  right_x = left_x + difference
47
- right_y = left_y + @bar_width * spacing_factor
48
+ right_y = left_y + @bar_width * @bar_spacing
48
49
  length[point_index] += difference
49
50
  height[point_index] += (data_point * @graph_width - 2)
50
51
 
51
52
  @d = @d.rectangle(left_x, left_y, right_x, right_y)
52
53
 
53
54
  # Calculate center based on bar_width and current row
54
- label_center = @graph_top + (@bar_width * point_index) + (@bar_width * spacing_factor / 2.0)
55
+ label_center = @graph_top + (@bar_width * point_index) + (@bar_width * @bar_spacing / 2.0)
55
56
  draw_label(label_center, point_index)
56
57
  end
57
58
 
@@ -14,8 +14,9 @@ class Gruff::StackedBar < Gruff::Base
14
14
  # Setup spacing.
15
15
  #
16
16
  # Columns sit stacked.
17
- spacing_factor = 0.9
17
+ @bar_spacing ||= 0.9
18
18
  @bar_width = @graph_width / @column_count.to_f
19
+ padding = (@bar_width * (1 - @bar_spacing)) / 2
19
20
 
20
21
  @d = @d.stroke_opacity 0.0
21
22
 
@@ -26,16 +27,16 @@ class Gruff::StackedBar < Gruff::Base
26
27
  @d = @d.fill data_row[DATA_COLOR_INDEX]
27
28
 
28
29
  # Calculate center based on bar_width and current row
29
- label_center = @graph_left + (@bar_width * point_index) + (@bar_width * spacing_factor / 2.0)
30
+ label_center = @graph_left + (@bar_width * point_index) + (@bar_width * @bar_spacing / 2.0)
30
31
  draw_label(label_center, point_index)
31
32
 
32
33
  next if (data_point == 0)
33
34
  # Use incremented x and scaled y
34
- left_x = @graph_left + (@bar_width * point_index)
35
+ left_x = @graph_left + (@bar_width * point_index) + padding
35
36
  left_y = @graph_top + (@graph_height -
36
37
  data_point * @graph_height -
37
38
  height[point_index]) + 1
38
- right_x = left_x + @bar_width * spacing_factor
39
+ right_x = left_x + @bar_width * @bar_spacing
39
40
  right_y = @graph_top + @graph_height - height[point_index] - 1
40
41
 
41
42
  # update the total height of the current stacked bar
data/test/test_bar.rb CHANGED
@@ -20,6 +20,7 @@ class TestGruffBar < GruffTestCase
20
20
  def test_bar_graph
21
21
  g = setup_basic_graph
22
22
  g.title = "Bar Graph Test"
23
+ g.title_margin = 100
23
24
  g.write("test/output/bar_keynote.png")
24
25
 
25
26
  g = setup_basic_graph
@@ -32,10 +33,20 @@ class TestGruffBar < GruffTestCase
32
33
  g.theme_odeo
33
34
  g.write("test/output/bar_odeo.png")
34
35
  end
36
+
37
+ def test_thousand_separators
38
+ g = Gruff::Bar.new(600)
39
+ g.title = "Formatted numbers"
40
+ g.bar_spacing = 0.2
41
+ g.marker_count = 8
42
+ g.data("data", [4025, 1024, 50257, 703672, 1580456])
43
+ g.write("test/output/bar_formatted_numbers.png")
44
+ end
35
45
 
36
46
  def test_bar_graph_set_colors
37
47
  g = Gruff::Bar.new
38
48
  g.title = "Bar Graph With Manual Colors"
49
+ g.legend_margin = 50
39
50
  g.labels = {
40
51
  0 => '5/6',
41
52
  1 => '5/15',
@@ -248,6 +259,18 @@ class TestGruffBar < GruffTestCase
248
259
  g.minimum_value = 0
249
260
  g.write("test/output/bar_themed.png")
250
261
  end
262
+
263
+ def test_legend_should_not_overlap
264
+ g = Gruff::Bar.new(400)
265
+ g.theme_37signals()
266
+ g.title = 'My Graph'
267
+ g.data("Apples Oranges Watermelon Apples Oranges", [1, 2, 3, 4, 4, 3])
268
+ g.data('Oranges', [4, 8, 7, 9, 8, 9])
269
+ g.data('Watermelon', [2, 3, 1, 5, 6, 8])
270
+ g.data('Peaches', [9, 9, 10, 8, 7, 9])
271
+ g.labels = {0 => '2003', 2 => '2004', 4 => '2005'}
272
+ g.write("test/output/bar_long_legend_text.png")
273
+ end
251
274
 
252
275
  def test_july_enhancements
253
276
  g = Gruff::Bar.new(600)