gruff 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE.md +18 -0
  3. data/.gitignore +3 -0
  4. data/.rubocop.yml +93 -0
  5. data/.rubocop_todo.yml +23 -810
  6. data/.travis.yml +4 -4
  7. data/.yardopts +1 -0
  8. data/CHANGELOG.md +22 -0
  9. data/Gemfile +3 -1
  10. data/README.md +44 -21
  11. data/Rakefile +2 -206
  12. data/docker/Dockerfile +14 -0
  13. data/docker/build.sh +4 -0
  14. data/docker/launch.sh +4 -0
  15. data/gruff.gemspec +11 -8
  16. data/init.rb +2 -0
  17. data/lib/gruff.rb +23 -0
  18. data/lib/gruff/accumulator_bar.rb +6 -6
  19. data/lib/gruff/area.rb +13 -17
  20. data/lib/gruff/bar.rb +58 -41
  21. data/lib/gruff/base.rb +243 -566
  22. data/lib/gruff/bezier.rb +12 -14
  23. data/lib/gruff/bullet.rb +39 -57
  24. data/lib/gruff/dot.rb +25 -59
  25. data/lib/gruff/{bar_conversion.rb → helper/bar_conversion.rb} +13 -12
  26. data/lib/gruff/helper/bar_value_label_mixin.rb +30 -0
  27. data/lib/gruff/{stacked_mixin.rb → helper/stacked_mixin.rb} +7 -6
  28. data/lib/gruff/line.rb +95 -177
  29. data/lib/gruff/mini/bar.rb +6 -7
  30. data/lib/gruff/mini/legend.rb +16 -32
  31. data/lib/gruff/mini/pie.rb +6 -7
  32. data/lib/gruff/mini/side_bar.rb +4 -5
  33. data/lib/gruff/net.rb +37 -65
  34. data/lib/gruff/patch/rmagick.rb +33 -0
  35. data/lib/gruff/patch/string.rb +8 -0
  36. data/lib/gruff/photo_bar.rb +19 -19
  37. data/lib/gruff/pie.rb +22 -73
  38. data/lib/gruff/renderer/bezier.rb +21 -0
  39. data/lib/gruff/renderer/circle.rb +21 -0
  40. data/lib/gruff/renderer/dash_line.rb +22 -0
  41. data/lib/gruff/renderer/dot.rb +39 -0
  42. data/lib/gruff/renderer/ellipse.rb +21 -0
  43. data/lib/gruff/renderer/line.rb +34 -0
  44. data/lib/gruff/renderer/polygon.rb +23 -0
  45. data/lib/gruff/renderer/polyline.rb +21 -0
  46. data/lib/gruff/renderer/rectangle.rb +19 -0
  47. data/lib/gruff/renderer/renderer.rb +127 -0
  48. data/lib/gruff/renderer/text.rb +42 -0
  49. data/lib/gruff/scatter.rb +85 -156
  50. data/lib/gruff/scene.rb +22 -30
  51. data/lib/gruff/side_bar.rb +62 -58
  52. data/lib/gruff/side_stacked_bar.rb +47 -43
  53. data/lib/gruff/spider.rb +19 -36
  54. data/lib/gruff/stacked_area.rb +17 -21
  55. data/lib/gruff/stacked_bar.rb +50 -24
  56. data/lib/gruff/store/base_data.rb +34 -0
  57. data/lib/gruff/store/custom_data.rb +34 -0
  58. data/lib/gruff/store/store.rb +80 -0
  59. data/lib/gruff/store/xy_data.rb +55 -0
  60. data/lib/gruff/themes.rb +3 -3
  61. data/lib/gruff/version.rb +3 -1
  62. metadata +41 -30
  63. data/Manifest.txt +0 -81
  64. data/assets/bubble.png +0 -0
  65. data/assets/city_scene/background/0000.png +0 -0
  66. data/assets/city_scene/background/0600.png +0 -0
  67. data/assets/city_scene/background/2000.png +0 -0
  68. data/assets/city_scene/clouds/cloudy.png +0 -0
  69. data/assets/city_scene/clouds/partly_cloudy.png +0 -0
  70. data/assets/city_scene/clouds/stormy.png +0 -0
  71. data/assets/city_scene/grass/default.png +0 -0
  72. data/assets/city_scene/haze/true.png +0 -0
  73. data/assets/city_scene/number_sample/1.png +0 -0
  74. data/assets/city_scene/number_sample/2.png +0 -0
  75. data/assets/city_scene/number_sample/default.png +0 -0
  76. data/assets/city_scene/sky/0000.png +0 -0
  77. data/assets/city_scene/sky/0200.png +0 -0
  78. data/assets/city_scene/sky/0400.png +0 -0
  79. data/assets/city_scene/sky/0600.png +0 -0
  80. data/assets/city_scene/sky/0800.png +0 -0
  81. data/assets/city_scene/sky/1000.png +0 -0
  82. data/assets/city_scene/sky/1200.png +0 -0
  83. data/assets/city_scene/sky/1400.png +0 -0
  84. data/assets/city_scene/sky/1500.png +0 -0
  85. data/assets/city_scene/sky/1700.png +0 -0
  86. data/assets/city_scene/sky/2000.png +0 -0
  87. data/assets/pc306715.jpg +0 -0
  88. data/lib/gruff/deprecated.rb +0 -38
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gruff
4
+ class Renderer::Text
5
+ def initialize(text, args = {})
6
+ @text = text.to_s
7
+ @font = args[:font]
8
+ @font_size = args[:size]
9
+ @font_color = args[:color]
10
+ @font_weight = args[:weight] || Magick::NormalWeight
11
+ @rotation = args[:rotation]
12
+ end
13
+
14
+ def render(width, height, x, y, gravity = Magick::NorthGravity)
15
+ draw = Renderer.instance.draw
16
+ image = Renderer.instance.image
17
+ scale = Renderer.instance.scale
18
+
19
+ draw.rotation = @rotation if @rotation
20
+ draw.fill = @font_color
21
+ draw.stroke = 'transparent'
22
+ draw.font = @font if @font
23
+ draw.font_weight = @font_weight
24
+ draw.pointsize = @font_size * scale
25
+ draw.gravity = gravity
26
+ draw.annotate_scaled(image,
27
+ width, height,
28
+ x, y,
29
+ @text, scale)
30
+ draw.rotation = -@rotation if @rotation
31
+ end
32
+
33
+ def self.metrics(text, size, font_weight = Magick::NormalWeight)
34
+ draw = Renderer.instance.draw
35
+ image = Renderer.instance.image
36
+
37
+ draw.font_weight = font_weight
38
+ draw.pointsize = size
39
+ draw.get_type_metrics(image, text.to_s)
40
+ end
41
+ end
42
+ end
@@ -1,15 +1,15 @@
1
- require File.dirname(__FILE__) + '/base'
1
+ # frozen_string_literal: true
2
+
3
+ require 'gruff/base'
2
4
 
