gruff 0.7.0-java → 0.12.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +5 -5
  2. data/{History.txt → CHANGELOG.md} +81 -25
  3. data/Gemfile +3 -1
  4. data/README.md +51 -23
  5. data/assets/plastik/blue.png +0 -0
  6. data/assets/plastik/green.png +0 -0
  7. data/assets/plastik/red.png +0 -0
  8. data/gruff.gemspec +23 -13
  9. data/init.rb +2 -0
  10. data/lib/gruff.rb +33 -4
  11. data/lib/gruff/accumulator_bar.rb +17 -9
  12. data/lib/gruff/area.rb +31 -21
  13. data/lib/gruff/bar.rb +90 -46
  14. data/lib/gruff/base.rb +476 -710
  15. data/lib/gruff/bezier.rb +29 -18
  16. data/lib/gruff/bullet.rb +58 -71
  17. data/lib/gruff/dot.rb +35 -83
  18. data/lib/gruff/helper/bar_conversion.rb +47 -0
  19. data/lib/gruff/helper/bar_value_label_mixin.rb +33 -0
  20. data/lib/gruff/helper/stacked_mixin.rb +23 -0
  21. data/lib/gruff/histogram.rb +59 -0
  22. data/lib/gruff/line.rb +121 -199
  23. data/lib/gruff/mini/bar.rb +17 -10
  24. data/lib/gruff/mini/legend.rb +26 -38
  25. data/lib/gruff/mini/pie.rb +18 -13
  26. data/lib/gruff/mini/side_bar.rb +25 -12
  27. data/lib/gruff/net.rb +69 -83
  28. data/lib/gruff/patch/rmagick.rb +31 -0
  29. data/lib/gruff/patch/string.rb +13 -0
  30. data/lib/gruff/photo_bar.rb +36 -43
  31. data/lib/gruff/pie.rb +42 -103
  32. data/lib/gruff/renderer/bezier.rb +22 -0
  33. data/lib/gruff/renderer/circle.rb +22 -0
  34. data/lib/gruff/renderer/dash_line.rb +23 -0
  35. data/lib/gruff/renderer/dot.rb +40 -0
  36. data/lib/gruff/renderer/ellipse.rb +22 -0
  37. data/lib/gruff/renderer/line.rb +43 -0
  38. data/lib/gruff/renderer/polygon.rb +24 -0
  39. data/lib/gruff/renderer/polyline.rb +22 -0
  40. data/lib/gruff/renderer/rectangle.rb +20 -0
  41. data/lib/gruff/renderer/renderer.rb +120 -0
  42. data/lib/gruff/renderer/text.rb +57 -0
  43. data/lib/gruff/scatter.rb +128 -201
  44. data/lib/gruff/scene.rb +30 -41
  45. data/lib/gruff/side_bar.rb +100 -68
  46. data/lib/gruff/side_stacked_bar.rb +92 -63
  47. data/lib/gruff/spider.rb +47 -53
  48. data/lib/gruff/stacked_area.rb +37 -34
  49. data/lib/gruff/stacked_bar.rb +99 -54
  50. data/lib/gruff/store/basic_data.rb +36 -0
  51. data/lib/gruff/store/custom_data.rb +36 -0
  52. data/lib/gruff/store/store.rb +81 -0
  53. data/lib/gruff/store/xy_data.rb +58 -0
  54. data/lib/gruff/themes.rb +32 -33
  55. data/lib/gruff/version.rb +3 -1
  56. metadata +74 -102
  57. data/.gitignore +0 -7
  58. data/.travis.yml +0 -19
  59. data/Manifest.txt +0 -81
  60. data/RELEASE.md +0 -30
  61. data/Rakefile +0 -218
  62. data/assets/bubble.png +0 -0
  63. data/assets/city_scene/background/0000.png +0 -0
  64. data/assets/city_scene/background/0600.png +0 -0
  65. data/assets/city_scene/background/2000.png +0 -0
  66. data/assets/city_scene/clouds/cloudy.png +0 -0
  67. data/assets/city_scene/clouds/partly_cloudy.png +0 -0
  68. data/assets/city_scene/clouds/stormy.png +0 -0
  69. data/assets/city_scene/grass/default.png +0 -0
  70. data/assets/city_scene/haze/true.png +0 -0
  71. data/assets/city_scene/number_sample/1.png +0 -0
  72. data/assets/city_scene/number_sample/2.png +0 -0
  73. data/assets/city_scene/number_sample/default.png +0 -0
  74. data/assets/city_scene/sky/0000.png +0 -0
  75. data/assets/city_scene/sky/0200.png +0 -0
  76. data/assets/city_scene/sky/0400.png +0 -0
  77. data/assets/city_scene/sky/0600.png +0 -0
  78. data/assets/city_scene/sky/0800.png +0 -0
  79. data/assets/city_scene/sky/1000.png +0 -0
  80. data/assets/city_scene/sky/1200.png +0 -0
  81. data/assets/city_scene/sky/1400.png +0 -0
  82. data/assets/city_scene/sky/1500.png +0 -0
  83. data/assets/city_scene/sky/1700.png +0 -0
  84. data/assets/city_scene/sky/2000.png +0 -0
  85. data/assets/pc306715.jpg +0 -0
  86. data/lib/gruff/bar_conversion.rb +0 -46
  87. data/lib/gruff/deprecated.rb +0 -39
  88. data/lib/gruff/stacked_mixin.rb +0 -23
  89. data/test/gruff_test_case.rb +0 -152
  90. data/test/image_compare.rb +0 -58
  91. data/test/test_accumulator_bar.rb +0 -51
  92. data/test/test_area.rb +0 -134
  93. data/test/test_bar.rb +0 -505
  94. data/test/test_base.rb +0 -33
  95. data/test/test_bezier.rb +0 -33
  96. data/test/test_bullet.rb +0 -26
  97. data/test/test_dot.rb +0 -263
  98. data/test/test_labels_for_null_data.rb +0 -27
  99. data/test/test_legend.rb +0 -68
  100. data/test/test_line.rb +0 -674
  101. data/test/test_mini_bar.rb +0 -33
  102. data/test/test_mini_pie.rb +0 -25
  103. data/test/test_mini_side_bar.rb +0 -36
  104. data/test/test_net.rb +0 -231
  105. data/test/test_photo.rb +0 -41
  106. data/test/test_pie.rb +0 -194
  107. data/test/test_scatter.rb +0 -270
  108. data/test/test_scene.rb +0 -100
  109. data/test/test_side_bar.rb +0 -56
  110. data/test/test_sidestacked_bar.rb +0 -105
  111. data/test/test_spider.rb +0 -226
  112. data/test/test_stacked_area.rb +0 -52
  113. data/test/test_stacked_bar.rb +0 -68
