pfsc_gruff 0.3.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. data/History.txt +117 -0
  2. data/MIT-LICENSE +21 -0
  3. data/Manifest.txt +81 -0
  4. data/README.txt +40 -0
  5. data/Rakefile +55 -0
  6. data/assets/bubble.png +0 -0
  7. data/assets/city_scene/background/0000.png +0 -0
  8. data/assets/city_scene/background/0600.png +0 -0
  9. data/assets/city_scene/background/2000.png +0 -0
  10. data/assets/city_scene/clouds/cloudy.png +0 -0
  11. data/assets/city_scene/clouds/partly_cloudy.png +0 -0
  12. data/assets/city_scene/clouds/stormy.png +0 -0
  13. data/assets/city_scene/grass/default.png +0 -0
  14. data/assets/city_scene/haze/true.png +0 -0
  15. data/assets/city_scene/number_sample/1.png +0 -0
  16. data/assets/city_scene/number_sample/2.png +0 -0
  17. data/assets/city_scene/number_sample/default.png +0 -0
  18. data/assets/city_scene/sky/0000.png +0 -0
  19. data/assets/city_scene/sky/0200.png +0 -0
  20. data/assets/city_scene/sky/0400.png +0 -0
  21. data/assets/city_scene/sky/0600.png +0 -0
  22. data/assets/city_scene/sky/0800.png +0 -0
  23. data/assets/city_scene/sky/1000.png +0 -0
  24. data/assets/city_scene/sky/1200.png +0 -0
  25. data/assets/city_scene/sky/1400.png +0 -0
  26. data/assets/city_scene/sky/1500.png +0 -0
  27. data/assets/city_scene/sky/1700.png +0 -0
  28. data/assets/city_scene/sky/2000.png +0 -0
  29. data/assets/pc306715.jpg +0 -0
  30. data/assets/plastik/blue.png +0 -0
  31. data/assets/plastik/green.png +0 -0
  32. data/assets/plastik/red.png +0 -0
  33. data/init.rb +2 -0
  34. data/lib/gruff.rb +28 -0
  35. data/lib/gruff/accumulator_bar.rb +27 -0
  36. data/lib/gruff/area.rb +58 -0
  37. data/lib/gruff/bar.rb +87 -0
  38. data/lib/gruff/bar_conversion.rb +46 -0
  39. data/lib/gruff/base.rb +1123 -0
  40. data/lib/gruff/bullet.rb +109 -0
  41. data/lib/gruff/deprecated.rb +39 -0
  42. data/lib/gruff/dot.rb +113 -0
  43. data/lib/gruff/line.rb +135 -0
  44. data/lib/gruff/mini/bar.rb +37 -0
  45. data/lib/gruff/mini/legend.rb +109 -0
  46. data/lib/gruff/mini/pie.rb +36 -0
  47. data/lib/gruff/mini/side_bar.rb +35 -0
  48. data/lib/gruff/net.rb +140 -0
  49. data/lib/gruff/photo_bar.rb +100 -0
  50. data/lib/gruff/pie.rb +126 -0
  51. data/lib/gruff/scene.rb +209 -0
  52. data/lib/gruff/side_bar.rb +118 -0
  53. data/lib/gruff/side_stacked_bar.rb +77 -0
  54. data/lib/gruff/spider.rb +130 -0
  55. data/lib/gruff/stacked_area.rb +67 -0
  56. data/lib/gruff/stacked_bar.rb +57 -0
  57. data/lib/gruff/stacked_mixin.rb +23 -0
  58. data/rails_generators/gruff/gruff_generator.rb +63 -0
  59. data/rails_generators/gruff/templates/controller.rb +32 -0
  60. data/rails_generators/gruff/templates/functional_test.rb +24 -0
  61. data/test/gruff_test_case.rb +123 -0
  62. data/test/test_accumulator_bar.rb +50 -0
  63. data/test/test_area.rb +134 -0
  64. data/test/test_bar.rb +321 -0
  65. data/test/test_base.rb +8 -0
  66. data/test/test_bullet.rb +26 -0
  67. data/test/test_dot.rb +273 -0
  68. data/test/test_legend.rb +68 -0
  69. data/test/test_line.rb +556 -0
  70. data/test/test_mini_bar.rb +33 -0
  71. data/test/test_mini_pie.rb +26 -0
  72. data/test/test_mini_side_bar.rb +37 -0
  73. data/test/test_net.rb +230 -0
  74. data/test/test_photo.rb +41 -0
  75. data/test/test_pie.rb +154 -0
  76. data/test/test_scene.rb +100 -0
  77. data/test/test_side_bar.rb +29 -0
  78. data/test/test_sidestacked_bar.rb +89 -0
  79. data/test/test_spider.rb +216 -0
  80. data/test/test_stacked_area.rb +52 -0
  81. data/test/test_stacked_bar.rb +52 -0
  82. metadata +186 -0