3
5
  # Here's how to set up an XY Scatter Chart
4
6
  #
5
- # g = Gruff::Scatter.new(800)
6
- # g.data(:apples, [1,2,3,4], [4,3,2,1])
7
- # g.data('oranges', [5,7,8], [4,1,7])
8
- # g.write('test/output/scatter.png')
9
- #
7
+ # g = Gruff::Scatter.new(800)
8
+ # g.data(:apples, [1,2,3,4], [4,3,2,1])
9
+ # g.data('oranges', [5,7,8], [4,1,7])
10
+ # g.write('test/output/scatter.png')
10
11
  #
11
12
  class Gruff::Scatter < Gruff::Base
12
-
13
13
  # Maximum X Value. The value will get overwritten by the max in the
14
14
  # datasets.
15
15
  attr_accessor :maximum_x_value
@@ -18,47 +18,29 @@ class Gruff::Scatter < Gruff::Base
18
18
  # datasets.
19
19
  attr_accessor :minimum_x_value
20
20
 
21
- # The number of vertical lines shown for reference
21
+ # The number of vertical lines shown for reference.
22
22
  attr_accessor :marker_x_count
23
23
 
24
- #~ # Draw a dashed horizontal line at the given y value
25
- #~ attr_accessor :baseline_y_value
26
-
27
- #~ # Color of the horizontal baseline
28
- #~ attr_accessor :baseline_y_color
29
-
30
- #~ # Draw a dashed horizontal line at the given y value
31
- #~ attr_accessor :baseline_x_value
32
-
33
- #~ # Color of the horizontal baseline
34
- #~ attr_accessor :baseline_x_color
35
-
36
- # Attributes to allow customising the size of the points
24
+ # Attributes to allow customising the size of the points.
37
25
  attr_accessor :circle_radius