@@ -1,10 +1,9 @@
1
+ # frozen_string_literal: true
1
2
 
2
- require "observer"
3
- require File.dirname(__FILE__) + '/base'
3
+ require 'observer'
4
4
 
5
- ##
6
5
  # A scene is a non-linear graph that assembles layers together to tell a story.
7
- # Layers are folders with appropriately named files (see below). You can group
6
+ # Layers are folders with appropriately named files (see below). You can group
8
7
  # layers and control them together or just set their values individually.
9
8
  #
10
9
  # Examples:
@@ -12,8 +11,6 @@ require File.dirname(__FILE__) + '/base'
12
11
  # * A city scene that changes with the time of day and the weather conditions.
13
12
  # * A traffic map that shows red lines on streets that are crowded and green on free-flowing ones.
14
13
  #
15
- # Usage:
16
- #
17
14
  # g = Gruff::Scene.new("500x100", "path/to/city_scene_directory")
18
15
  #
19
16
  # # Define order of layers, back to front
@@ -31,34 +28,31 @@ require File.dirname(__FILE__) + '/base'
31
28
  # # Write the final graph to disk
32
29
  # g.write "hazy_daytime_city_scene.png"
33
30
  #
34
- #
35
31
  # There are several rules that will magically select a layer when possible.
36
32
  #
37
33
  # * Numbered files will be selected according to the closest value that is less than the input value.
