gruff 0.3.4 → 0.3.6

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/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)