38
26
  attr_accessor :stroke_width
39
27
 
40
- # Allow disabling the significant rounding when labeling the X axis
41
- # This is useful when working with a small range of high values (for example, a date range of months, while seconds as units)
28
+ # Allow disabling the significant rounding when labeling the X axis.
29
+ # This is useful when working with a small range of high values (for example, a date range of months, while seconds as units).
42
30
  attr_accessor :disable_significant_rounding_x_axis
43
31
 
44
- # Allow enabling vertical lines. When you have a lot of data, they can work great
32
+ # Allow enabling vertical lines. When you have a lot of data, they can work great.
45
33
  attr_accessor :enable_vertical_line_markers
46
34
 
47
- # Allow using vertical labels in the X axis (and setting the label margin)
35
+ # Allow using vertical labels in the X axis (and setting the label margin).
48
36
  attr_accessor :x_label_margin
49
37
  attr_accessor :use_vertical_x_labels
50
38
 
51
- # Allow passing lambdas to format labels
39
+ # Allow passing lambdas to format labels.
52
40
  attr_accessor :y_axis_label_format
53
41
  attr_accessor :x_axis_label_format
54
42
 
55
- # Gruff::Scatter takes the same parameters as the Gruff::Line graph
56
- #
57
- # ==== Example
58
- #
59
- # g = Gruff::Scatter.new
60
- #
61
- def initialize(*)
43
+ def initialize_ivars
62
44
  super
63
45
 
64
46
  @baseline_x_color = @baseline_y_color = 'red'
@@ -73,61 +55,33 @@ class Gruff::Scatter < Gruff::Base
73
55
  @x_axis_label_format = nil
74
56
  @x_label_margin = nil
75
57
  @y_axis_label_format = nil
76
- end
77
58
 
78
- def setup_drawing
79
- # TODO Need to get x-axis labels working. Current behavior will be to not allow.
80
- @labels = {}
81
-
82
- super
83
-
84
- # Translate our values so that we can use the base methods for drawing
85
- # the standard chart stuff
86
- @column_count = @x_spread
59
+ @store = Gruff::Store.new(Gruff::Store::XYData)
87
60
  end
61
+ private :initialize_ivars
88
62
 
89
63
  def draw
90
64
  super
91
- return unless @has_data
65
+ return unless data_given?
92
66
 
93
67
  # Check to see if more than one datapoint was given. NaN can result otherwise.
94
- @x_increment = (@column_count > 1) ? (@graph_width / (@column_count - 1).to_f) : @graph_width
68
+ @x_increment = (@x_spread > 1) ? (@graph_width / (@x_spread - 1).to_f) : @graph_width
95
69
 
96
- #~ if (defined?(@norm_y_baseline)) then
97
- #~ level = @graph_top + (@graph_height - @norm_baseline * @graph_height)
98
- #~ @d = @d.push
99
- #~ @d.stroke_color @baseline_color
100
- #~ @d.fill_opacity 0.0
101
- #~ @d.stroke_dasharray(10, 20)
102
- #~ @d.stroke_width 5
103
- #~ @d.line(@graph_left, level, @graph_left + @graph_width, level)
104
- #~ @d = @d.pop
105
- #~ end
106
-
107
- #~ if (defined?(@norm_x_baseline)) then
108
-
109
- #~ end
110
-
111
- @norm_data.each do |data_row|
112
- data_row[DATA_VALUES_INDEX].each_with_index do |data_point, index|
113
- x_value = data_row[DATA_VALUES_X_INDEX][index]
114
- next if data_point.nil? || x_value.nil?
70
+ store.norm_data.each do |data_row|
71
+ data_row.coordinates.each do |x_value, y_value|
72
+ next if y_value.nil? || x_value.nil?
115
73
 
116
74
  new_x = get_x_coord(x_value, @graph_width, @graph_left)
117
- new_y = @graph_top + (@graph_height - data_point * @graph_height)
75
+ new_y = @graph_top + (@graph_height - y_value * @graph_height)
118
76
 
119
77
  # Reset each time to avoid thin-line errors