38
- # * 'true.png' and 'false.png' will be used as booleans.
34
+ # * +'true.png'+ and +'false.png'+ will be used as booleans.
39
35
  # * Other named files will be used if the input matches the filename (without the filetype extension).
40
- # * If there is a file named 'default.png', it will be used unless other input values are set for the corresponding layer.
41
-
36
+ # * If there is a file named +'default.png'+, it will be used unless other input values are set for the corresponding layer.
42
37
  class Gruff::Scene < Gruff::Base
43
-
44
- # An array listing the foldernames that will be rendered, from back to front.
45
- #
46
- # g.layers = %w(sky clouds buildings street people)
38
+ # An array listing the folder names that will be rendered, from back to front.
47
39
  #
40
+ # @example
41
+ # g.layers = %w(sky clouds buildings street people)
48
42
  attr_reader :layers
49
43
 
50
44
  def initialize(target_width, base_dir)
51
45
  @base_dir = base_dir
52
46
  @groups = {}
53
- @layers = []
47
+ @layers = []
54
48
  super target_width
55
49
  end
56
50
 
57
51
  def draw
58
52
  # Join all the custom paths and filter out the empty ones
59
- image_paths = @layers.map { |layer| layer.path }.select { |path| !path.empty? }
53
+ image_paths = @layers.map(&:path).reject(&:empty?)
60
54
  images = Magick::ImageList.new(*image_paths)
61
- @base_image = images.flatten_images
55
+ Gruff::Renderer.background_image = images.flatten_images
62
56
  end
63
57
 
64
58
  def layers=(ordered_list)
@@ -78,10 +72,10 @@ class Gruff::Scene < Gruff::Base
78
72
  def method_missing(method_name, *args)
79
73
  case method_name.to_s
80
74
  when /^(\w+)_group=$/
81
- add_group $1, *args
75
+ add_group Regexp.last_match(1), *args
82
76
  return
83
77
  when /^(\w+)=$/
84
- set_input $1, args.first
78
+ set_input Regexp.last_match(1), args.first
85
79
  return
86
80
  end
87
81
  super
@@ -94,20 +88,16 @@ private
94
88
  end
95
89
 
96
90
  def set_input(input_name, input_value)
97
- if not @groups[input_name].nil?
91
+ if !@groups[input_name].nil?
98
92
  @groups[input_name].send_updates(input_value)
99
- else
100
- if chosen_layer = @layers.detect { |layer| layer.name == input_name }
101
- chosen_layer.update input_value
102
- end
93
+ elsif chosen_layer = @layers.find { |layer| layer.name == input_name }
94
+ chosen_layer.update input_value
103
95
  end
104
96
  end
105
-
106
97
  end
107
98
 
108
-
99
+ # @private
109
100
  class Gruff::Group
110
-
111
101
  include Observable
112
102
  attr_reader :name
113
103
 
@@ -117,31 +107,29 @@ class Gruff::Group
117
107
  layer.observe self
118
108
  end
119
109
  end
120
-
110
+
121
111
  def send_updates(value)
122
112
  changed
123
113
  notify_observers value
124
114
  end
125
-
126
115
  end
127
116
 
128
-
117
+ # @private
129
118
  class Gruff::Layer
130
-
131
119
  attr_reader :name
132
-
120
+
133
121
  def initialize(base_dir, folder_name)
134
122
  @base_dir = base_dir.to_s
135
123
  @name = folder_name.to_s
136
124
  @filenames = Dir.open(File.join(base_dir, folder_name)).entries.select { |file| file =~ /^[^.]+\.png$/ }.sort
137
125
  @selected_filename = select_default
138
126
  end
139
-
127
+
140
128
  # Register this layer so it receives updates from the group
141
129
  def observe(obj)
142
130
  obj.add_observer self
143
131
  end
144
-
132
+
145
133
  # Choose the appropriate filename for this layer, based on the input
146
134
  def update(value)
147
135
  @selected_filename = case value.to_s
@@ -152,7 +140,7 @@ class Gruff::Layer
152
140
  when /^-?(\d+\.)?\d+$/
