schapht-gruff 0.3.5

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 (80) hide show
  1. data/History.txt +111 -0
  2. data/MIT-LICENSE +21 -0
  3. data/Manifest.txt +79 -0
  4. data/README.txt +40 -0
  5. data/Rakefile +55 -0
  6. data/assets/bubble.png +0 -0
  7. data/assets/city_scene/background/0000.png +0 -0
  8. data/assets/city_scene/background/0600.png +0 -0
  9. data/assets/city_scene/background/2000.png +0 -0
  10. data/assets/city_scene/clouds/cloudy.png +0 -0
  11. data/assets/city_scene/clouds/partly_cloudy.png +0 -0
  12. data/assets/city_scene/clouds/stormy.png +0 -0
  13. data/assets/city_scene/grass/default.png +0 -0
  14. data/assets/city_scene/haze/true.png +0 -0
  15. data/assets/city_scene/number_sample/1.png +0 -0
  16. data/assets/city_scene/number_sample/2.png +0 -0
  17. data/assets/city_scene/number_sample/default.png +0 -0
  18. data/assets/city_scene/sky/0000.png +0 -0
  19. data/assets/city_scene/sky/0200.png +0 -0
  20. data/assets/city_scene/sky/0400.png +0 -0
  21. data/assets/city_scene/sky/0600.png +0 -0
  22. data/assets/city_scene/sky/0800.png +0 -0
  23. data/assets/city_scene/sky/1000.png +0 -0
  24. data/assets/city_scene/sky/1200.png +0 -0
  25. data/assets/city_scene/sky/1400.png +0 -0
  26. data/assets/city_scene/sky/1500.png +0 -0
  27. data/assets/city_scene/sky/1700.png +0 -0
  28. data/assets/city_scene/sky/2000.png +0 -0
  29. data/assets/pc306715.jpg +0 -0
  30. data/assets/plastik/blue.png +0 -0
  31. data/assets/plastik/green.png +0 -0
  32. data/assets/plastik/red.png +0 -0
  33. data/init.rb +2 -0
  34. data/lib/gruff.rb +27 -0
  35. data/lib/gruff/accumulator_bar.rb +27 -0
  36. data/lib/gruff/area.rb +58 -0
  37. data/lib/gruff/bar.rb +84 -0
  38. data/lib/gruff/bar_conversion.rb +46 -0
  39. data/lib/gruff/base.rb +1112 -0
  40. data/lib/gruff/bullet.rb +109 -0
  41. data/lib/gruff/deprecated.rb +39 -0
  42. data/lib/gruff/line.rb +105 -0
  43. data/lib/gruff/mini/bar.rb +32 -0
  44. data/lib/gruff/mini/legend.rb +77 -0
  45. data/lib/gruff/mini/pie.rb +36 -0
  46. data/lib/gruff/mini/side_bar.rb +35 -0
  47. data/lib/gruff/net.rb +142 -0
  48. data/lib/gruff/photo_bar.rb +100 -0
  49. data/lib/gruff/pie.rb +124 -0
  50. data/lib/gruff/scene.rb +209 -0
  51. data/lib/gruff/side_bar.rb +115 -0
  52. data/lib/gruff/side_stacked_bar.rb +74 -0
  53. data/lib/gruff/spider.rb +130 -0
  54. data/lib/gruff/stacked_area.rb +67 -0
  55. data/lib/gruff/stacked_bar.rb +54 -0
  56. data/lib/gruff/stacked_mixin.rb +23 -0
  57. data/rails_generators/gruff/gruff_generator.rb +63 -0
  58. data/rails_generators/gruff/templates/controller.rb +32 -0
  59. data/rails_generators/gruff/templates/functional_test.rb +24 -0
  60. data/test/gruff_test_case.rb +123 -0
  61. data/test/test_accumulator_bar.rb +50 -0
  62. data/test/test_area.rb +134 -0
  63. data/test/test_bar.rb +283 -0
  64. data/test/test_base.rb +8 -0
  65. data/test/test_bullet.rb +26 -0
  66. data/test/test_legend.rb +68 -0
  67. data/test/test_line.rb +513 -0
  68. data/test/test_mini_bar.rb +32 -0
  69. data/test/test_mini_pie.rb +20 -0
  70. data/test/test_mini_side_bar.rb +37 -0
  71. data/test/test_net.rb +230 -0
  72. data/test/test_photo.rb +41 -0
  73. data/test/test_pie.rb +154 -0
  74. data/test/test_scene.rb +100 -0
  75. data/test/test_side_bar.rb +12 -0
  76. data/test/test_sidestacked_bar.rb +89 -0
  77. data/test/test_spider.rb +216 -0
  78. data/test/test_stacked_area.rb +52 -0
  79. data/test/test_stacked_bar.rb +52 -0
  80. metadata +160 -0
