gruff 0.7.0-java → 0.12.0-java

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.
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