153
141
  select_numeric value
154
142
  when /(\d\d):(\d\d):\d\d/
155
- select_time "#{$1}#{$2}"
143
+ select_time "#{Regexp.last_match(1)}#{Regexp.last_match(2)}"
156
144
  else
157
145
  select_default
158
146
  end
@@ -165,6 +153,7 @@ class Gruff::Layer
165
153
  unless @selected_filename.nil? || @selected_filename.empty?
166
154
  return File.join(@base_dir, @name, @selected_filename)
167
155
  end
156
+
168
157
  ''
169
158
  end
170
159
 
@@ -179,7 +168,7 @@ private
179
168
  def select_numeric(value)
180
169
  file_exists_or_blank value.to_s.gsub('-', '_')
181
170
  end
182
-
171
+
183
172
  def select_time(value)
184
173
  times = @filenames.map { |filename| filename.gsub('.png', '') }
185
174
  times.each_with_index do |time, index|
@@ -187,16 +176,17 @@ private
187
176
  return "#{times[index - 1]}.png"
188
177
  end
189
178
  end
190
- return "#{times.last}.png"
179
+
180
+ "#{times.last}.png"
191
181
  end
192
-
182
+
193
183
  # Match "partly cloudy" to "partly_cloudy.png"
194
184
  def select_string(value)
195
185
  file_exists_or_blank value.to_s.gsub(' ', '_')
196
186
  end
197
-
187
+
198
188
  def select_default
199
- @filenames.include?("default.png") ? "default.png" : ''
189
+ @filenames.include?('default.png') ? 'default.png' : ''
200
190
  end
201
191
 
202
192
  # Returns the string "#{filename}.png", if it exists.
@@ -205,5 +195,4 @@ private
205
195
  def file_exists_or_blank(filename)
206
196
  @filenames.include?("#{filename}.png") ? "#{filename}.png" : select_default
207
197
  end
208
-
209
198
  end
@@ -1,46 +1,97 @@
1
- require File.dirname(__FILE__) + '/base'
1
+ # frozen_string_literal: true
2
2
 
3
- ##
4
3
  # Graph with individual horizontal bars instead of vertical bars.
5
-
4
+ #
5
+ # Here's how to set up a Gruff::SideBar.
6
+ #
7
+ # g = Gruff::SideBar.new
8
+ # g.title = 'SideBar Graph'
9
+ # g.labels = {
10
+ # 0 => '5/6',
11
+ # 1 => '5/15',
12
+ # 2 => '5/24',
13
+ # 3 => '5/30',
14
+ # }
15
+ # g.group_spacing = 20
16
+ # g.data :Art, [0, 5, 8, 15]
17
+ # g.data :Philosophy, [10, 3, 2, 8]
18
+ # g.data :Science, [2, 15, 8, 11]
19
+ # g.write('sidebar.png')
20
+ #
6
21
  class Gruff::SideBar < Gruff::Base
22
+ using String::GruffCommify
23
+
24
+ # Spacing factor applied between bars.
25
+ attr_writer :bar_spacing
26
+
27
+ # Spacing factor applied between a group of bars belonging to the same label.
28
+ attr_writer :group_spacing
29
+
30
+ # Set the number output format for labels using sprintf.
31
+ # Default is +"%.2f"+.
32
+ attr_writer :label_formatting
7
33
 
8
- # Spacing factor applied between bars
9
- attr_accessor :bar_spacing
34
+ # Output the values for the bars on a bar graph.
35
+ # Default is +false+.
36
+ attr_writer :show_labels_for_bar_values
37
+
38
+ # Prevent drawing of column labels left of a side bar graph. Default is +false+.
39
+ attr_writer :hide_labels
40
+
41
+ def initialize_ivars
42
+ super
43
+ @bar_spacing = 0.9
44
+ @group_spacing = 10
45
+ @label_formatting = nil
46
+ @show_labels_for_bar_values = false
47
+ @hide_labels = false
48
+ end
49
+ private :initialize_ivars
10
50
 
11
51
  def draw
12
52
  @has_left_labels = true
13
53
  super