120
- @d = @d.stroke data_row[DATA_COLOR_INDEX]
121
- @d = @d.fill data_row[DATA_COLOR_INDEX]
122
- @d = @d.stroke_opacity 1.0
123
- @d = @d.stroke_width @stroke_width || clip_value_if_greater_than(@columns / (@norm_data.first[1].size * 4), 5.0)
124
-
125
- circle_radius = @circle_radius || clip_value_if_greater_than(@columns / (@norm_data.first[1].size * 2.5), 5.0)
126
- @d = @d.circle(new_x, new_y, new_x - circle_radius, new_y)
78
+ stroke_width = @stroke_width || clip_value_if_greater_than(@columns / (store.norm_data.first[1].size * 4), 5.0)
79
+ circle_radius = @circle_radius || clip_value_if_greater_than(@columns / (store.norm_data.first[1].size * 2.5), 5.0)
80
+ Gruff::Renderer::Circle.new(color: data_row.color, width: stroke_width).render(new_x, new_y, new_x - circle_radius, new_y)
127
81
  end
128
82
  end
129
83
 
130
- @d.draw(@base_image)
84
+ Gruff::Renderer.finish
131
85
  end
132
86
 
133
87
  # The first parameter is the name of the dataset. The next two are the
@@ -140,57 +94,63 @@ class Gruff::Scatter < Gruff::Base
140
94
  # If the color argument is nil, the next color from the default theme will
141
95
  # be used.
142
96
  #
143
- # NOTE: If you want to use a preset theme, you must set it before calling
144
- # data().
97
+ # @note If you want to use a preset theme, you must set it before calling {#data}.
98
+ #
99
+ # @param name [String, Symbol] containing the name of the dataset.
100
+ # @param x_data_points [Array] An Array of of x-axis data points.
101
+ # @param y_data_points [Array] An Array of of y-axis data points.
102
+ # @param color [String] The hex string for the color of the dataset. Defaults to nil.
145
103
  #
146
- # ==== Parameters
147
- # name:: String or Symbol containing the name of the dataset.
148
- # x_data_points:: An Array of of x-axis data points.
149
- # y_data_points:: An Array of of y-axis data points.
150
- # color:: The hex string for the color of the dataset. Defaults to nil.
151
104
  #
152
- # ==== Exceptions
153
- # Data points contain nil values::
105
+ # @raise [ArgumentError] Data points contain nil values.
154
106
  # This error will get raised if either the x or y axis data points array
155
- # contains a <tt>nil</tt> value. The graph will not make an assumption
156
- # as how to graph <tt>nil</tt>
157
- # x_data_points is empty::
107
+ # contains a +nil+ value. The graph will not make an assumption
108
+ # as how to graph +nil+.
109
+ # @raise [ArgumentError] +x_data_points+ is empty.
158
110
  # This error is raised when the array for the x-axis points are empty
159
- # y_data_points is empty::
160
- # This error is raised when the array for the y-axis points are empty
161
- # x_data_points.length != y_data_points.length::
162
- # Error means that the x and y axis point arrays do not match in length
111
+ # @raise [ArgumentError] +y_data_points+ is empty.
112
+ # This error is raised when the array for the y-axis points are empty.
113
+ # @raise [ArgumentError] +x_data_points.length != y_data_points.length+.
114
+ # Error means that the x and y axis point arrays do not match in length.
163
115
  #
164
- # ==== Examples
165
- # g = Gruff::Scatter.new
166
- # g.data(:apples, [1,2,3], [3,2,1])
167
- # g.data('oranges', [1,1,1], [2,3,4])
168
- # g.data('bitter_melon', [3,5,6], [6,7,8], '#000000')
116
+ # @example
117
+ # g = Gruff::Scatter.new
118
+ # g.data(:apples, [1,2,3], [3,2,1])
119
+ # g.data('oranges', [1,1,1], [2,3,4])
120
+ # g.data('bitter_melon', [3,5,6], [6,7,8], '#000000')
169
121
  #
170
122
  def data(name, x_data_points = [], y_data_points = [], color = nil)
123
+ # make sure it's an array
124
+ x_data_points = Array(x_data_points)
125
+ y_data_points = Array(y_data_points)
126
+
171
127
  raise ArgumentError, 'Data Points contain nil Value!' if x_data_points.include?(nil) || y_data_points.include?(nil)
172
128
  raise ArgumentError, 'x_data_points is empty!' if x_data_points.empty?