@@ -0,0 +1,109 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+
3
+ class Gruff::Bullet < Gruff::Base
4
+
5
+ def initialize(target_width="400x40")
6
+ if not Numeric === target_width
7
+ geometric_width, geometric_height = target_width.split('x')
8
+ @columns = geometric_width.to_f
9
+ @rows = geometric_height.to_f
10
+ else
11
+ @columns = target_width.to_f
12
+ @rows = target_width.to_f / 5.0
13
+ end
14
+
15
+ initialize_ivars
16
+
17
+ reset_themes
18
+ theme_greyscale
19
+ @title_font_size = 20
20
+ end
21
+
22
+ def data(value, maximum_value, options={})
23
+ @value = value.to_f
24
+ @maximum_value = maximum_value.to_f
25
+ @options = options
26
+ @options.map { |k, v| @options[k] = v.to_f if v === Numeric }
27
+ end
28
+
29
+ # def setup_drawing
30
+ # # Maybe should be done in one of the following functions for more granularity.
31
+ # unless @has_data
32
+ # draw_no_data()
33
+ # return
34
+ # end
35
+ #
36
+ # normalize()
37
+ # setup_graph_measurements()
38
+ # sort_norm_data() if @sort # Sort norm_data with avg largest values set first (for display)
39
+ #
40
+ # draw_legend()
41
+ # draw_line_markers()
42
+ # draw_axis_labels()
43
+ # draw_title
44
+ # end
45
+
46
+ def draw
47
+ # TODO Left label
48
+ # TODO Bottom labels and markers
49
+ # @graph_bottom
50
+ # Calculations are off 800x???
51
+
52
+ @colors.reverse!
53
+
54
+ draw_title
55
+
56
+ @margin = 30.0
57
+ @thickness = @raw_rows / 6.0
58
+ @right_margin = @margin
59
+ @graph_left = @title_width * 1.3 rescue @margin # HACK Need to calculate real width
60
+ @graph_width = @raw_columns - @graph_left - @right_margin
61
+ @graph_height = @thickness * 3.0
62
+
63
+ # Background
64
+ @d = @d.fill @colors[0]
65
+ @d = @d.rectangle(@graph_left, 0, @graph_left + @graph_width, @graph_height)
66
+
67
+ [:high, :low].each_with_index do |indicator, index|
68
+ next unless @options.has_key?(indicator)
69
+ @d = @d.fill @colors[index + 1]
70
+ indicator_width_x = @graph_left + @graph_width * (@options[indicator] / @maximum_value)
71
+ @d = @d.rectangle(@graph_left, 0, indicator_width_x, @graph_height)
72
+ end
73
+
74
+ if @options.has_key?(:target)
75
+ @d = @d.fill @font_color
76
+ target_x = @graph_left + @graph_width * (@options[:target] / @maximum_value)
77
+ half_thickness = @thickness / 2.0
78
+ @d = @d.rectangle(target_x, half_thickness, target_x + half_thickness, @thickness * 2 + half_thickness)
79
+ end
80
+
81
+ # Value
82
+ @d = @d.fill @font_color
83
+ @d = @d.rectangle(@graph_left, @thickness, @graph_left + @graph_width * (@value / @maximum_value), @thickness * 2)
84
+
85
+ @d.draw(@base_image)
86
+ end
87
+
88
+ def draw_title
89
+ return unless @title
90
+
91
+ @font_height = calculate_caps_height(scale_fontsize(@title_font_size))
92
+ @title_width = calculate_width(@title_font_size, @title)
93
+
94
+ @d.fill = @font_color
95
+ @d.font = @font if @font
96
+ @d.stroke('transparent')
97
+ @d.font_weight = NormalWeight
98
+ @d.pointsize = scale_fontsize(@title_font_size)
99
+ @d.gravity = NorthWestGravity
100
+ @d = @d.annotate_scaled(*[
101
+ @base_image,
102
+ 1.0, 1.0,
103
+ @font_height/2, @font_height/2,
104
+ @title,
105
+ @scale
106
+ ])
107
+ end
108
+
109
+ end
@@ -0,0 +1,39 @@
1
+
2
+ ##
3
+ # A mixin for methods that need to be deleted or have been
4
+ # replaced by cleaner code.
5
+
6
+ module Gruff
7
+ module Deprecated
8
+
9
+ def scale_measurements
10
+ setup_graph_measurements
11
+ end
12
+
13
+ def total_height
14
+ @rows + 10
15
+ end
16
+
17
+ def graph_top
18
+ @graph_top * @scale
19
+ end
20
+
21
+ def graph_height
22
+ @graph_height * @scale
23
+ end
24
+
25
+ def graph_left
26
+ @graph_left * @scale
27
+ end
28
+
29
+ def graph_width
30
+ @graph_width * @scale
31
+ end
32
+
33
+ # TODO Should be calculate_graph_height
34
+ # def setup_graph_height
35
+ # @graph_height = @graph_bottom - @graph_top
36
+ # end
37
+
38
+ end
39
+ end
@@ -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
+
@@ -0,0 +1,135 @@
1
+
2
+ require File.dirname(__FILE__) + '/base'
3
+
4
+ ##
5
+ # Here's how to make a Line graph:
6
+ #
7
+ # g = Gruff::Line.new
8
+ # g.title = "A Line Graph"
9
+ # g.data 'Fries', [20, 23, 19, 8]
10
+ # g.data 'Hamburgers', [50, 19, 99, 29]
11
+ # g.write("test/output/line.png")
12
+ #
13
+ # There are also other options described below, such as #baseline_value, #baseline_color, #hide_dots, and #hide_lines.
14
+
15
+ class Gruff::Line < Gruff::Base
16
+
17
+ # Draw a dashed line at the given value
18
+ attr_accessor :baseline_value
19
+
20
+ # Color of the baseline
21
+ attr_accessor :baseline_color
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
+
27
+ # Hide parts of the graph to fit more datapoints, or for a different appearance.
28
+ attr_accessor :hide_dots, :hide_lines
29
+
30
+ # Call with target pixel width of graph (800, 400, 300), and/or 'false' to omit lines (points only).
31
+ #
32
+ # g = Gruff::Line.new(400) # 400px wide with lines
33
+ #
34
+ # g = Gruff::Line.new(400, false) # 400px wide, no lines (for backwards compatibility)
35
+ #
36
+ # g = Gruff::Line.new(false) # Defaults to 800px wide, no lines (for backwards compatibility)
37
+ #
38
+ # The preferred way is to call hide_dots or hide_lines instead.
39
+ def initialize(*args)
40
+ raise ArgumentError, "Wrong number of arguments" if args.length > 2
41
+ if args.empty? or ((not Numeric === args.first) && (not String === args.first)) then
42
+ super()
43
+ else
44
+ super args.shift
45
+ end
46
+
47
+ @hide_dots = @hide_lines = false
48
+ @baseline_color = 'red'
49
+ @baseline_value = nil
50
+ end
51
+
52
+ def draw
53
+ super
54
+
55
+ return unless @has_data
56
+
57
+ # Check to see if more than one datapoint was given. NaN can result otherwise.
58
+ @x_increment = (@column_count > 1) ? (@graph_width / (@column_count - 1).to_f) : @graph_width
59
+
60
+ if (defined?(@norm_baseline)) then
61
+ level = @graph_top + (@graph_height - @norm_baseline * @graph_height)
62
+ @d = @d.push
63
+ @d.stroke_color @baseline_color
64
+ @d.fill_opacity 0.0
65
+ @d.stroke_dasharray(10, 20)
66
+ @d.stroke_width 5
67
+ @d.line(@graph_left, level, @graph_left + @graph_width, level)
68
+ @d = @d.pop
69
+ end
70
+
71
+ @norm_data.each do |data_row|
72
+ prev_x = prev_y = nil
73
+
74
+ @one_point = contains_one_point_only?(data_row)
75
+
76
+ data_row[DATA_VALUES_INDEX].each_with_index do |data_point, index|
77
+ new_x = @graph_left + (@x_increment * index)
78
+ next if data_point.nil?
79
+
80
+ draw_label(new_x, index)
81
+
82
+ new_y = @graph_top + (@graph_height - data_point * @graph_height)
83
+
84
+ # Reset each time to avoid thin-line errors
85
+ @d = @d.stroke data_row[DATA_COLOR_INDEX]
86
+ @d = @d.fill data_row[DATA_COLOR_INDEX]
87
+ @d = @d.stroke_opacity 1.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
+
91
+
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
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)
100
+ end
101
+ @d = @d.circle(new_x, new_y, new_x - circle_radius, new_y) unless @hide_dots
102
+
103
+ prev_x = new_x
104
+ prev_y = new_y
105
+ end
106
+
107
+ end
108
+
109
+ @d.draw(@base_image)
110
+ end
111
+
112
+ def normalize
113
+ @maximum_value = [@maximum_value.to_f, @baseline_value.to_f].max
114
+ super
115
+ @norm_baseline = (@baseline_value.to_f / @maximum_value.to_f) if @baseline_value
116
+ end
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
+
135
+ end
@@ -0,0 +1,37 @@
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 initialize_ivars
13
+ super
14
+
15
+ @hide_legend = true
16
+ @hide_title = true
17
+ @hide_line_numbers = true
18
+
19
+ @marker_font_size = 50.0
20
+ @minimum_value = 0.0
21
+ @maximum_value = 0.0
22
+ @legend_font_size = 60.0
23
+ end
24
+
25
+ def draw
26
+ expand_canvas_for_vertical_legend
27
+
28
+ super
29
+
30
+ draw_vertical_legend
31
+ @d.draw(@base_image)
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,109 @@
1
+ module Gruff
2
+ module Mini
3
+ module Legend
4
+
5
+ attr_accessor :hide_mini_legend, :legend_position
6
+
7
+ ##
8
+ # The canvas needs to be bigger so we can put the legend beneath it.
9
+
10
+ def expand_canvas_for_vertical_legend
11
+ return if @hide_mini_legend
12
+
13
+ @legend_labels = @data.collect {|item| item[Gruff::Base::DATA_LABEL_INDEX] }
14
+
15
+ legend_height = scale_fontsize(
16
+ @data.length * calculate_line_height +
17
+ @top_margin + @bottom_margin)
18
+
19
+ @original_rows = @raw_rows
20
+ @original_columns = @raw_columns
21
+
22
+ case @legend_position
23
+ when :right then
24
+ @rows = [@rows, legend_height].max
25
+ @columns += calculate_legend_width + @left_margin
26
+ else
27
+ @rows += @data.length * calculate_caps_height(scale_fontsize(@legend_font_size)) * 1.7
28
+ end
29
+ render_background
30
+ end
31
+
32
+ def calculate_line_height
33
+ calculate_caps_height(@legend_font_size) * 1.7
34
+ end
35
+
36
+ def calculate_legend_width
37
+ width = @legend_labels.map { |label| calculate_width(@legend_font_size, label) }.max
38
+ scale_fontsize(width + 40*1.7)
39
+ end
40
+
41
+ ##
42
+ # Draw the legend beneath the existing graph.
43
+
44
+ def draw_vertical_legend
45
+ return if @hide_mini_legend
46
+
47
+ legend_square_width = 40.0 # small square with color of this item
48
+ legend_square_margin = 10.0
49
+ @legend_left_margin = 100.0
50
+ legend_top_margin = 40.0
51
+
52
+ # May fix legend drawing problem at small sizes
53
+ @d.font = @font if @font
54
+ @d.pointsize = @legend_font_size
55
+
56
+ case @legend_position
57
+ when :right then
58
+ current_x_offset = @original_columns + @left_margin
59
+ current_y_offset = @top_margin + legend_top_margin
60
+ else
61
+ current_x_offset = @legend_left_margin
62
+ current_y_offset = @original_rows + legend_top_margin
63
+ end
64
+
65
+ debug { @d.line 0.0, current_y_offset, @raw_columns, current_y_offset }
66
+
67
+ @legend_labels.each_with_index do |legend_label, index|
68
+
69
+ # Draw label
70
+ @d.fill = @font_color
71
+ @d.font = @font if @font
72
+ @d.pointsize = scale_fontsize(@legend_font_size)
73
+ @d.stroke = 'transparent'
74
+ @d.font_weight = Magick::NormalWeight
75
+ @d.gravity = Magick::WestGravity
76
+ @d = @d.annotate_scaled( @base_image,
77
+ @raw_columns, 1.0,
78
+ current_x_offset + (legend_square_width * 1.7), current_y_offset,
79
+ truncate_legend_label(legend_label), @scale)
80
+
81
+ # Now draw box with color of this dataset
82
+ @d = @d.stroke 'transparent'
83
+ @d = @d.fill @data[index][Gruff::Base::DATA_COLOR_INDEX]
84
+ @d = @d.rectangle(current_x_offset,
85
+ current_y_offset - legend_square_width / 2.0,
86
+ current_x_offset + legend_square_width,
87
+ current_y_offset + legend_square_width / 2.0)
88
+
89
+ current_y_offset += calculate_line_height
90
+ end
91
+ @color_index = 0
92
+ end
93
+
94
+ ##
95
+ # Shorten long labels so they will fit on the canvas.
96
+ #
97
+ # Department of Hu...
98
+
99
+ def truncate_legend_label(label)
100
+ truncated_label = label.to_s
101
+ while calculate_width(scale_fontsize(@legend_font_size), truncated_label) > (@columns - @legend_left_margin - @right_margin) && (truncated_label.length > 1)
102
+ truncated_label = truncated_label[0..truncated_label.length-2]
103
+ end
104
+ truncated_label + (truncated_label.length < label.to_s.length ? "..." : '')
105
+ end
106
+
107
+ end
108
+ end
109
+ end