14
54
 
15
- return unless @has_data
55
+ return unless data_given?
56
+
16
57
  draw_bars
17
58
  end
18
59
 
19
- protected
60
+ protected
61
+
62
+ def hide_labels?
63
+ @hide_labels
64
+ end
65
+
66
+ def hide_left_label_area?
67
+ hide_labels?
68
+ end
69
+
70
+ def hide_bottom_label_area?
71
+ @hide_line_markers
72
+ end
73
+
74
+ private
20
75
 
21
76
  def draw_bars
22
77
  # Setup spacing.
23
78
  #
24
- @bar_spacing ||= 0.9
25
-
26
- @bars_width = @graph_height / @column_count.to_f
27
- @bar_width = @bars_width / @norm_data.size
28
- @d = @d.stroke_opacity 0.0
29
- height = Array.new(@column_count, 0)
30
- length = Array.new(@column_count, @graph_left)
31
- padding = (@bar_width * (1 - @bar_spacing)) / 2
79
+ bars_width = (@graph_height - calculate_spacing) / column_count.to_f
80
+ bar_width = bars_width / store.length
81
+ height = Array.new(column_count, 0)
82
+ length = Array.new(column_count, @graph_left)
83
+ padding = (bar_width * (1 - @bar_spacing)) / 2
32
84
 
33
85
  # if we're a side stacked bar then we don't need to draw ourself at all
34
86
  # because sometimes (due to different heights/min/max) you can actually
35
87
  # see both graphs and it looks like crap
36
- return if self.is_a?(Gruff::SideStackedBar)
37
-
38
- @norm_data.each_with_index do |data_row, row_index|
39
- @d = @d.fill data_row[DATA_COLOR_INDEX]
88
+ return if is_a?(Gruff::SideStackedBar)
40
89
 
41
- data_row[DATA_VALUES_INDEX].each_with_index do |data_point, point_index|
90
+ store.norm_data.each_with_index do |data_row, row_index|
91
+ data_row.points.each_with_index do |data_point, point_index|
92
+ group_spacing = @group_spacing * @scale * point_index
42
93
 
43
- # Using the original calcs from the stacked bar chart
94
+ # Using the original calculations from the stacked bar chart
44
95
  # to get the difference between
45
96
  # part of the bart chart we wish to stack.
46
97
  temp1 = @graph_left + (@graph_width - data_point * @graph_width - height[point_index])
@@ -48,91 +99,72 @@ class Gruff::SideBar < Gruff::Base
48
99
  difference = temp2 - temp1
49
100
 
50
101
  left_x = length[point_index] - 1
51
- left_y = @graph_top + (@bars_width * point_index) + (@bar_width * row_index) + padding
102
+ left_y = @graph_top + (bars_width * point_index) + (bar_width * row_index) + padding + group_spacing
52
103
  right_x = left_x + difference
53
- right_y = left_y + @bar_width * @bar_spacing
104
+ right_y = left_y + bar_width * @bar_spacing
54
105
 
55
106
  height[point_index] += (data_point * @graph_width)
56
107
 
57
- @d = @d.rectangle(left_x, left_y, right_x, right_y)
108
+ rect_renderer = Gruff::Renderer::Rectangle.new(color: data_row.color)
109
+ rect_renderer.render(left_x, left_y, right_x, right_y)
58
110
 
59
111
  # Calculate center based on bar_width and current row
60
112
 
61
113
  if @use_data_label
62
- label_center = @graph_top + (@bar_width * (row_index+point_index) + @bar_width / 2)
63
- draw_label(label_center, row_index, @norm_data[row_index][DATA_LABEL_INDEX])
114
+ label_center = left_y + bar_width / 2
115
+ draw_label(label_center, row_index, store.norm_data[row_index].label)
64
116
  else
65
- label_center = @graph_top + (@bars_width * point_index + @bars_width / 2)
117
+ label_center = left_y + bars_width / 2
66
118
  draw_label(label_center, point_index)
67
119
  end
68
120
  if @show_labels_for_bar_values
