gruff 0.5.1 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
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 +99 -92
  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