173
129
  raise ArgumentError, 'y_data_points is empty!' if y_data_points.empty?
174
130
  raise ArgumentError, 'x_data_points.length != y_data_points.length!' if x_data_points.length != y_data_points.length
175
131
 
176
- # Call the existing data routine for the y axis data
177
- super(name, y_data_points, color)
132
+ # Call the existing data routine for the x/y axis data
133
+ store.add(name, y_data_points, color, x_data_points)
134
+ end
178
135
 
179
- #append the x data to the last entry that was just added in the @data member
180
- last_elem = @data.length() - 1
181
- @data[last_elem] << x_data_points
136
+ alias dataxy data
182
137
 
183
- if @maximum_x_value.nil? && @minimum_x_value.nil?
184
- @maximum_x_value = @minimum_x_value = x_data_points.first
185
- end
138
+ private
139
+
140
+ def setup_data
141
+ # Update the global min/max values for the x data
142
+ @maximum_x_value ||= store.max_x
143
+ @minimum_x_value ||= store.min_x
186
144
 
187
- @maximum_x_value = x_data_points.max > @maximum_x_value ?
188
- x_data_points.max : @maximum_x_value
189
- @minimum_x_value = x_data_points.min < @minimum_x_value ?
190
- x_data_points.min : @minimum_x_value
145
+ super
191
146
  end
192
147
 
193
- protected
148
+ def setup_drawing
149
+ # TODO: Need to get x-axis labels working. Current behavior will be to not allow.
150
+ @labels = {}
151
+
152
+ super
153
+ end
194
154
 
195
155
  def calculate_spread #:nodoc:
196
156
  super
@@ -198,25 +158,10 @@ protected
198
158
  @x_spread = @x_spread > 0 ? @x_spread : 1
199
159
  end
200
160
 
201
- def normalize(force = nil)
202
- if @norm_data.nil? || force
203
- @norm_data = []
204
- return unless @has_data
161
+ def normalize
162
+ return unless data_given?
205
163
 
206
- @data.each do |data_row|
207
- norm_data_points = [data_row[DATA_LABEL_INDEX]]
208
- norm_data_points << data_row[DATA_VALUES_INDEX].map do |r|
209
- (r.to_f - @minimum_value.to_f) / @spread
210
- end
211
- norm_data_points << data_row[DATA_COLOR_INDEX]
212
- norm_data_points << data_row[DATA_VALUES_X_INDEX].map do |r|
213
- (r.to_f - @minimum_x_value.to_f) / @x_spread
214
- end
215
- @norm_data << norm_data_points
216
- end
217
- end
218
- #~ @norm_y_baseline = (@baseline_y_value.to_f / @maximum_value.to_f) if @baseline_y_value
219
- #~ @norm_x_baseline = (@baseline_x_value.to_f / @maximum_x_value.to_f) if @baseline_x_value
164
+ store.normalize(minimum_x: @minimum_x_value, spread_x: @x_spread, minimum_y: minimum_value, spread_y: @spread)
220
165
  end
221
166
 
222
167
  def draw_line_markers
@@ -224,10 +169,8 @@ protected
224
169
  super
225
170
  return if @hide_line_markers
226
171
 
227
- @d = @d.stroke_antialias false
228
-
229
172
  if @x_axis_increment.nil?
230
- # TODO Do the same for larger numbers...100, 75, 50, 25
173
+ # TODO: Do the same for larger numbers...100, 75, 50, 25
231
174
  if @marker_x_count.nil?
232
175
  (3..7).each do |lines|
233
176
  if @x_spread % lines == 0.0
@@ -242,47 +185,36 @@ protected
242
185
  @x_increment = significant(@x_increment)
243
186
  end
244
187
  else
245
- # TODO Make this work for negative values
246
- @maximum_x_value = [@maximum_value.ceil, @x_axis_increment].max
188
+ # TODO: Make this work for negative values
189
+ @maximum_x_value = [maximum_value.ceil, @x_axis_increment].max
247
190
  @minimum_x_value = @minimum_x_value.floor
248
191
  calculate_spread
249
- normalize(true)
192
+ normalize
250
193
 
251
194
  @marker_count = (@x_spread / @x_axis_increment).to_i
252
195
  @x_increment = @x_axis_increment
253
196
  end