data/lib/gruff/bar.rb ADDED
@@ -0,0 +1,84 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+ require File.dirname(__FILE__) + '/bar_conversion'
3
+
4
+ class Gruff::Bar < Gruff::Base
5
+
6
+ def draw
7
+ # Labels will be centered over the left of the bar if
8
+ # there are more labels than columns. This is basically the same
9
+ # as where it would be for a line graph.
10
+ @center_labels_over_point = (@labels.keys.length > @column_count ? true : false)
11
+
12
+ super
13
+ return unless @has_data
14
+
15
+ draw_bars
16
+ end
17
+
18
+ protected
19
+
20
+ def draw_bars
21
+ # Setup spacing.
22
+ #
23
+ # Columns sit side-by-side.
24
+ spacing_factor = 0.9 # space between the bars
25
+ @bar_width = @graph_width / (@column_count * @data.length).to_f
26
+ padding = (@bar_width * (1 - spacing_factor)) / 2
27
+
28
+ @d = @d.stroke_opacity 0.0
29
+
30
+ # Setup the BarConversion Object
31
+ conversion = Gruff::BarConversion.new()
32
+ conversion.graph_height = @graph_height
33
+ conversion.graph_top = @graph_top
34
+
35
+ # Set up the right mode [1,2,3] see BarConversion for further explanation
36
+ if @minimum_value >= 0 then
37
+ # all bars go from zero to positiv
38
+ conversion.mode = 1
39
+ else
40
+ # all bars go from 0 to negativ
41
+ if @maximum_value <= 0 then
42
+ conversion.mode = 2
43
+ else
44
+ # bars either go from zero to negativ or to positiv
45
+ conversion.mode = 3
46
+ conversion.spread = @spread
47
+ conversion.minimum_value = @minimum_value
48
+ conversion.zero = -@minimum_value/@spread
49
+ end
50
+ end
51
+
52
+ # iterate over all normalised data
53
+ @norm_data.each_with_index do |data_row, row_index|
54
+
55
+ data_row[DATA_VALUES_INDEX].each_with_index do |data_point, point_index|
56
+ # Use incremented x and scaled y
57
+ # x
58
+ left_x = @graph_left + (@bar_width * (row_index + point_index + ((@data.length - 1) * point_index))) + padding
59
+ right_x = left_x + @bar_width * spacing_factor
60
+ # y
61
+ conv = []
62
+ conversion.getLeftYRightYscaled( data_point, conv )
63
+
64
+ # create new bar
65
+ @d = @d.fill data_row[DATA_COLOR_INDEX]
66
+ @d = @d.rectangle(left_x, conv[0], right_x, conv[1])
67
+
68
+ # Calculate center based on bar_width and current row
69
+ label_center = @graph_left +
70
+ (@data.length * @bar_width * point_index) +
71
+ (@data.length * @bar_width / 2.0)
72
+ # Subtract half a bar width to center left if requested
73
+ draw_label(label_center - (@center_labels_over_point ? @bar_width / 2.0 : 0.0), point_index)
74
+ end
75
+
76
+ end
77
+
78
+ # Draw the last label if requested
79
+ draw_label(@graph_right, @column_count) if @center_labels_over_point
80
+
81
+ @d.draw(@base_image)
82
+ end
83
+
84
+ end
@@ -0,0 +1,46 @@
1
+ ##
2
+ # Original Author: David Stokar
3
+ #
4
+ # This class perfoms the y coordinats conversion for the bar class.
5
+ #
6
+ # There are three cases:
7
+ #
8
+ # 1. Bars all go from zero in positive direction
9
+ # 2. Bars all go from zero to negative direction
10
+ # 3. Bars either go from zero to positive or from zero to negative
11
+ #
12
+ class Gruff::BarConversion
13
+ attr_writer :mode
14
+ attr_writer :zero
15
+ attr_writer :graph_top
16
+ attr_writer :graph_height
17
+ attr_writer :minimum_value
18
+ attr_writer :spread
19
+
20
+ def getLeftYRightYscaled( data_point, result )
21
+ case @mode
22
+ when 1 then # Case one
23
+ # minimum value >= 0 ( only positiv values )
24
+ result[0] = @graph_top + @graph_height*(1 - data_point) + 1
25
+ result[1] = @graph_top + @graph_height - 1
26
+ when 2 then # Case two
27
+ # only negativ values
28
+ result[0] = @graph_top + 1
29
+ result[1] = @graph_top + @graph_height*(1 - data_point) - 1
30
+ when 3 then # Case three
31
+ # positiv and negativ values
32
+ val = data_point-@minimum_value/@spread
33
+ if ( data_point >= @zero ) then
34
+ result[0] = @graph_top + @graph_height*(1 - (val-@zero)) + 1
35
+ result[1] = @graph_top + @graph_height*(1 - @zero) - 1
36
+ else
37
+ result[0] = @graph_top + @graph_height*(1 - (val-@zero)) + 1
38
+ result[1] = @graph_top + @graph_height*(1 - @zero) - 1
39
+ end
40
+ else
41
+ result[0] = 0.0
42
+ result[1] = 0.0
43
+ end
44
+ end
45
+
46
+ end
data/lib/gruff/base.rb ADDED
@@ -0,0 +1,1112 @@
1
+ require 'rubygems'
2
+ require 'RMagick'
3
+
4
+ require File.dirname(__FILE__) + '/deprecated'
5
+
6
+ ##
7
+ # = Gruff. Graphs.
8
+ #
9
+ # Author:: Geoffrey Grosenbach boss@topfunky.com
10
+ #
11
+ # Originally Created:: October 23, 2005
12
+ #
13
+ # Extra thanks to Tim Hunter for writing RMagick, and also contributions by
14
+ # Jarkko Laine, Mike Perham, Andreas Schwarz, Alun Eyre, Guillaume Theoret,
15
+ # David Stokar, Paul Rogers, Dave Woodward, Frank Oxener, Kevin Clark, Cies
16
+ # Breijs, Richard Cowin, and a cast of thousands.
17
+ #
18
+ # See Gruff::Base#theme= for setting themes.
19
+
20
+ module Gruff
21
+
22
+ # This is the version of Gruff you are using.
23
+ VERSION = '0.3.5'
24
+
25
+ class Base
26
+
27
+ include Magick
28
+ include Deprecated
29
+
30
+ # Draw extra lines showing where the margins and text centers are
31
+ DEBUG = false
32
+
33
+ # Used for navigating the array of data to plot
34
+ DATA_LABEL_INDEX = 0
35
+ DATA_VALUES_INDEX = 1
36
+ DATA_COLOR_INDEX = 2
37
+
38
+ # Space around text elements. Mostly used for vertical spacing
39
+ LEGEND_MARGIN = TITLE_MARGIN = LABEL_MARGIN = 10.0
40
+
41
+ DEFAULT_TARGET_WIDTH = 800
42
+
43
+ # Blank space above the graph
44
+ attr_accessor :top_margin
45
+
46
+ # Blank space below the graph
47
+ attr_accessor :bottom_margin
48
+
49
+ # Blank space to the right of the graph
50
+ attr_accessor :right_margin
51
+
52
+ # Blank space to the left of the graph
53
+ attr_accessor :left_margin
54
+
55
+ # A hash of names for the individual columns, where the key is the array
56
+ # index for the column this label represents.
57
+ #
58
+ # Not all columns need to be named.
59
+ #
60
+ # Example: 0 => 2005, 3 => 2006, 5 => 2007, 7 => 2008
61
+ attr_accessor :labels
62
+
63
+ # Used internally for spacing.
64
+ #
65
+ # By default, labels are centered over the point they represent.
66
+ attr_accessor :center_labels_over_point
67
+
68
+ # Used internally for horizontal graph types.
69
+ attr_accessor :has_left_labels
70
+
71
+ # A label for the bottom of the graph
72
+ attr_accessor :x_axis_label
73
+
74
+ # A label for the left side of the graph
75
+ attr_accessor :y_axis_label
76
+
77
+ # attr_accessor :x_axis_increment
78
+
79
+ # Manually set increment of the horizontal marking lines
80
+ attr_accessor :y_axis_increment
81
+
82
+ # Get or set the list of colors that will be used to draw the bars or lines.
83
+ attr_accessor :colors
84
+
85
+ # The large title of the graph displayed at the top
86
+ attr_accessor :title
87
+
88
+ # Font used for titles, labels, etc. Works best if you provide the full
89
+ # path to the TTF font file. RMagick must be built with the Freetype
90
+ # libraries for this to work properly.
91
+ #
92
+ # Tries to find Bitstream Vera (Vera.ttf) in the location specified by
93
+ # ENV['MAGICK_FONT_PATH']. Uses default RMagick font otherwise.
94
+ #
95
+ # The font= method below fulfills the role of the writer, so we only need
96
+ # a reader here.
97
+ attr_reader :font
98
+
99
+ attr_accessor :font_color
100
+
101
+ # Prevent drawing of line markers
102
+ attr_accessor :hide_line_markers
103
+
104
+ # Prevent drawing of the legend
105
+ attr_accessor :hide_legend
106
+
107
+ # Prevent drawing of the title
108
+ attr_accessor :hide_title
109
+
110
+ # Prevent drawing of line numbers
111
+ attr_accessor :hide_line_numbers
112
+
113
+ # Message shown when there is no data. Fits up to 20 characters. Defaults
114
+ # to "No Data."
115
+ attr_accessor :no_data_message
116
+
117
+ # The font size of the large title at the top of the graph
118
+ attr_accessor :title_font_size
119
+
120
+ # Optionally set the size of the font. Based on an 800x600px graph.
121
+ # Default is 20.
122
+ #
123
+ # Will be scaled down if graph is smaller than 800px wide.
124
+ attr_accessor :legend_font_size
125
+
126
+ # The font size of the labels around the graph
127
+ attr_accessor :marker_font_size
128
+
129
+ # The color of the auxiliary lines
130
+ attr_accessor :marker_color
131
+
132
+ # The number of horizontal lines shown for reference
133
+ attr_accessor :marker_count
134
+
135
+ # You can manually set a minimum value instead of having the values
136
+ # guessed for you.
137
+ #
138
+ # Set it after you have given all your data to the graph object.
139
+ attr_accessor :minimum_value
140
+
141
+ # You can manually set a maximum value, such as a percentage-based graph
142
+ # that always goes to 100.
143
+ #
144
+ # If you use this, you must set it after you have given all your data to
145
+ # the graph object.
146
+ attr_accessor :maximum_value
147
+
148
+ # Set to false if you don't want the data to be sorted with largest avg
149
+ # values at the back.
150
+ attr_accessor :sort
151
+
152
+ # Experimental
153
+ attr_accessor :additional_line_values
154
+
155
+ # Experimental
156
+ attr_accessor :stacked
157
+
158
+ # Optionally set the size of the colored box by each item in the legend.
159
+ # Default is 20.0
160
+ #
161
+ # Will be scaled down if graph is smaller than 800px wide.
162
+ attr_accessor :legend_box_size
163
+
164
+ # If one numerical argument is given, the graph is drawn at 4/3 ratio
165
+ # according to the given width (800 results in 800x600, 400 gives 400x300,
166
+ # etc.).
167
+ #
168
+ # Or, send a geometry string for other ratios ('800x400', '400x225').
169
+ #
170
+ # Looks for Bitstream Vera as the default font. Expects an environment var
171
+ # of MAGICK_FONT_PATH to be set. (Uses RMagick's default font otherwise.)
172
+ def initialize(target_width=DEFAULT_TARGET_WIDTH)
173
+ @top_margin = @bottom_margin = @left_margin = @right_margin = 20.0
174
+
175
+ if not Numeric === target_width
176
+ geometric_width, geometric_height = target_width.split('x')
177
+ @columns = geometric_width.to_f
178
+ @rows = geometric_height.to_f
179
+ else
180
+ @columns = target_width.to_f
181
+ @rows = target_width.to_f * 0.75
182
+ end
183
+
184
+ initialize_ivars
185
+
186
+ reset_themes
187
+ theme_keynote
188
+ end
189
+
190
+ # Set instance variables for this object.
191
+ #
192
+ # Subclasses can override this, call super, then set values separately.
193
+ #
194
+ # This makes it possible to set defaults in a subclass but still allow
195
+ # developers to change this values in their program.
196
+ def initialize_ivars
197
+ # Internal for calculations
198
+ @raw_columns = 800.0
199
+ @raw_rows = 800.0 * (@rows/@columns)
200
+ @column_count = 0
201
+ @marker_count = nil
202
+ @maximum_value = @minimum_value = nil
203
+ @has_data = false
204
+ @data = Array.new
205
+ @labels = Hash.new
206
+ @labels_seen = Hash.new
207
+ @sort = true
208
+ @title = nil
209
+
210
+ @scale = @columns / @raw_columns
211
+
212
+ vera_font_path = File.expand_path('Vera.ttf', ENV['MAGICK_FONT_PATH'])
213
+ @font = File.exists?(vera_font_path) ? vera_font_path : nil
214
+
215
+ @marker_font_size = 21.0
216
+ @legend_font_size = 20.0
217
+ @title_font_size = 36.0
218
+
219
+ @legend_box_size = 20.0
220
+
221
+ @no_data_message = "No Data"
222
+
223
+ @hide_line_markers = @hide_legend = @hide_title = @hide_line_numbers = false
224
+ @center_labels_over_point = true
225
+ @has_left_labels = false
226
+
227
+ @additional_line_values = []
228
+ @additional_line_colors = []
229
+ @theme_options = {}
230
+
231
+ @x_axis_label = @y_axis_label = nil
232
+ @y_axis_increment = nil
233
+ @stacked = nil
234
+ @norm_data = nil
235
+ end
236
+
237
+ # Sets the top, bottom, left and right margins to +margin+.
238
+ def margins=(margin)
239
+ @top_margin = @left_margin = @right_margin = @bottom_margin = margin
240
+ end
241
+
242
+ # Sets the font for graph text to the font at +font_path+.
243
+ def font=(font_path)
244
+ @font = font_path
245
+ @d.font = @font
246
+ end
247
+
248
+ # Add a color to the list of available colors for lines.
249
+ #
250
+ # Example:
251
+ # add_color('#c0e9d3')
252
+ def add_color(colorname)
253
+ @colors << colorname
254
+ end
255
+
256
+ # Replace the entire color list with a new array of colors. You need to
257
+ # have one more color than the number of datasets you intend to draw. Also
258
+ # aliased as the colors= setter method.
259
+ #
260
+ # Example:
261
+ # replace_colors ['#cc99cc', '#d9e043', '#34d8a2']
262
+ def replace_colors(color_list=[])
263
+ @colors = color_list
264
+ end
265
+
266
+ # You can set a theme manually. Assign a hash to this method before you
267
+ # send your data.
268
+ #
269
+ # graph.theme = {
270
+ # :colors => %w(orange purple green white red),
271
+ # :marker_color => 'blue',
272
+ # :background_colors => %w(black grey)
273
+ # }
274
+ #
275
+ # :background_image => 'squirrel.png' is also possible.
276
+ #
277
+ # (Or hopefully something better looking than that.)
278
+ #
279
+ def theme=(options)
280
+ reset_themes()
281
+
282
+ defaults = {
283
+ :colors => ['black', 'white'],
284
+ :additional_line_colors => [],
285
+ :marker_color => 'white',
286
+ :font_color => 'black',
287
+ :background_colors => nil,
288
+ :background_image => nil
289
+ }
290
+ @theme_options = defaults.merge options
291
+
292
+ @colors = @theme_options[:colors]
293
+ @marker_color = @theme_options[:marker_color]
294
+ @font_color = @theme_options[:font_color] || @marker_color
295
+ @additional_line_colors = @theme_options[:additional_line_colors]
296
+
297
+ render_background
298
+ end
299
+
300
+ # A color scheme similar to the popular presentation software.
301
+ def theme_keynote
302
+ # Colors
303
+ @blue = '#6886B4'
304
+ @yellow = '#FDD84E'
305
+ @green = '#72AE6E'
306
+ @red = '#D1695E'
307
+ @purple = '#8A6EAF'
308
+ @orange = '#EFAA43'
309
+ @white = 'white'
310
+ @colors = [@yellow, @blue, @green, @red, @purple, @orange, @white]
311
+
312
+ self.theme = {
313
+ :colors => @colors,
314
+ :marker_color => 'white',
315
+ :font_color => 'white',
316
+ :background_colors => ['black', '#4a465a']
317
+ }
318
+ end
319
+
320
+ # A color scheme plucked from the colors on the popular usability blog.
321
+ def theme_37signals
322
+ # Colors
323
+ @green = '#339933'
324
+ @purple = '#cc99cc'
325
+ @blue = '#336699'
326
+ @yellow = '#FFF804'
327
+ @red = '#ff0000'
328
+ @orange = '#cf5910'
329
+ @black = 'black'
330
+ @colors = [@yellow, @blue, @green, @red, @purple, @orange, @black]
331
+
332
+ self.theme = {
333
+ :colors => @colors,
334
+ :marker_color => 'black',
335
+ :font_color => 'black',
336
+ :background_colors => ['#d1edf5', 'white']
337
+ }
338
+ end
339
+
340
+ # A color scheme from the colors used on the 2005 Rails keynote
341
+ # presentation at RubyConf.
342
+ def theme_rails_keynote
343
+ # Colors
344
+ @green = '#00ff00'
345
+ @grey = '#333333'
346
+ @orange = '#ff5d00'
347
+ @red = '#f61100'
348
+ @white = 'white'
349
+ @light_grey = '#999999'
350
+ @black = 'black'
351
+ @colors = [@green, @grey, @orange, @red, @white, @light_grey, @black]
352
+
353
+ self.theme = {
354
+ :colors => @colors,
355
+ :marker_color => 'white',
356
+ :font_color => 'white',
357
+ :background_colors => ['#0083a3', '#0083a3']
358
+ }
359
+ end
360
+
361
+ # A color scheme similar to that used on the popular podcast site.
362
+ def theme_odeo
363
+ # Colors
364
+ @grey = '#202020'
365
+ @white = 'white'
366
+ @dark_pink = '#a21764'
367
+ @green = '#8ab438'
368
+ @light_grey = '#999999'
369
+ @dark_blue = '#3a5b87'
370
+ @black = 'black'
371
+ @colors = [@grey, @white, @dark_blue, @dark_pink, @green, @light_grey, @black]
372
+
373
+ self.theme = {
374
+ :colors => @colors,
375
+ :marker_color => 'white',
376
+ :font_color => 'white',
377
+ :background_colors => ['#ff47a4', '#ff1f81']
378
+ }
379
+ end
380
+
381
+ # A pastel theme
382
+ def theme_pastel
383
+ # Colors
384
+ @colors = [
385
+ '#a9dada', # blue
386
+ '#aedaa9', # green
387
+ '#daaea9', # peach
388
+ '#dadaa9', # yellow
389
+ '#a9a9da', # dk purple
390
+ '#daaeda', # purple
391
+ '#dadada' # grey
392
+ ]
393
+
394
+ self.theme = {
395
+ :colors => @colors,
396
+ :marker_color => '#aea9a9', # Grey
397
+ :font_color => 'black',
398
+ :background_colors => 'white'
399
+ }
400
+ end
401
+
402
+ # A greyscale theme
403
+ def theme_greyscale
404
+ # Colors
405
+ @colors = [
406
+ '#282828', #
407
+ '#383838', #
408
+ '#686868', #
409
+ '#989898', #
410
+ '#c8c8c8', #
411
+ '#e8e8e8', #
412
+ ]
413
+
414
+ self.theme = {
415
+ :colors => @colors,
416
+ :marker_color => '#aea9a9', # Grey
417
+ :font_color => 'black',
418
+ :background_colors => 'white'
419
+ }
420
+ end
421
+
422
+ # Parameters are an array where the first element is the name of the dataset
423
+ # and the value is an array of values to plot.
424
+ #
425
+ # Can be called multiple times with different datasets for a multi-valued
426
+ # graph.
427
+ #
428
+ # If the color argument is nil, the next color from the default theme will
429
+ # be used.
430
+ #
431
+ # NOTE: If you want to use a preset theme, you must set it before calling
432
+ # data().
433
+ #
434
+ # Example:
435
+ # data("Bart S.", [95, 45, 78, 89, 88, 76], '#ffcc00')
436
+ def data(name, data_points=[], color=nil)
437
+ data_points = Array(data_points) # make sure it's an array
438
+ @data << [name, data_points, (color || increment_color)]
439
+ # Set column count if this is larger than previous counts
440
+ @column_count = (data_points.length > @column_count) ? data_points.length : @column_count
441
+
442
+ # Pre-normalize
443
+ data_points.each_with_index do |data_point, index|
444
+ next if data_point.nil?
445
+
446
+ # Setup max/min so spread starts at the low end of the data points
447
+ if @maximum_value.nil? && @minimum_value.nil?
448
+ @maximum_value = @minimum_value = data_point
449
+ end
450
+
451
+ # TODO Doesn't work with stacked bar graphs
452
+ # Original: @maximum_value = larger_than_max?(data_point, index) ? max(data_point, index) : @maximum_value
453
+ @maximum_value = larger_than_max?(data_point) ? data_point : @maximum_value
454
+ @has_data = true if @maximum_value > 0
455
+
456
+ @minimum_value = less_than_min?(data_point) ? data_point : @minimum_value
457
+ @has_data = true if @minimum_value < 0
458
+ end
459
+ end
460
+
461
+ # Writes the graph to a file. Defaults to 'graph.png'
462
+ #
463
+ # Example:
464
+ # write('graphs/my_pretty_graph.png')
465
+ def write(filename="graph.png")
466
+ draw()
467
+ @base_image.write(filename)
468
+ end
469
+
470
+ # Return the graph as a rendered binary blob.
471
+ def to_blob(fileformat='PNG')
472
+ draw()
473
+ return @base_image.to_blob do
474
+ self.format = fileformat
475
+ end
476
+ end
477
+
478
+
479
+
480
+ protected
481
+
482
+ # Overridden by subclasses to do the actual plotting of the graph.
483
+ #
484
+ # Subclasses should start by calling super() for this method.
485
+ def draw
486
+ make_stacked if @stacked
487
+ setup_drawing
488
+
489
+ debug {
490
+ # Outer margin
491
+ @d.rectangle( @left_margin, @top_margin,
492
+ @raw_columns - @right_margin, @raw_rows - @bottom_margin)
493
+ # Graph area box
494
+ @d.rectangle( @graph_left, @graph_top, @graph_right, @graph_bottom)
495
+ }
496
+ end
497
+
498
+ # Calculates size of drawable area and draws the decorations.
499
+ #
500
+ # * line markers
501
+ # * legend
502
+ # * title
503
+ def setup_drawing
504
+ # Maybe should be done in one of the following functions for more granularity.
505
+ unless @has_data
506
+ draw_no_data()
507
+ return
508
+ end
509
+
510
+ normalize()
511
+ setup_graph_measurements()
512
+ sort_norm_data() if @sort # Sort norm_data with avg largest values set first (for display)
513
+
514
+ draw_legend()
515
+ draw_line_markers()
516
+ draw_axis_labels()
517
+ draw_title
518
+ end
519
+
520
+ # Make copy of data with values scaled between 0-100
521
+ def normalize(force=false)
522
+ if @norm_data.nil? || force
523
+ @norm_data = []
524
+ return unless @has_data
525
+
526
+ calculate_spread
527
+
528
+ @data.each do |data_row|
529
+ norm_data_points = []
530
+ data_row[DATA_VALUES_INDEX].each do |data_point|
531
+ if data_point.nil?
532
+ norm_data_points << nil
533
+ else
534
+ norm_data_points << ((data_point.to_f - @minimum_value.to_f ) / @spread)
535
+ end
536
+ end
537
+ @norm_data << [data_row[DATA_LABEL_INDEX], norm_data_points, data_row[DATA_COLOR_INDEX]]
538
+ end
539
+ end
540
+ end
541
+
542
+ def calculate_spread # :nodoc:
543
+ @spread = @maximum_value.to_f - @minimum_value.to_f
544
+ @spread = @spread > 0 ? @spread : 1
545
+ end
546
+
547
+ ##
548
+ # Calculates size of drawable area, general font dimensions, etc.
549
+
550
+ def setup_graph_measurements
551
+ @marker_caps_height = @hide_line_markers ? 0 :
552
+ calculate_caps_height(@marker_font_size)
553
+ @title_caps_height = @hide_title ? 0 :
554
+ calculate_caps_height(@title_font_size)
555
+ @legend_caps_height = @hide_legend ? 0 :
556
+ calculate_caps_height(@legend_font_size)
557
+
558
+ if @hide_line_markers
559
+ (@graph_left,
560
+ @graph_right_margin,
561
+ @graph_bottom_margin) = [@left_margin, @right_margin, @bottom_margin]
562
+ else
563
+ longest_left_label_width = 0
564
+ if @has_left_labels
565
+ longest_left_label_width = calculate_width(@marker_font_size,
566
+ labels.values.inject('') { |value, memo| (value.to_s.length > memo.to_s.length) ? value : memo }) * 1.25
567
+ else
568
+ longest_left_label_width = calculate_width(@marker_font_size,
569
+ label(@maximum_value.to_f))
570
+ end
571
+
572
+ # Shift graph if left line numbers are hidden
573
+ line_number_width = @hide_line_numbers && !@has_left_labels ?
574
+ 0.0 :
575
+ (longest_left_label_width + LABEL_MARGIN * 2)
576
+
577
+ @graph_left = @left_margin +
578
+ line_number_width +
579
+ (@y_axis_label.nil? ? 0.0 : @marker_caps_height + LABEL_MARGIN * 2)
580
+
581
+ # Make space for half the width of the rightmost column label.
582
+ # Might be greater than the number of columns if between-style bar markers are used.
583
+ last_label = @labels.keys.sort.last.to_i
584
+ extra_room_for_long_label = (last_label >= (@column_count-1) && @center_labels_over_point) ?
585
+ calculate_width(@marker_font_size, @labels[last_label]) / 2.0 :
586
+ 0
587
+ @graph_right_margin = @right_margin + extra_room_for_long_label
588
+
589
+ @graph_bottom_margin = @bottom_margin +
590
+ @marker_caps_height + LABEL_MARGIN
591
+ end
592
+
593
+ @graph_right = @raw_columns - @graph_right_margin
594
+ @graph_width = @raw_columns - @graph_left - @graph_right_margin
595
+
596
+ # When @hide title, leave a TITLE_MARGIN space for aesthetics.
597
+ # Same with @hide_legend
598
+ @graph_top = @top_margin +
599
+ (@hide_title ? TITLE_MARGIN : @title_caps_height + TITLE_MARGIN * 2) +
600
+ (@hide_legend ? LEGEND_MARGIN : @legend_caps_height + LEGEND_MARGIN * 2)
601
+
602
+ x_axis_label_height = @x_axis_label.nil? ? 0.0 :
603
+ @marker_caps_height + LABEL_MARGIN
604
+ @graph_bottom = @raw_rows - @graph_bottom_margin - x_axis_label_height
605
+ @graph_height = @graph_bottom - @graph_top
606
+ end
607
+
608
+ # Draw the optional labels for the x axis and y axis.
609
+ def draw_axis_labels
610
+ unless @x_axis_label.nil?
611
+ # X Axis
612
+ # Centered vertically and horizontally by setting the
613
+ # height to 1.0 and the width to the width of the graph.
614
+ x_axis_label_y_coordinate = @graph_bottom + LABEL_MARGIN * 2 + @marker_caps_height
615
+
616
+ # TODO Center between graph area
617
+ @d.fill = @font_color
618
+ @d.font = @font if @font
619
+ @d.stroke('transparent')
620
+ @d.pointsize = scale_fontsize(@marker_font_size)
621
+ @d.gravity = NorthGravity
622
+ @d = @d.annotate_scaled( @base_image,
623
+ @raw_columns, 1.0,
624
+ 0.0, x_axis_label_y_coordinate,
625
+ @x_axis_label, @scale)
626
+ debug { @d.line 0.0, x_axis_label_y_coordinate, @raw_columns, x_axis_label_y_coordinate }
627
+ end
628
+
629
+ unless @y_axis_label.nil?
630
+ # Y Axis, rotated vertically
631
+ @d.rotation = 90.0
632
+ @d.gravity = CenterGravity
633
+ @d = @d.annotate_scaled( @base_image,
634
+ 1.0, @raw_rows,
635
+ @left_margin + @marker_caps_height / 2.0, 0.0,
636
+ @y_axis_label, @scale)
637
+ @d.rotation = -90.0
638
+ end
639
+ end
640
+
641
+ # Draws horizontal background lines and labels
642
+ def draw_line_markers
643
+ return if @hide_line_markers
644
+
645
+ @d = @d.stroke_antialias false
646
+
647
+ if @y_axis_increment.nil?
648
+ # Try to use a number of horizontal lines that will come out even.
649
+ #
650
+ # TODO Do the same for larger numbers...100, 75, 50, 25
651
+ if @marker_count.nil?
652
+ (3..7).each do |lines|
653
+ if @spread % lines == 0.0
654
+ @marker_count = lines
655
+ break
656
+ end
657
+ end
658
+ @marker_count ||= 4
659
+ end
660
+ @increment = (@spread > 0) ? significant(@spread / @marker_count) : 1
661
+ else
662
+ # TODO Make this work for negative values
663
+ @maximum_value = [@maximum_value.ceil, @y_axis_increment].max
664
+ @minimum_value = @minimum_value.floor
665
+ calculate_spread
666
+ normalize(true)
667
+
668
+ @marker_count = (@spread / @y_axis_increment).to_i
669
+ @increment = @y_axis_increment
670
+ end
671
+ @increment_scaled = @graph_height.to_f / (@spread / @increment)
672
+
673
+ # Draw horizontal line markers and annotate with numbers
674
+ (0..@marker_count).each do |index|
675
+ y = @graph_top + @graph_height - index.to_f * @increment_scaled
676
+
677
+ @d = @d.stroke(@marker_color)
678
+ @d = @d.stroke_width 1
679
+ @d = @d.line(@graph_left, y, @graph_right, y)
680
+
681
+ marker_label = index * @increment + @minimum_value.to_f
682
+
683
+ unless @hide_line_numbers
684
+ @d.fill = @font_color
685
+ @d.font = @font if @font
686
+ @d.stroke('transparent')
687
+ @d.pointsize = scale_fontsize(@marker_font_size)
688
+ @d.gravity = EastGravity
689
+
690
+ # Vertically center with 1.0 for the height
691
+ @d = @d.annotate_scaled( @base_image,
692
+ @graph_left - LABEL_MARGIN, 1.0,
693
+ 0.0, y,
694
+ label(marker_label), @scale)
695
+ end
696
+ end
697
+
698
+ # # Submitted by a contibutor...the utility escapes me
699
+ # i = 0
700
+ # @additional_line_values.each do |value|
701
+ # @increment_scaled = @graph_height.to_f / (@maximum_value.to_f / value)
702
+ #
703
+ # y = @graph_top + @graph_height - @increment_scaled
704
+ #
705
+ # @d = @d.stroke(@additional_line_colors[i])
706
+ # @d = @d.line(@graph_left, y, @graph_right, y)
707
+ #
708
+ #
709
+ # @d.fill = @additional_line_colors[i]
710
+ # @d.font = @font if @font
711
+ # @d.stroke('transparent')
712
+ # @d.pointsize = scale_fontsize(@marker_font_size)
713
+ # @d.gravity = EastGravity
714
+ # @d = @d.annotate_scaled( @base_image,
715
+ # 100, 20,
716
+ # -10, y - (@marker_font_size/2.0),
717
+ # "", @scale)
718
+ # i += 1
719
+ # end
720
+
721
+ @d = @d.stroke_antialias true
722
+ end
723
+
724
+ ##
725
+ # Return the sum of values in an array.
726
+ #
727
+ # Duplicated to not conflict with active_support in Rails.
728
+
729
+ def sum(arr)
730
+ arr.inject(0) { |i, m| m + i }
731
+ end
732
+
733
+ ##
734
+ # Return a calculation of center
735
+
736
+ def center(size)
737
+ (@raw_columns - size) / 2
738
+ end
739
+
740
+ ##
741
+ # Draws a legend with the names of the datasets matched
742
+ # to the colors used to draw them.
743
+
744
+ def draw_legend
745
+ return if @hide_legend
746
+
747
+ @legend_labels = @data.collect {|item| item[DATA_LABEL_INDEX] }
748
+
749
+ legend_square_width = @legend_box_size # small square with color of this item
750
+
751
+ # May fix legend drawing problem at small sizes
752
+ @d.font = @font if @font
753
+ @d.pointsize = @legend_font_size
754
+
755
+ label_widths = [[]] # Used to calculate line wrap
756
+ @legend_labels.each do |label|
757
+ metrics = @d.get_type_metrics(@base_image, label.to_s)
758
+ label_width = metrics.width + legend_square_width * 2.7
759
+ label_widths.last.push label_width
760
+
761
+ if sum(label_widths.last) > (@raw_columns * 0.9)
762
+ label_widths.push [label_widths.last.pop]
763
+ end
764
+ end
765
+
766
+ current_x_offset = center(sum(label_widths.first))
767
+ current_y_offset = @hide_title ?
768
+ @top_margin + LEGEND_MARGIN :
769
+ @top_margin + TITLE_MARGIN + @title_caps_height + LEGEND_MARGIN
770
+
771
+ @legend_labels.each_with_index do |legend_label, index|
772
+
773
+ # Draw label
774
+ @d.fill = @font_color
775
+ @d.font = @font if @font
776
+ @d.pointsize = scale_fontsize(@legend_font_size)
777
+ @d.stroke('transparent')
778
+ @d.font_weight = NormalWeight
779
+ @d.gravity = WestGravity
780
+ @d = @d.annotate_scaled( @base_image,
781
+ @raw_columns, 1.0,
782
+ current_x_offset + (legend_square_width * 1.7), current_y_offset,
783
+ legend_label.to_s, @scale)
784
+
785
+ # Now draw box with color of this dataset
786
+ @d = @d.stroke('transparent')
787
+ @d = @d.fill @data[index][DATA_COLOR_INDEX]
788
+ @d = @d.rectangle(current_x_offset,
789
+ current_y_offset - legend_square_width / 2.0,
790
+ current_x_offset + legend_square_width,
791
+ current_y_offset + legend_square_width / 2.0)
792
+
793
+ @d.pointsize = @legend_font_size
794
+ metrics = @d.get_type_metrics(@base_image, legend_label.to_s)
795
+ current_string_offset = metrics.width + (legend_square_width * 2.7)
796
+
797
+ # Handle wrapping
798
+ label_widths.first.shift
799
+ if label_widths.first.empty?
800
+ debug { @d.line 0.0, current_y_offset, @raw_columns, current_y_offset }
801
+
802
+ label_widths.shift
803
+ current_x_offset = center(sum(label_widths.first)) unless label_widths.empty?
804
+ line_height = [@legend_caps_height, legend_square_width].max + LEGEND_MARGIN
805
+ if label_widths.length > 0
806
+ # Wrap to next line and shrink available graph dimensions
807
+ current_y_offset += line_height
808
+ @graph_top += line_height
809
+ @graph_height = @graph_bottom - @graph_top
810
+ end
811
+ else
812
+ current_x_offset += current_string_offset
813
+ end
814
+ end
815
+ @color_index = 0
816
+ end
817
+
818
+ # Draws a title on the graph.
819
+ def draw_title
820
+ return if (@hide_title || @title.nil?)
821
+
822
+ @d.fill = @font_color
823
+ @d.font = @font if @font
824
+ @d.stroke('transparent')
825
+ @d.pointsize = scale_fontsize(@title_font_size)
826
+ @d.font_weight = BoldWeight
827
+ @d.gravity = NorthGravity
828
+ @d = @d.annotate_scaled( @base_image,
829
+ @raw_columns, 1.0,
830
+ 0, @top_margin,
831
+ @title, @scale)
832
+ end
833
+
834
+ # Draws column labels below graph, centered over x_offset
835
+ #--
836
+ # TODO Allow WestGravity as an option
837
+ def draw_label(x_offset, index)
838
+ return if @hide_line_markers
839
+
840
+ if !@labels[index].nil? && @labels_seen[index].nil?
841
+ y_offset = @graph_bottom + LABEL_MARGIN
842
+
843
+ @d.fill = @font_color
844
+ @d.font = @font if @font
845
+ @d.stroke('transparent')
846
+ @d.font_weight = NormalWeight
847
+ @d.pointsize = scale_fontsize(@marker_font_size)
848
+ @d.gravity = NorthGravity
849
+ @d = @d.annotate_scaled(@base_image,
850
+ 1.0, 1.0,
851
+ x_offset, y_offset,
852
+ @labels[index], @scale)
853
+ @labels_seen[index] = 1
854
+ debug { @d.line 0.0, y_offset, @raw_columns, y_offset }
855
+ end
856
+ end
857
+
858
+ # Shows an error message because you have no data.
859
+ def draw_no_data
860
+ @d.fill = @font_color
861
+ @d.font = @font if @font
862
+ @d.stroke('transparent')
863
+ @d.font_weight = NormalWeight
864
+ @d.pointsize = scale_fontsize(80)
865
+ @d.gravity = CenterGravity
866
+ @d = @d.annotate_scaled( @base_image,
867
+ @raw_columns, @raw_rows/2.0,
868
+ 0, 10,
869
+ @no_data_message, @scale)
870
+ end
871
+
872
+ # Finds the best background to render based on the provided theme options.
873
+ #
874
+ # Creates a @base_image to draw on.
875
+ def render_background
876
+ case @theme_options[:background_colors]
877
+ when Array
878
+ @base_image = render_gradiated_background(*@theme_options[:background_colors])
879
+ when String
880
+ @base_image = render_solid_background(@theme_options[:background_colors])
881
+ else
882
+ @base_image = render_image_background(*@theme_options[:background_image])
883
+ end
884
+ end
885
+
886
+ # Make a new image at the current size with a solid +color+.
887
+ def render_solid_background(color)
888
+ Image.new(@columns, @rows) {
889
+ self.background_color = color
890
+ }
891
+ end
892
+
893
+ # Use with a theme definition method to draw a gradiated background.
894
+ def render_gradiated_background(top_color, bottom_color)
895
+ Image.new(@columns, @rows,
896
+ GradientFill.new(0, 0, 100, 0, top_color, bottom_color))
897
+ end
898
+
899
+ # Use with a theme to use an image (800x600 original) background.
900
+ def render_image_background(image_path)
901
+ image = Image.read(image_path)
902
+ if @scale != 1.0
903
+ image[0].resize!(@scale) # TODO Resize with new scale (crop if necessary for wide graph)
904
+ end
905
+ image[0]
906
+ end
907
+
908
+ # Use with a theme to make a transparent background
909
+ def render_transparent_background
910
+ Image.new(@columns, @rows) do
911
+ self.background_color = 'transparent'
912
+ end
913
+ end
914
+
915
+ # Resets everything to defaults (except data).
916
+ def reset_themes
917
+ @color_index = 0
918
+ @labels_seen = {}
919
+ @theme_options = {}
920
+
921
+ @d = Draw.new
922
+ # Scale down from 800x600 used to calculate drawing.
923
+ @d = @d.scale(@scale, @scale)
924
+ end
925
+
926
+ def scale(value) # :nodoc:
927
+ value * @scale
928
+ end
929
+
930
+ # Return a comparable fontsize for the current graph.
931
+ def scale_fontsize(value)
932
+ new_fontsize = value * @scale
933
+ # return new_fontsize < 10.0 ? 10.0 : new_fontsize
934
+ return new_fontsize
935
+ end
936
+
937
+ def clip_value_if_greater_than(value, max_value) # :nodoc:
938
+ (value > max_value) ? max_value : value
939
+ end
940
+
941
+ # Overridden by subclasses such as stacked bar.
942
+ def larger_than_max?(data_point, index=0) # :nodoc:
943
+ data_point > @maximum_value
944
+ end
945
+
946
+ def less_than_min?(data_point, index=0) # :nodoc:
947
+ data_point < @minimum_value
948
+ end
949
+
950
+ # Overridden by subclasses that need it.
951
+ def max(data_point, index) # :nodoc:
952
+ data_point
953
+ end
954
+
955
+ # Overridden by subclasses that need it.
956
+ def min(data_point, index) # :nodoc:
957
+ data_point
958
+ end
959
+
960
+ def significant(inc) # :nodoc:
961
+ return 1.0 if inc == 0 # Keep from going into infinite loop
962
+ factor = 1.0
963
+ while (inc < 10)
964
+ inc *= 10
965
+ factor /= 10
966
+ end
967
+
968
+ while (inc > 100)
969
+ inc /= 10
970
+ factor *= 10
971
+ end
972
+
973
+ res = inc.floor * factor
974
+ if (res.to_i.to_f == res)
975
+ res.to_i
976
+ else
977
+ res
978
+ end
979
+ end
980
+
981
+ # Sort with largest overall summed value at front of array so it shows up
982
+ # correctly in the drawn graph.
983
+ def sort_norm_data
984
+ @norm_data.sort! { |a,b| sums(b[DATA_VALUES_INDEX]) <=> sums(a[DATA_VALUES_INDEX]) }
985
+ end
986
+
987
+ def sums(data_set) # :nodoc:
988
+ total_sum = 0
989
+ data_set.collect {|num| total_sum += num.to_f }
990
+ total_sum
991
+ end
992
+
993
+ # Used by StackedBar and child classes.
994
+ #
995
+ # May need to be moved to the StackedBar class.
996
+ def get_maximum_by_stack
997
+ # Get sum of each stack
998
+ max_hash = {}
999
+ @data.each do |data_set|
1000
+ data_set[DATA_VALUES_INDEX].each_with_index do |data_point, i|
1001
+ max_hash[i] = 0.0 unless max_hash[i]
1002
+ max_hash[i] += data_point.to_f
1003
+ end
1004
+ end
1005
+
1006
+ # @maximum_value = 0
1007
+ max_hash.keys.each do |key|
1008
+ @maximum_value = max_hash[key] if max_hash[key] > @maximum_value
1009
+ end
1010
+ @minimum_value = 0
1011
+ end
1012
+
1013
+ def make_stacked # :nodoc:
1014
+ stacked_values = Array.new(@column_count, 0)
1015
+ @data.each do |value_set|
1016
+ value_set[DATA_VALUES_INDEX].each_with_index do |value, index|
1017
+ stacked_values[index] += value
1018
+ end
1019
+ value_set[DATA_VALUES_INDEX] = stacked_values.dup
1020
+ end
1021
+ end
1022
+
1023
+ private
1024
+
1025
+ # Takes a block and draws it if DEBUG is true.
1026
+ #
1027
+ # Example:
1028
+ # debug { @d.rectangle x1, y1, x2, y2 }
1029
+ def debug
1030
+ if DEBUG
1031
+ @d = @d.fill 'transparent'
1032
+ @d = @d.stroke 'turquoise'
1033
+ @d = yield
1034
+ end
1035
+ end
1036
+
1037
+ # Uses the next color in your color list.
1038
+ def increment_color
1039
+ if @color_index == 0
1040
+ @color_index += 1
1041
+ return @colors[0]
1042
+ else
1043
+ if @color_index < @colors.length
1044
+ @color_index += 1
1045
+ return @colors[@color_index - 1]
1046
+ else
1047
+ # Start over
1048
+ @color_index = 0
1049
+ return @colors[-1]
1050
+ end
1051
+ end
1052
+ end
1053
+
1054
+ # Return a formatted string representing a number value that should be
1055
+ # printed as a label.
1056
+ def label(value)
1057
+ if (@spread.to_f % @marker_count.to_f == 0) || !@y_axis_increment.nil?
1058
+ return value.to_i.to_s
1059
+ end
1060
+
1061
+ if @spread > 10.0
1062
+ sprintf("%0i", value)
1063
+ elsif @spread >= 3.0
1064
+ sprintf("%0.2f", value)
1065
+ else
1066
+ value.to_s
1067
+ end
1068
+ end
1069
+
1070
+ # Returns the height of the capital letter 'X' for the current font and
1071
+ # size.
1072
+ #
1073
+ # Not scaled since it deals with dimensions that the regular scaling will
1074
+ # handle.
1075
+ def calculate_caps_height(font_size)
1076
+ @d.pointsize = font_size
1077
+ @d.get_type_metrics(@base_image, 'X').height
1078
+ end
1079
+
1080
+ # Returns the width of a string at this pointsize.
1081
+ #
1082
+ # Not scaled since it deals with dimensions that the regular
1083
+ # scaling will handle.
1084
+ def calculate_width(font_size, text)
1085
+ @d.pointsize = font_size
1086
+ @d.get_type_metrics(@base_image, text.to_s).width
1087
+ end
1088
+
1089
+ end # Gruff::Base
1090
+
1091
+ class IncorrectNumberOfDatasetsException < StandardError; end
1092
+
1093
+ end # Gruff
1094
+
1095
+ module Magick
1096
+
1097
+ class Draw
1098
+
1099
+ # Additional method to scale annotation text since Draw.scale doesn't.
1100
+ def annotate_scaled(img, width, height, x, y, text, scale)
1101
+ scaled_width = (width * scale) >= 1 ? (width * scale) : 1
1102
+ scaled_height = (height * scale) >= 1 ? (height * scale) : 1
1103
+
1104
+ self.annotate( img,
1105
+ scaled_width, scaled_height,
1106
+ x * scale, y * scale,
1107
+ text)
1108
+ end
1109
+
1110
+ end
1111
+
1112
+ end # Magick