69
- val = (@label_formatting || '%.2f') % @norm_data[row_index][3][point_index]
70
- draw_value_label(right_x+40, (@graph_top + (((row_index+point_index+1) * @bar_width) - (@bar_width / 2)))-12, val.commify, true)
121
+ val = (@label_formatting || '%.2f') % store.data[row_index].points[point_index]
122
+ draw_value_label(right_x + 40, right_y - bar_width / 2, val.commify, true)
71
123
  end
72
124
  end
73
-
74
125
  end
75
-
76
- @d.draw(@base_image)
77
126
  end
78
127
 
79
128
  # Instead of base class version, draws vertical background lines and label
80
129
  def draw_line_markers
81
-
82
130
  return if @hide_line_markers
83
131
 
84
- @d = @d.stroke_antialias false
85
-
86
132
  # Draw horizontal line markers and annotate with numbers
87
- @d = @d.stroke(@marker_color)
88
- @d = @d.stroke_width 1
89
- number_of_lines = @marker_count || 5
133
+ number_of_lines = marker_count
90
134
  number_of_lines = 1 if number_of_lines == 0
91
135
 
92
- # TODO Round maximum marker value to a round number like 100, 0.1, 0.5, etc.
136
+ # TODO: Round maximum marker value to a round number like 100, 0.1, 0.5, etc.
93
137
  increment = significant(@spread.to_f / number_of_lines)
94
138
  (0..number_of_lines).each do |index|
95
-
96
139
  line_diff = (@graph_right - @graph_left) / number_of_lines
97
140
  x = @graph_right - (line_diff * index) - 1
98
- @d = @d.line(x, @graph_bottom, x, @graph_top)
141
+
142
+ line_renderer = Gruff::Renderer::Line.new(color: @marker_color, shadow_color: @marker_shadow_color)
143
+ line_renderer.render(x, @graph_bottom, x, @graph_top)
144
+
99
145
  diff = index - number_of_lines
100
- marker_label = diff.abs * increment + @minimum_value
146
+ marker_label = diff.abs * increment + minimum_value
101
147
 
102
148
  unless @hide_line_numbers
103
- @d.fill = @font_color
104
- @d.font = @font if @font
105
- @d.stroke = 'transparent'
106
- @d.pointsize = scale_fontsize(@marker_font_size)
107
- @d.gravity = CenterGravity
108
- # TODO Center text over line
109
- @d = @d.annotate_scaled(@base_image,
110
- 0, 0, # Width of box to draw text in
111
- x, @graph_bottom + (LABEL_MARGIN * 2.0), # Coordinates of text
112
- marker_label.to_s, @scale)
113
- end # unless
114
- @d = @d.stroke_antialias true
149
+ text_renderer = Gruff::Renderer::Text.new(marker_label, font: @font, size: @marker_font_size, color: @font_color)
150
+ text_renderer.add_to_render_queue(0, 0, x, @graph_bottom + LABEL_MARGIN, Magick::CenterGravity)
151
+ end
115
152
  end
116
153
  end
117
154
 
118
155
  ##
119
156
  # Draw on the Y axis instead of the X
120
157
 
121
- def draw_label(y_offset, index, label=nil)
122
- if !@labels[index].nil? && @labels_seen[index].nil?
123
- lbl = (@use_data_label) ? label : @labels[index]
124
- @d.fill = @font_color
125
- @d.font = @font if @font
126
- @d.stroke = 'transparent'
127
- @d.font_weight = NormalWeight
128
- @d.pointsize = scale_fontsize(@marker_font_size)
129
- @d.gravity = EastGravity
130
- @d = @d.annotate_scaled(@base_image,
131
- 1, 1,
132
- -@graph_left + LABEL_MARGIN * 2.0, y_offset,
133
- lbl, @scale)
134
- @labels_seen[index] = 1
158
+ def draw_label(y_offset, index, label = nil)
159
+ draw_unique_label(index) do
160
+ lbl = @use_data_label ? label : @labels[index]
161
+
162
+ text_renderer = Gruff::Renderer::Text.new(lbl, font: @font, size: @marker_font_size, color: @font_color)
163
+ text_renderer.add_to_render_queue(@graph_left - LABEL_MARGIN, 1.0, 0.0, y_offset, Magick::EastGravity)
135
164
  end