254
- @increment_x_scaled = @graph_width.to_f / (@x_spread / @x_increment)
197
+ increment_x_scaled = @graph_width.to_f / (@x_spread / @x_increment)
255
198
 
256
199
  # Draw vertical line markers and annotate with numbers
257
200
  (0..@marker_x_count).each do |index|
258
- # TODO Fix the vertical lines, and enable them by default. Not pretty when they don't match up with top y-axis line
201
+ # TODO: Fix the vertical lines, and enable them by default. Not pretty when they don't match up with top y-axis line
259
202
  if @enable_vertical_line_markers
260
- x = @graph_left + @graph_width - index.to_f * @increment_x_scaled
261
- @d = @d.stroke(@marker_color)
262
- @d = @d.stroke_width 1
263
- @d = @d.line(x, @graph_top, x, @graph_bottom)
203
+ x = @graph_left + @graph_width - index.to_f * increment_x_scaled
204
+ Gruff::Renderer::Line.new(color: @marker_color).render(x, @graph_top, x, @graph_bottom)
264
205
  end
265
206
 
266
207
  unless @hide_line_numbers
267
208
  marker_label = index * @x_increment + @minimum_x_value.to_f
268
209
  y_offset = @graph_bottom + (@x_label_margin || LABEL_MARGIN)
269
- x_offset = get_x_coord(index.to_f, @increment_x_scaled, @graph_left)
270
-
271
- @d.fill = @font_color
272
- @d.font = @font if @font
273
- @d.stroke('transparent')
274
- @d.pointsize = scale_fontsize(@marker_font_size)
275
- @d.gravity = NorthGravity
276
- @d.rotation = -90.0 if @use_vertical_x_labels
277
- @d = @d.annotate_scaled(@base_image,
278
- 1.0, 1.0,
279
- x_offset, y_offset,
280
- vertical_label(marker_label, @x_increment), @scale)
281
- @d.rotation = 90.0 if @use_vertical_x_labels
210
+ x_offset = get_x_coord(index.to_f, increment_x_scaled, @graph_left)
211
+
212
+ label = vertical_label(marker_label, @x_increment)
213
+ rotation = -90.0 if @use_vertical_x_labels
214
+ text_renderer = Gruff::Renderer::Text.new(label, font: @font, size: @marker_font_size, color: @font_color, rotation: rotation)
215
+ text_renderer.render(1.0, 1.0, x_offset, y_offset)
282
216
  end
283
217
  end
284
-
285
- @d = @d.stroke_antialias true
286
218
  end
287
219
 
288
220
  def label(value, increment)
@@ -301,10 +233,7 @@ protected
301
233
  end
302
234
  end
303
235
 
304
- private
305
-
306
236
  def get_x_coord(x_data_point, width, offset) #:nodoc:
307
237
  x_data_point * width + offset
308
238
  end
309
-
310
- end # end Gruff::Scatter
239
+ end
@@ -1,7 +1,8 @@
1
- require "observer"
2
- require File.dirname(__FILE__) + '/base'
1
+ # frozen_string_literal: true
2
+
3
+ require 'observer'
4
+ require 'gruff/base'
3
5
 
4
- ##
5
6
  # A scene is a non-linear graph that assembles layers together to tell a story.
6
7
  # Layers are folders with appropriately named files (see below). You can group
7
8
  # layers and control them together or just set their values individually.
@@ -11,8 +12,6 @@ require File.dirname(__FILE__) + '/base'
11
12
  # * A city scene that changes with the time of day and the weather conditions.
12
13
  # * A traffic map that shows red lines on streets that are crowded and green on free-flowing ones.
13
14
  #
14
- # Usage:
15
- #
16
15
  # g = Gruff::Scene.new("500x100", "path/to/city_scene_directory")
17
16
  #
18
17
  # # Define order of layers, back to front
@@ -30,20 +29,17 @@ require File.dirname(__FILE__) + '/base'
30
29
  # # Write the final graph to disk
31
30
  # g.write "hazy_daytime_city_scene.png"
32
31
  #
33
- #
34
32
  # There are several rules that will magically select a layer when possible.
35
33
  #
36
34
  # * Numbered files will be selected according to the closest value that is less than the input value.
