gruff 0.6.0-java → 0.11.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 (121) 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 +112 -0
  7. data/.travis.yml +24 -15
  8. data/.yardopts +1 -0
  9. data/{History.txt → CHANGELOG.md} +72 -25
  10. data/Gemfile +3 -7
  11. data/README.md +57 -25
  12. data/Rakefile +21 -192
  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 +21 -13
  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 +435 -704
  26. data/lib/gruff/bezier.rb +32 -17
  27. data/lib/gruff/bullet.rb +62 -68
  28. data/lib/gruff/dot.rb +38 -82
  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 +60 -0
  33. data/lib/gruff/line.rb +134 -170
  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 +68 -81
  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 +39 -42
  42. data/lib/gruff/pie.rb +180 -89
  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 +42 -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 +132 -0
  53. data/lib/gruff/renderer/text.rb +53 -0
  54. data/lib/gruff/scatter.rb +163 -182
  55. data/lib/gruff/scene.rb +31 -41
  56. data/lib/gruff/side_bar.rb +81 -65
  57. data/lib/gruff/side_stacked_bar.rb +78 -62
  58. data/lib/gruff/spider.rb +49 -57
  59. data/lib/gruff/stacked_area.rb +40 -32
  60. data/lib/gruff/stacked_bar.rb +86 -53
  61. data/lib/gruff/store/base_data.rb +38 -0
  62. data/lib/gruff/store/custom_data.rb +38 -0
  63. data/lib/gruff/store/store.rb +80 -0
  64. data/lib/gruff/store/xy_data.rb +59 -0
  65. data/lib/gruff/themes.rb +32 -33
  66. data/lib/gruff/version.rb +3 -1
  67. metadata +80 -102
  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_labels_for_null_data.rb +0 -27
  107. data/test/test_legend.rb +0 -68
  108. data/test/test_line.rb +0 -657
  109. data/test/test_mini_bar.rb +0 -33
  110. data/test/test_mini_pie.rb +0 -25
  111. data/test/test_mini_side_bar.rb +0 -36
  112. data/test/test_net.rb +0 -231
  113. data/test/test_photo.rb +0 -41
  114. data/test/test_pie.rb +0 -161
  115. data/test/test_scatter.rb +0 -233
  116. data/test/test_scene.rb +0 -100
  117. data/test/test_side_bar.rb +0 -56
  118. data/test/test_sidestacked_bar.rb +0 -105
  119. data/test/test_spider.rb +0 -226
  120. data/test/test_stacked_area.rb +0 -52
  121. data/test/test_stacked_bar.rb +0 -68
@@ -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,77 @@
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
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
33
+
34
+ # Output the values for the bars on a bar graph.
35
+ # Default is +false+.
36
+ attr_writer :show_labels_for_bar_values
7
37
 
8
- # Spacing factor applied between bars
9
- attr_accessor :bar_spacing
38
+ def initialize_ivars
39
+ super
40
+ @bar_spacing = 0.9
41
+ @group_spacing = 10
42
+ @label_formatting = nil
43
+ @show_labels_for_bar_values = false
44
+ end
45
+ private :initialize_ivars
10
46
 
11
47
  def draw
12
48
  @has_left_labels = true
13
49
  super
14
50
 
15
- return unless @has_data
51
+ return unless data_given?
52
+
16
53
  draw_bars
17
54
  end
18
55
 
19
- protected
56
+ private
20
57
 
21
58
  def draw_bars
22
59
  # Setup spacing.
23
60
  #
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
61
+ bars_width = (@graph_height - calculate_spacing) / column_count.to_f
62
+ bar_width = bars_width / store.length
63
+ height = Array.new(column_count, 0)
64
+ length = Array.new(column_count, @graph_left)
65
+ padding = (bar_width * (1 - @bar_spacing)) / 2
32
66
 
33
67
  # if we're a side stacked bar then we don't need to draw ourself at all
34
68
  # because sometimes (due to different heights/min/max) you can actually
35
69
  # 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]
70
+ return if is_a?(Gruff::SideStackedBar)
40
71
 
41
- data_row[DATA_VALUES_INDEX].each_with_index do |data_point, point_index|
72
+ store.norm_data.each_with_index do |data_row, row_index|
73
+ data_row.points.each_with_index do |data_point, point_index|
74
+ group_spacing = @group_spacing * @scale * point_index
42
75
 