136
165
  end
137
166
 
167
+ def calculate_spacing
168
+ @scale * @group_spacing * (column_count - 1)
169
+ end
138
170
  end
@@ -1,97 +1,126 @@
1
- require File.dirname(__FILE__) + '/base'
2
- require File.dirname(__FILE__) + '/side_bar'
3
- require File.dirname(__FILE__) + '/stacked_mixin'
1
+ # frozen_string_literal: true
4
2
 
5
- ##
6
- # New gruff graph type added to enable sideways stacking bar charts
3
+ #
4
+ # New gruff graph type added to enable sideways stacking bar charts
7
5
  # (basically looks like a x/y flip of a standard stacking bar chart)
8
6
  #
9
- # alun.eyre@googlemail.com
10
-
7
+ # Here's how to set up a Gruff::SideStackedBar.
8
+ #
9
+ # g = Gruff::SideStackedBar.new
10
+ # g.title = 'SideStackedBar Graph'
11
+ # g.labels = {
12
+ # 0 => '5/6',
13
+ # 1 => '5/15',
14
+ # 2 => '5/24',
15
+ # 3 => '5/30',
16
+ # }
17
+ # g.data :Art, [0, 5, 8, 15]
18
+ # g.data :Philosophy, [10, 3, 2, 8]
19
+ # g.data :Science, [2, 15, 8, 11]
20
+ # g.write('side_stacked_bar.png')
21
+ #
11
22
  class Gruff::SideStackedBar < Gruff::SideBar
12
23
  include StackedMixin
24
+ include BarValueLabelMixin
25
+
26
+ # Spacing factor applied between bars.
27
+ attr_writer :bar_spacing
28
+
29
+ # Number of pixels between bar segments.
30
+ attr_writer :segment_spacing
31
+
32
+ # Set the number output format for labels using sprintf.
33
+ # Default is +"%.2f"+.
34
+ attr_writer :label_formatting
35
+
36
+ # Output the values for the bars on a bar graph.
37
+ # Default is +false+.
38
+ attr_writer :show_labels_for_bar_values
39
+
40
+ # Prevent drawing of column labels left of a side stacked bar graph. Default is +false+.
41
+ attr_writer :hide_labels
42
+
43
+ def initialize_ivars
44
+ super
45
+ @bar_spacing = 0.9
46
+ @segment_spacing = 2.0
47
+ @label_formatting = nil
48
+ @show_labels_for_bar_values = false
49
+ @hide_labels = false
50
+ end
51
+ private :initialize_ivars
13
52
 
14
- # Spacing factor applied between bars
15
- attr_accessor :bar_spacing
16
-
17
53
  def draw
18
54
  @has_left_labels = true
19
- get_maximum_by_stack
55
+ calculate_maximum_by_stack
20
56
  super
21
57
  end
22
58
 
23
- protected
59
+ protected
60
+
61
+ def hide_labels?
62
+ @hide_labels
63
+ end
64
+
65
+ def hide_left_label_area?
66
+ hide_labels?
67
+ end
68
+
69
+ def hide_bottom_label_area?
70
+ @hide_line_markers
71
+ end
72
+
73
+ private
24
74
 
25
75
  def draw_bars
26
76
  # Setup spacing.
27
77
  #
28
78
  # Columns sit stacked.
29
- @bar_spacing ||= 0.9
79
+ bar_width = @graph_height / column_count.to_f
80
+ height = Array.new(column_count, 0)
81
+ length = Array.new(column_count, @graph_left)
82
+ padding = (bar_width * (1 - @bar_spacing)) / 2
83
+ bar_value_label = BarValueLabel.new(column_count, bar_width)
30
84
 
