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