43
76
  # Using the original calcs from the stacked bar chart
44
77
  # to get the difference between
@@ -48,91 +81,74 @@ class Gruff::SideBar < Gruff::Base
48
81
  difference = temp2 - temp1
49
82
 
50
83
  left_x = length[point_index] - 1
51
- left_y = @graph_top + (@bars_width * point_index) + (@bar_width * row_index) + padding
84
+ left_y = @graph_top + (bars_width * point_index) + (bar_width * row_index) + padding + group_spacing
52
85
  right_x = left_x + difference
53
- right_y = left_y + @bar_width * @bar_spacing
86
+ right_y = left_y + bar_width * @bar_spacing
54
87
 
55
88
  height[point_index] += (data_point * @graph_width)
56
89
 
57
- @d = @d.rectangle(left_x, left_y, right_x, right_y)
90
+ rect_renderer = Gruff::Renderer::Rectangle.new(color: data_row.color)
91
+ rect_renderer.render(left_x, left_y, right_x, right_y)
58
92
 
59
93
  # Calculate center based on bar_width and current row
60
94
 
61
95
  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])
96
+ label_center = left_y + bar_width / 2
97
+ draw_label(label_center, row_index, store.norm_data[row_index].label)
64
98
  else
65
- label_center = @graph_top + (@bars_width * point_index + @bars_width / 2)
99
+ label_center = left_y + bars_width / 2
66
100
  draw_label(label_center, point_index)
67
101
  end
68
102
  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)
103
+ val = (@label_formatting || '%.2f') % store.data[row_index].points[point_index]
104
+ draw_value_label(right_x + 40, right_y - bar_width / 2, val.commify, true)
71
105
  end
72
106
  end
73
-
74
107
  end
75
108
 
76
- @d.draw(@base_image)
109
+ Gruff::Renderer.finish
77
110
  end
78
111
 
79
112
  # Instead of base class version, draws vertical background lines and label
80
113
  def draw_line_markers
81
-
82
114
  return if @hide_line_markers
83
115
 
84
- @d = @d.stroke_antialias false
85
-
86
116
  # Draw horizontal line markers and annotate with numbers
87
- @d = @d.stroke(@marker_color)
88
- @d = @d.stroke_width 1
89
117
  number_of_lines = @marker_count || 5
90
118
  number_of_lines = 1 if number_of_lines == 0
91
119
 
92
- # TODO Round maximum marker value to a round number like 100, 0.1, 0.5, etc.
120
+ # TODO: Round maximum marker value to a round number like 100, 0.1, 0.5, etc.
93
121
  increment = significant(@spread.to_f / number_of_lines)
94
122
  (0..number_of_lines).each do |index|
95
-
96
123
  line_diff = (@graph_right - @graph_left) / number_of_lines
97
124
  x = @graph_right - (line_diff * index) - 1
98
- @d = @d.line(x, @graph_bottom, x, @graph_top)
125
+
126
+ line_renderer = Gruff::Renderer::Line.new(color: @marker_color, shadow_color: @marker_shadow_color)
127
+ line_renderer.render(x, @graph_bottom, x, @graph_top)
128
+
99
129
  diff = index - number_of_lines
100
- marker_label = diff.abs * increment + @minimum_value
130
+ marker_label = diff.abs * increment + minimum_value
101
131
 
102
132
  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
133
+ text_renderer = Gruff::Renderer::Text.new(marker_label, font: @font, size: @marker_font_size, color: @font_color)
134
+ text_renderer.add_to_render_queue(0, 0, x, @graph_bottom + LABEL_MARGIN, Magick::CenterGravity)
135
+ end
115
136
  end
116
137
  end
117
138
 
118
139
  ##
119
140
  # Draw on the Y axis instead of the X
120
141
 
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
142
+ def draw_label(y_offset, index, label = nil)
143
+ draw_unique_label(index) do
144
+ lbl = @use_data_label ? label : @labels[index]
145
+
146
+ text_renderer = Gruff::Renderer::Text.new(lbl, font: @font, size: @marker_font_size, color: @font_color)
147
+ text_renderer.add_to_render_queue(@graph_left - LABEL_MARGIN, 1.0, 0.0, y_offset, Magick::EastGravity)
135
148
  end