31
- @bar_width = @graph_height / @column_count.to_f
32
- @d = @d.stroke_opacity 0.0
33
- height = Array.new(@column_count, 0)
34
- length = Array.new(@column_count, @graph_left)
35
- padding = (@bar_width * (1 - @bar_spacing)) / 2
36
- if @show_labels_for_bar_values
37
- label_values = Array.new
38
- 0.upto(@column_count-1) {|i| label_values[i] = {:value => 0, :right_x => 0}}
39
- end
40
- @norm_data.each_with_index do |data_row, row_index|
41
- data_row[DATA_VALUES_INDEX].each_with_index do |data_point, point_index|
42
-
43
- ## using the original calcs from the stacked bar chart to get the difference between
44
- ## part of the bart chart we wish to stack.
45
- temp1 = @graph_left + (@graph_width -
46
- data_point * @graph_width -
47
- height[point_index]) + 1
48
- temp2 = @graph_left + @graph_width - height[point_index] - 1
49
- difference = temp2 - temp1
50
-
51
- @d = @d.fill data_row[DATA_COLOR_INDEX]
52
-
53
- left_x = length[point_index] #+ 1
54
- left_y = @graph_top + (@bar_width * point_index) + padding
55
- right_x = left_x + difference
56
- right_y = left_y + @bar_width * @bar_spacing
57
- length[point_index] += difference
85
+ store.norm_data.each_with_index do |data_row, row_index|
86
+ data_row.points.each_with_index do |data_point, point_index|
87
+ ## using the original calculations from the stacked bar chart to get the difference between
88
+ ## part of the bart chart we wish to stack.
89
+ temp1 = @graph_left + (@graph_width -
90
+ data_point * @graph_width -
91
+ height[point_index]) + 1
92
+ temp2 = @graph_left + @graph_width - height[point_index] - 1
93
+ difference = temp2 - temp1
94
+
95
+ left_x = length[point_index]
96
+ left_y = @graph_top + (bar_width * point_index) + padding
97
+ right_x = left_x + difference - @segment_spacing
98
+ right_y = left_y + bar_width * @bar_spacing
99
+ length[point_index] += difference
58
100
  height[point_index] += (data_point * @graph_width - 2)
59
-
60
- if @show_labels_for_bar_values
61
- label_values[point_index][:value] += @norm_data[row_index][3][point_index]
62
- label_values[point_index][:right_x] = right_x
63
- end
64
-
101
+
102
+ bar_value_label.coordinates[point_index] = [left_x, left_y, right_x, right_y]
103
+ bar_value_label.values[point_index] += store.data[row_index].points[point_index]
104
+
65
105
  # if a data point is 0 it can result in weird really thing lines
66
106
  # that shouldn't even be there being drawn on top of the existing
67
107
  # bar - this is bad
68
108
  if data_point != 0
69
- @d = @d.rectangle(left_x, left_y, right_x, right_y)
109
+ rect_renderer = Gruff::Renderer::Rectangle.new(color: data_row.color)
110
+ rect_renderer.render(left_x, left_y, right_x, right_y)
70
111
  # Calculate center based on bar_width and current row
71
112
  end
72
113
  # we still need to draw the labels
73
114
  # Calculate center based on bar_width and current row
74
- label_center = @graph_top + (@bar_width * point_index) + (@bar_width * @bar_spacing / 2.0)
115
+ label_center = left_y + bar_width / 2
75
116
  draw_label(label_center, point_index)
76
117
  end
77
-
78
118
  end
119
+
79
120
  if @show_labels_for_bar_values
80
- label_values.each_with_index do |data, i|
81
- val = (@label_formatting || "%.2f") % data[:value]
82
- draw_value_label(data[:right_x]+40, (@graph_top + (((i+1) * @bar_width) - (@bar_width / 2)))-12, val.commify, true)
121
+ bar_value_label.prepare_sidebar_rendering(@label_formatting) do |x, y, text|
122
+ draw_value_label(x, y, text, true)
83
123
  end
84
124
  end
85
-
86
- @d.draw(@base_image)
87
- end
88
-
89
- def larger_than_max?(data_point, index=0)
90
- max(data_point, index) > @maximum_value
91
- end
92
-
93
- def max(data_point, index)
94
- @data.inject(0) {|sum, item| sum + item[DATA_VALUES_INDEX][index]}
95
125
  end
96
-
97
126
  end