37
- # * 'true.png' and 'false.png' will be used as booleans.
35
+ # * +'true.png'+ and +'false.png'+ will be used as booleans.
38
36
  # * Other named files will be used if the input matches the filename (without the filetype extension).
39
- # * If there is a file named 'default.png', it will be used unless other input values are set for the corresponding layer.
40
-
37
+ # * If there is a file named +'default.png'+, it will be used unless other input values are set for the corresponding layer.
41
38
  class Gruff::Scene < Gruff::Base
42
-
43
- # An array listing the foldernames that will be rendered, from back to front.
44
- #
45
- # g.layers = %w(sky clouds buildings street people)
39
+ # An array listing the folder names that will be rendered, from back to front.
46
40
  #
41
+ # @example
42
+ # g.layers = %w(sky clouds buildings street people)
47
43
  attr_reader :layers
48
44
 
49
45
  def initialize(target_width, base_dir)
@@ -55,9 +51,9 @@ class Gruff::Scene < Gruff::Base
55
51
 
56
52
  def draw
57
53
  # Join all the custom paths and filter out the empty ones
58
- image_paths = @layers.map { |layer| layer.path }.reject { |path| path.empty? }
54
+ image_paths = @layers.map(&:path).reject(&:empty?)
59
55
  images = Magick::ImageList.new(*image_paths)
60
- @base_image = images.flatten_images
56
+ Gruff::Renderer.background_image = images.flatten_images
61
57
  end
62
58
 
63
59
  def layers=(ordered_list)
@@ -77,10 +73,10 @@ class Gruff::Scene < Gruff::Base
77
73
  def method_missing(method_name, *args)
78
74
  case method_name.to_s
79
75
  when /^(\w+)_group=$/
80
- add_group $1, *args
76
+ add_group Regexp.last_match(1), *args
81
77
  return
82
78
  when /^(\w+)=$/
83
- set_input $1, args.first
79
+ set_input Regexp.last_match(1), args.first
84
80
  return
85
81
  end
86
82
  super
@@ -93,19 +89,16 @@ private
93
89
  end
94
90
 
95
91
  def set_input(input_name, input_value)
96
- if not @groups[input_name].nil?
92
+ if !@groups[input_name].nil?
97
93
  @groups[input_name].send_updates(input_value)
98
- else
99
- if chosen_layer = @layers.detect { |layer| layer.name == input_name }
100
- chosen_layer.update input_value
101
- end
94
+ elsif chosen_layer = @layers.find { |layer| layer.name == input_name }
95
+ chosen_layer.update input_value
102
96
  end
103
97
  end
104
-
105
98
  end
106
99
 
100
+ # @private
107
101
  class Gruff::Group
108
-
109
102
  include Observable
110
103
  attr_reader :name
111
104
 
@@ -120,11 +113,10 @@ class Gruff::Group
120
113
  changed
121
114
  notify_observers value
122
115
  end
123
-
124
116
  end
125
117
 
118
+ # @private
126
119
  class Gruff::Layer
127
-
128
120
  attr_reader :name
129
121
 
130
122
  def initialize(base_dir, folder_name)
@@ -149,7 +141,7 @@ class Gruff::Layer
149
141
  when /^-?(\d+\.)?\d+$/
150
142
  select_numeric value
151
143
  when /(\d\d):(\d\d):\d\d/
152
- select_time "#{$1}#{$2}"
144
+ select_time "#{Regexp.last_match(1)}#{Regexp.last_match(2)}"
153
145
  else
154
146
  select_default
155
147
  end
@@ -185,7 +177,8 @@ private
185
177
  return "#{times[index - 1]}.png"
186
178
  end
187
179
  end
188
- return "#{times.last}.png"
180
+
181
+ "#{times.last}.png"
189
182
  end
190
183
 
191
184
  # Match "partly cloudy" to "partly_cloudy.png"
@@ -194,7 +187,7 @@ private
194
187
  end
195
188
 
196
189
  def select_default
197
- @filenames.include?("default.png") ? "default.png" : ''
190
+ @filenames.include?('default.png') ? 'default.png' : ''
198
191
  end
199
192
 
200
193
  # Returns the string "#{filename}.png", if it exists.
@@ -203,5 +196,4 @@ private
203
196
  def file_exists_or_blank(filename)
204
197
  @filenames.include?("#{filename}.png") ? "#{filename}.png" : select_default
205
198
  end
206
-
207
199
  end