136
149
  end
137
150
 
151
+ def calculate_spacing
152
+ @scale * @group_spacing * (column_count - 1)
153
+ end
138
154
  end
@@ -1,97 +1,113 @@
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
28
+
29
+ # Spacing factor applied between bars.
30
+ attr_writer :bar_spacing
31
+
32
+ # Number of pixels between bar segments.
33
+ attr_writer :segment_spacing
34
+
35
+ # Set the number output format for labels using sprintf.
36
+ # Default is +"%.2f"+.
37
+ attr_writer :label_formatting
38
+
39
+ # Output the values for the bars on a bar graph.
40
+ # Default is +false+.
41
+ attr_writer :show_labels_for_bar_values
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
+ end
50
+ private :initialize_ivars
13
51
 
14
- # Spacing factor applied between bars
15
- attr_accessor :bar_spacing
16
-
17
52
  def draw
18
53
  @has_left_labels = true
19
- get_maximum_by_stack
54
+ calculate_maximum_by_stack
20
55
  super
21
56
  end
22
57
 
23
- protected
58
+ private
24
59
 
25
60
  def draw_bars
26
61
  # Setup spacing.
27
62
  #
28
63
  # Columns sit stacked.
29
- @bar_spacing ||= 0.9
64
+ bar_width = @graph_height / column_count.to_f
65
+ height = Array.new(column_count, 0)
66
+ length = Array.new(column_count, @graph_left)
67
+ padding = (bar_width * (1 - @bar_spacing)) / 2
68
+ bar_value_label = BarValueLabel.new(column_count, bar_width)
30
69
 
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
70
+ store.norm_data.each_with_index do |data_row, row_index|
71
+ data_row.points.each_with_index do |data_point, point_index|
72
+ ## using the original calcs from the stacked bar chart to get the difference between
73
+ ## part of the bart chart we wish to stack.
74
+ temp1 = @graph_left + (@graph_width -
75
+ data_point * @graph_width -
76
+ height[point_index]) + 1
77
+ temp2 = @graph_left + @graph_width - height[point_index] - 1
78
+ difference = temp2 - temp1
79
+
80
+ left_x = length[point_index]
81
+ left_y = @graph_top + (bar_width * point_index) + padding
82
+ right_x = left_x + difference - @segment_spacing
83
+ right_y = left_y + bar_width * @bar_spacing
84
+ length[point_index] += difference
58
85
  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
-
86
+
87
+ bar_value_label.coordinates[point_index] = [left_x, left_y, right_x, right_y]
88
+ bar_value_label.values[point_index] += store.data[row_index].points[point_index]
89
+
65
90
  # if a data point is 0 it can result in weird really thing lines
66
91
  # that shouldn't even be there being drawn on top of the existing
67
92
  # bar - this is bad
68
93
  if data_point != 0
69
- @d = @d.rectangle(left_x, left_y, right_x, right_y)
94
+ rect_renderer = Gruff::Renderer::Rectangle.new(color: data_row.color)
95
+ rect_renderer.render(left_x, left_y, right_x, right_y)
70
96
  # Calculate center based on bar_width and current row
71
97
  end
72
98
  # we still need to draw the labels
73
99
  # Calculate center based on bar_width and current row
74
- label_center = @graph_top + (@bar_width * point_index) + (@bar_width * @bar_spacing / 2.0)
100
+ label_center = left_y + bar_width / 2
75
101
  draw_label(label_center, point_index)
76
102
  end
77
-
78
103
  end
104
+
79
105
  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)
106
+ bar_value_label.prepare_sidebar_rendering(@label_formatting) do |x, y, text|
107
+ draw_value_label(x, y, text, true)
83
108
  end
84
109
  end
85
-
86
- @d.draw(@base_image)
87
- end
88
110
 
89
- def larger_than_max?(data_point, index=0)
90
- max(data_point, index) > @maximum_value
111
+ Gruff::Renderer.finish
91
112
  end
92
-
93
- def max(data_point, index)
94
- @data.inject(0) {|sum, item| sum + item[DATA_VALUES_INDEX][index]}
95
- end
96
-
97
113
  end