gruffy 0.0.2 → 0.0.3

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.
@@ -0,0 +1,46 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+
3
+ class Gruffy::Bezier < Gruffy::Base
4
+ def draw
5
+ super
6
+
7
+ return unless @has_data
8
+
9
+ @x_increment = @graph_width / (@column_count - 1).to_f
10
+
11
+ @norm_data.each do |data_row|
12
+ poly_points = Array.new
13
+ @d = @d.fill data_row[DATA_COLOR_INDEX]
14
+
15
+ data_row[1].each_with_index do |data_point, index|
16
+ # Use incremented x and scaled y
17
+ new_x = @graph_left + (@x_increment * index)
18
+ new_y = @graph_top + (@graph_height - data_point * @graph_height)
19
+
20
+ if index == 0 && RUBY_PLATFORM != 'java'
21
+ poly_points << new_x
22
+ poly_points << new_y
23
+ end
24
+
25
+ poly_points << new_x
26
+ poly_points << new_y
27
+
28
+ draw_label(new_x, index)
29
+ end
30
+
31
+ @d = @d.fill_opacity 0.0
32
+ @d = @d.stroke data_row[DATA_COLOR_INDEX]
33
+ @d = @d.stroke_width clip_value_if_greater_than(@columns / (@norm_data.first[1].size * 4), 5.0)
34
+
35
+ if RUBY_PLATFORM == 'java'
36
+ @d = @d.polyline(*poly_points)
37
+ else
38
+ @d = @d.bezier(*poly_points)
39
+ end
40
+ end
41
+
42
+ @d.draw(@base_image)
43
+ end
44
+
45
+
46
+ end
@@ -0,0 +1,111 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+ require 'gruffy/themes'
3
+
4
+ # http://en.wikipedia.org/wiki/Bullet_graph
5
+ class Gruffy::Bullet < Gruffy::Base
6
+
7
+ def initialize(target_width="400x40")
8
+ if not Numeric === target_width
9
+ geometric_width, geometric_height = target_width.split('x')
10
+ @columns = geometric_width.to_f
11
+ @rows = geometric_height.to_f
12
+ else
13
+ @columns = target_width.to_f
14
+ @rows = target_width.to_f / 5.0
15
+ end
16
+
17
+ initialize_ivars
18
+
19
+ reset_themes
20
+ self.theme = Gruffy::Themes::GREYSCALE
21
+ @title_font_size = 20
22
+ end
23
+
24
+ def data(value, maximum_value, options={})
25
+ @value = value.to_f
26
+ @maximum_value = maximum_value.to_f
27
+ @options = options
28
+ @options.map { |k, v| @options[k] = v.to_f if v === Numeric }
29
+ end
30
+
31
+ # def setup_drawing
32
+ # # Maybe should be done in one of the following functions for more granularity.
33
+ # unless @has_data
34
+ # draw_no_data()
35
+ # return
36
+ # end
37
+ #
38
+ # normalize()
39
+ # setup_graph_measurements()
40
+ # sort_norm_data() if @sort # Sort norm_data with avg largest values set first (for display)
41
+ #
42
+ # draw_legend()
43
+ # draw_line_markers()
44
+ # draw_axis_labels()
45
+ # draw_title
46
+ # end
47
+
48
+ def draw
49
+ # TODO Left label
50
+ # TODO Bottom labels and markers
51
+ # @graph_bottom
52
+ # Calculations are off 800x???
53
+
54
+ @colors.reverse!
55
+
56
+ draw_title
57
+
58
+ @margin = 30.0
59
+ @thickness = @raw_rows / 6.0
60
+ @right_margin = @margin
61
+ @graph_left = (@title && (@title_width * 1.3)) || @margin
62
+ @graph_width = @raw_columns - @graph_left - @right_margin
63
+ @graph_height = @thickness * 3.0
64
+
65
+ # Background
66
+ @d = @d.fill @colors[0]
67
+ @d = @d.rectangle(@graph_left, 0, @graph_left + @graph_width, @graph_height)
68
+
69
+ [:high, :low].each_with_index do |indicator, index|
70
+ next unless @options.has_key?(indicator)
71
+ @d = @d.fill @colors[index + 1]
72
+ indicator_width_x = @graph_left + @graph_width * (@options[indicator] / @maximum_value)
73
+ @d = @d.rectangle(@graph_left, 0, indicator_width_x, @graph_height)
74
+ end
75
+
76
+ if @options.has_key?(:target)
77
+ @d = @d.fill @font_color
78
+ target_x = @graph_left + @graph_width * (@options[:target] / @maximum_value)
79
+ half_thickness = @thickness / 2.0
80
+ @d = @d.rectangle(target_x, half_thickness, target_x + half_thickness, @thickness * 2 + half_thickness)
81
+ end
82
+
83
+ # Value
84
+ @d = @d.fill @font_color
85
+ @d = @d.rectangle(@graph_left, @thickness, @graph_left + @graph_width * (@value / @maximum_value), @thickness * 2)
86
+
87
+ @d.draw(@base_image)
88
+ end
89
+
90
+ def draw_title
91
+ return unless @title
92
+
93
+ @font_height = calculate_caps_height(scale_fontsize(@title_font_size))
94
+ @title_width = calculate_width(@title_font_size, @title)
95
+
96
+ @d.fill = @font_color
97
+ @d.font = @font if @font
98
+ @d.stroke('transparent')
99
+ @d.font_weight = NormalWeight
100
+ @d.pointsize = scale_fontsize(@title_font_size)
101
+ @d.gravity = NorthWestGravity
102
+ @d = @d.annotate_scaled(*[
103
+ @base_image,
104
+ 1.0, 1.0,
105
+ @font_height/2, @font_height/2,
106
+ @title,
107
+ @scale
108
+ ])
109
+ end
110
+
111
+ end
@@ -0,0 +1,39 @@
1
+
2
+ ##
3
+ # A mixin for methods that need to be deleted or have been
4
+ # replaced by cleaner code.
5
+
6
+ module Gruffy
7
+ module Deprecated
8
+
9
+ def scale_measurements
10
+ setup_graph_measurements
11
+ end
12
+
13
+ def total_height
14
+ @rows + 10
15
+ end
16
+
17
+ def graph_top
18
+ @graph_top * @scale
19
+ end
20
+
21
+ def graph_height
22
+ @graph_height * @scale
23
+ end
24
+
25
+ def graph_left
26
+ @graph_left * @scale
27
+ end
28
+
29
+ def graph_width
30
+ @graph_width * @scale
31
+ end
32
+
33
+ # TODO Should be calculate_graph_height
34
+ # def setup_graph_height
35
+ # @graph_height = @graph_bottom - @graph_top
36
+ # end
37
+
38
+ end
39
+ end
@@ -0,0 +1,125 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+
3
+ ##
4
+ # Graph with dots and labels along a vertical access
5
+ # see: 'Creating More Effective Graphs' by Robbins
6
+
7
+ class Gruffy::Dot < Gruffy::Base
8
+
9
+ def draw
10
+ @has_left_labels = true
11
+ super
12
+
13
+ return unless @has_data
14
+
15
+ # Setup spacing.
16
+ #
17
+ spacing_factor = 1.0
18
+
19
+ @items_width = @graph_height / @column_count.to_f
20
+ @item_width = @items_width * spacing_factor / @norm_data.size
21
+ @d = @d.stroke_opacity 0.0
22
+ padding = (@items_width * (1 - spacing_factor)) / 2
23
+
24
+ @norm_data.each_with_index do |data_row, row_index|
25
+ data_row[DATA_VALUES_INDEX].each_with_index do |data_point, point_index|
26
+ x_pos = @graph_left + (data_point * @graph_width)
27
+ y_pos = @graph_top + (@items_width * point_index) + padding + (@items_width.to_f/2.0).round
28
+
29
+ if row_index == 0
30
+ @d = @d.stroke(@marker_color)
31
+ @d = @d.fill(@marker_color)
32
+ @d = @d.stroke_width 1.0
33
+ @d = @d.stroke_opacity 0.1
34
+ @d = @d.fill_opacity 0.1
35
+ @d = @d.line(@graph_left, y_pos, @graph_left + @graph_width, y_pos)
36
+ @d = @d.fill_opacity 1
37
+ end
38
+
39
+ @d = @d.fill data_row[DATA_COLOR_INDEX]
40
+ @d = @d.stroke('transparent')
41
+ @d = @d.circle(x_pos, y_pos, x_pos + (@item_width.to_f/3.0).round, y_pos)
42
+
43
+ draw_label(y_pos, point_index)
44
+ end
45
+
46
+ end
47
+
48
+ @d.draw(@base_image)
49
+ end
50
+
51
+ protected
52
+
53
+ # Instead of base class version, draws vertical background lines and label
54
+ def draw_line_markers
55
+ return if @hide_line_markers
56
+
57
+ @d = @d.stroke_antialias false
58
+
59
+ # Draw horizontal line markers and annotate with numbers
60
+ @d = @d.stroke(@marker_color)
61
+ @d = @d.stroke_width 1
62
+ if @y_axis_increment
63
+ increment = @y_axis_increment
64
+ number_of_lines = (@spread / @y_axis_increment).to_i
65
+ else
66
+ # Try to use a number of horizontal lines that will come out even.
67
+ #
68
+ # TODO Do the same for larger numbers...100, 75, 50, 25
69
+ if @marker_count.nil?
70
+ (3..7).each do |lines|
71
+ if @spread % lines == 0.0
72
+ @marker_count = lines
73
+ break
74
+ end
75
+ end
76
+ @marker_count ||= 5
77
+ end
78
+ # TODO Round maximum marker value to a round number like 100, 0.1, 0.5, etc.
79
+ @increment = (@spread > 0 && @marker_count > 0) ? significant(@spread / @marker_count) : 1
80
+
81
+ number_of_lines = @marker_count
82
+ increment = @increment
83
+ end
84
+
85
+ (0..number_of_lines).each do |index|
86
+ marker_label = @minimum_value + index * increment
87
+ x = @graph_left + (marker_label - @minimum_value) * @graph_width / @spread
88
+ @d = @d.line(x, @graph_bottom, x, @graph_bottom + 0.5 * LABEL_MARGIN)
89
+
90
+ unless @hide_line_numbers
91
+ @d.fill = @font_color
92
+ @d.font = @font if @font
93
+ @d.stroke = 'transparent'
94
+ @d.pointsize = scale_fontsize(@marker_font_size)
95
+ @d.gravity = CenterGravity
96
+ # TODO Center text over line
97
+ @d = @d.annotate_scaled(@base_image,
98
+ 0, 0, # Width of box to draw text in
99
+ x, @graph_bottom + (LABEL_MARGIN * 2.0), # Coordinates of text
100
+ label(marker_label, increment), @scale)
101
+ end # unless
102
+ @d = @d.stroke_antialias true
103
+ end
104
+ end
105
+
106
+ ##
107
+ # Draw on the Y axis instead of the X
108
+
109
+ def draw_label(y_offset, index)
110
+ if !@labels[index].nil? && @labels_seen[index].nil?
111
+ @d.fill = @font_color
112
+ @d.font = @font if @font
113
+ @d.stroke = 'transparent'
114
+ @d.font_weight = NormalWeight
115
+ @d.pointsize = scale_fontsize(@marker_font_size)
116
+ @d.gravity = EastGravity
117
+ @d = @d.annotate_scaled(@base_image,
118
+ 1, 1,
119
+ -@graph_left + LABEL_MARGIN * 2.0, y_offset,
120
+ @labels[index], @scale)
121
+ @labels_seen[index] = 1
122
+ end
123
+ end
124
+
125
+ end
@@ -0,0 +1,365 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+
3
+ ##
4
+ # Here's how to make a Line graph:
5
+ #
6
+ # g = Gruffy::Line.new
7
+ # g.title = "A Line Graph"
8
+ # g.data 'Fries', [20, 23, 19, 8]
9
+ # g.data 'Hamburgers', [50, 19, 99, 29]
10
+ # g.write("test/output/line.png")
11
+ #
12
+ # There are also other options described below, such as #baseline_value, #baseline_color, #hide_dots, and #hide_lines.
13
+
14
+ class Gruffy::Line < Gruffy::Base
15
+
16
+ # Allow for reference lines ( which are like baseline ... just allowing for more & on both axes )
17
+ attr_accessor :reference_lines
18
+ attr_accessor :reference_line_default_color
19
+ attr_accessor :reference_line_default_width
20
+
21
+ # Allow for vertical marker lines
22
+ attr_accessor :show_vertical_markers
23
+
24
+ # Dimensions of lines and dots; calculated based on dataset size if left unspecified
25
+ attr_accessor :line_width
26
+ attr_accessor :dot_radius
27
+
28
+ # default is a circle, other options include square
29
+ attr_accessor :dot_style
30
+
31
+ # Hide parts of the graph to fit more datapoints, or for a different appearance.
32
+ attr_accessor :hide_dots, :hide_lines
33
+
34
+ #accessors for support of xy data
35
+ attr_accessor :minimum_x_value
36
+ attr_accessor :maximum_x_value
37
+
38
+ # Get the value if somebody has defined it.
39
+ def baseline_value
40
+ if (@reference_lines.key?(:baseline))
41
+ @reference_lines[:baseline][:value]
42
+ else
43
+ nil
44
+ end
45
+ end
46
+
47
+ # Set a value for a baseline reference line..
48
+ def baseline_value=(new_value)
49
+ @reference_lines[:baseline] ||= Hash.new
50
+ @reference_lines[:baseline][:value] = new_value
51
+ end
52
+
53
+ def baseline_color
54
+ if (@reference_lines.key?(:baseline))
55
+ @reference_lines[:baseline][:color]
56
+ else
57
+ nil
58
+ end
59
+ end
60
+
61
+ def baseline_color=(new_value)
62
+ @reference_lines[:baseline] ||= Hash.new
63
+ @reference_lines[:baseline][:color] = new_value
64
+ end
65
+
66
+ # Call with target pixel width of graph (800, 400, 300), and/or 'false' to omit lines (points only).
67
+ #
68
+ # g = Gruffy::Line.new(400) # 400px wide with lines
69
+ #
70
+ # g = Gruffy::Line.new(400, false) # 400px wide, no lines (for backwards compatibility)
71
+ #
72
+ # g = Gruffy::Line.new(false) # Defaults to 800px wide, no lines (for backwards compatibility)
73
+ #
74
+ # The preferred way is to call hide_dots or hide_lines instead.
75
+ def initialize(*args)
76
+ raise ArgumentError, 'Wrong number of arguments' if args.length > 2
77
+ if args.empty? || ((not Numeric === args.first) && (not String === args.first))
78
+ super()
79
+ else
80
+ super args.shift
81
+ end
82
+
83
+ @reference_lines = Hash.new
84
+ @reference_line_default_color = 'red'
85
+ @reference_line_default_width = 5
86
+
87
+ @hide_dots = @hide_lines = false
88
+ @maximum_x_value = nil
89
+ @minimum_x_value = nil
90
+
91
+ @dot_style = 'circle'
92
+
93
+ @show_vertical_markers = false
94
+ end
95
+
96
+ # This method allows one to plot a dataset with both X and Y data.
97
+ #
98
+ # Parameters are as follows:
99
+ # name: string, the title of the dataset
100
+ # x_data_points: an array containing the x data points for the graph
101
+ # y_data_points: an array containing the y data points for the graph
102
+ # color: hex number indicating the line color as an RGB triplet
103
+ #
104
+ # or
105
+ #
106
+ # name: string, the title of the dataset
107
+ # xy_data_points: an array containing both x and y data points for the graph
108
+ # color: hex number indicating the line color as an RGB triplet
109
+ #
110
+ # Notes:
111
+ # -if (x_data_points.length != y_data_points.length) an error is
112
+ # returned.
113
+ # -if the color argument is nil, the next color from the default theme will
114
+ # be used.
115
+ # -if you want to use a preset theme, you must set it before calling
116
+ # dataxy().
117
+ #
118
+ # Example:
119
+ # g = Gruffy::Line.new
120
+ # g.title = "X/Y Dataset"
121
+ # g.dataxy("Apples", [1,3,4,5,6,10], [1, 2, 3, 4, 4, 3])
122
+ # g.dataxy("Bapples", [1,3,4,5,7,9], [1, 1, 2, 2, 3, 3])
123
+ # g.dataxy("Capples", [[1,1],[2,3],[3,4],[4,5],[5,7],[6,9]])
124
+ # #you can still use the old data method too if you want:
125
+ # g.data("Capples", [1, 1, 2, 2, 3, 3])
126
+ # #labels will be drawn at the x locations of the keys passed in.
127
+ # In this example the lables are drawn at x positions 2, 4, and 6:
128
+ # g.labels = {0 => '2003', 2 => '2004', 4 => '2005', 6 => '2006'}
129
+ # The 0 => '2003' label will be ignored since it is outside the chart range.
130
+ def dataxy(name, x_data_points=[], y_data_points=[], color=nil)
131
+ raise ArgumentError, 'x_data_points is nil!' if x_data_points.length == 0
132
+
133
+ if x_data_points.all? { |p| p.is_a?(Array) && p.size == 2 }
134
+ x_data_points, y_data_points = x_data_points.map { |p| p[0] }, x_data_points.map { |p| p[1] }
135
+ end
136
+
137
+ raise ArgumentError, 'x_data_points.length != y_data_points.length!' if x_data_points.length != y_data_points.length
138
+
139
+ # call the existing data routine for the y data.
140
+ self.data(name, y_data_points, color)
141
+
142
+ x_data_points = Array(x_data_points) # make sure it's an array
143
+ # append the x data to the last entry that was just added in the @data member
144
+ @data.last[DATA_VALUES_X_INDEX] = x_data_points
145
+
146
+ # Update the global min/max values for the x data
147
+ x_data_points.each do |x_data_point|
148
+ next if x_data_point.nil?
149
+
150
+ # Setup max/min so spread starts at the low end of the data points
151
+ if @maximum_x_value.nil? && @minimum_x_value.nil?
152
+ @maximum_x_value = @minimum_x_value = x_data_point
153
+ end
154
+
155
+ @maximum_x_value = (x_data_point > @maximum_x_value) ?
156
+ x_data_point : @maximum_x_value
157
+ @minimum_x_value = (x_data_point < @minimum_x_value) ?
158
+ x_data_point : @minimum_x_value
159
+ end
160
+
161
+ end
162
+
163
+ def draw_reference_line(reference_line, left, right, top, bottom)
164
+ @d = @d.push
165
+ @d.stroke_color(reference_line[:color] || @reference_line_default_color)
166
+ @d.fill_opacity 0.0
167
+ @d.stroke_dasharray(10, 20)
168
+ @d.stroke_width(reference_line[:width] || @reference_line_default_width)
169
+ @d.line(left, top, right, bottom)
170
+ @d = @d.pop
171
+ end
172
+
173
+ def draw_horizontal_reference_line(reference_line)
174
+ level = @graph_top + (@graph_height - reference_line[:norm_value] * @graph_height)
175
+ draw_reference_line(reference_line, @graph_left, @graph_left + @graph_width, level, level)
176
+ end
177
+
178
+ def draw_vertical_reference_line(reference_line)
179
+ index = @graph_left + (@x_increment * reference_line[:index])
180
+ draw_reference_line(reference_line, index, index, @graph_top, @graph_top + @graph_height)
181
+ end
182
+
183
+ def draw
184
+ super
185
+
186
+ return unless @has_data
187
+
188
+ # Check to see if more than one datapoint was given. NaN can result otherwise.
189
+ @x_increment = (@column_count > 1) ? (@graph_width / (@column_count - 1).to_f) : @graph_width
190
+
191
+ @reference_lines.each_value do |curr_reference_line|
192
+ draw_horizontal_reference_line(curr_reference_line) if curr_reference_line.key?(:norm_value)
193
+ draw_vertical_reference_line(curr_reference_line) if curr_reference_line.key?(:index)
194
+ end
195
+
196
+ if (@show_vertical_markers)
197
+ (0..@column_count).each do |column|
198
+ x = @graph_left + @graph_width - column.to_f * @x_increment
199
+
200
+ @d = @d.fill(@marker_color)
201
+
202
+ # FIXME(uwe): Workaround for Issue #66
203
+ # https://github.com/topfunky/gruffy/issues/66
204
+ # https://github.com/rmagick/rmagick/issues/82
205
+ # Remove if the issue gets fixed.
206
+ x += 0.001 unless defined?(JRUBY_VERSION)
207
+ # EMXIF
208
+
209
+ @d = @d.line(x, @graph_bottom, x, @graph_top)
210
+ #If the user specified a marker shadow color, draw a shadow just below it
211
+ unless @marker_shadow_color.nil?
212
+ @d = @d.fill(@marker_shadow_color)
213
+ @d = @d.line(x + 1, @graph_bottom, x + 1, @graph_top)
214
+ end
215
+ end
216
+ end
217
+
218
+ @norm_data.each do |data_row|
219
+ prev_x = prev_y = nil
220
+
221
+ @one_point = contains_one_point_only?(data_row)
222
+
223
+ data_row[DATA_VALUES_INDEX].each_with_index do |data_point, index|
224
+ x_data = data_row[DATA_VALUES_X_INDEX]
225
+ if x_data == nil
226
+ #use the old method: equally spaced points along the x-axis
227
+ new_x = @graph_left + (@x_increment * index)
228
+ draw_label(new_x, index)
229
+ else
230
+ new_x = get_x_coord(x_data[index], @graph_width, @graph_left)
231
+ @labels.each do |label_pos, _|
232
+ draw_label(@graph_left + ((label_pos - @minimum_x_value) * @graph_width) / (@maximum_x_value - @minimum_x_value), label_pos)
233
+ end
234
+ end
235
+ unless data_point # we can't draw a line for a null data point, we can still label the axis though
236
+ prev_x = prev_y = nil
237
+ next
238
+ end
239
+
240
+ new_y = @graph_top + (@graph_height - data_point * @graph_height)
241
+
242
+ # Reset each time to avoid thin-line errors
243
+ @d = @d.stroke data_row[DATA_COLOR_INDEX]
244
+ @d = @d.fill data_row[DATA_COLOR_INDEX]
245
+ @d = @d.stroke_opacity 1.0
246
+ @d = @d.stroke_width line_width ||
247
+ clip_value_if_greater_than(@columns / (@norm_data.first[DATA_VALUES_INDEX].size * 4), 5.0)
248
+
249
+ circle_radius = dot_radius ||
250
+ clip_value_if_greater_than(@columns / (@norm_data.first[DATA_VALUES_INDEX].size * 2.5), 5.0)
251
+
252
+ if !@hide_lines && !prev_x.nil? && !prev_y.nil?
253
+ @d = @d.line(prev_x, prev_y, new_x, new_y)
254
+ elsif @one_point
255
+ # Show a circle if there's just one_point
256
+ @d = DotRenderers.renderer(@dot_style).render(@d, new_x, new_y, circle_radius)
257
+ end
258
+
259
+ unless @hide_dots
260
+ @d = DotRenderers.renderer(@dot_style).render(@d, new_x, new_y, circle_radius)
261
+ end
262
+
263
+ prev_x, prev_y = new_x, new_y
264
+ end
265
+ end
266
+
267
+ @d.draw(@base_image)
268
+ end
269
+
270
+ def setup_data
271
+
272
+ # Deal with horizontal reference line values that exceed the existing minimum & maximum values.
273
+ possible_maximums = [@maximum_value.to_f]
274
+ possible_minimums = [@minimum_value.to_f]
275
+
276
+ @reference_lines.each_value do |curr_reference_line|
277
+ if (curr_reference_line.key?(:value))
278
+ possible_maximums << curr_reference_line[:value].to_f
279
+ possible_minimums << curr_reference_line[:value].to_f
280
+ end
281
+ end
282
+
283
+ @maximum_value = possible_maximums.max
284
+ @minimum_value = possible_minimums.min
285
+
286
+ super
287
+ end
288
+
289
+ def normalize(force=false)
290
+ super(force)
291
+
292
+ @reference_lines.each_value do |curr_reference_line|
293
+
294
+ # We only care about horizontal markers ... for normalization.
295
+ # Vertical markers won't have a :value, they will have an :index
296
+
297
+ curr_reference_line[:norm_value] = ((curr_reference_line[:value].to_f - @minimum_value) / @spread.to_f) if (curr_reference_line.key?(:value))
298
+
299
+ end
300
+
301
+ #normalize the x data if it is specified
302
+ @data.each_with_index do |data_row, index|
303
+ norm_x_data_points = []
304
+ if data_row[DATA_VALUES_X_INDEX] != nil
305
+ data_row[DATA_VALUES_X_INDEX].each do |x_data_point|
306
+ norm_x_data_points << ((x_data_point.to_f - @minimum_x_value.to_f) /
307
+ (@maximum_x_value.to_f - @minimum_x_value.to_f))
308
+ end
309
+ @norm_data[index] << norm_x_data_points
310
+ end
311
+ end
312
+
313
+ end
314
+
315
+ def sort_norm_data
316
+ super unless @data.any? { |d| d[DATA_VALUES_X_INDEX] }
317
+ end
318
+
319
+ def get_x_coord(x_data_point, width, offset)
320
+ x_data_point * width + offset
321
+ end
322
+
323
+ def contains_one_point_only?(data_row)
324
+ # Spin through data to determine if there is just one_value present.
325
+ one_point = false
326
+ data_row[DATA_VALUES_INDEX].each do |data_point|
327
+ unless data_point.nil?
328
+ if one_point
329
+ # more than one point, bail
330
+ return false
331
+ end
332
+ # there is at least one data point
333
+ one_point = true
334
+ end
335
+ end
336
+ one_point
337
+ end
338
+
339
+ module DotRenderers
340
+ class Circle
341
+ def render(d, new_x, new_y, circle_radius)
342
+ d.circle(new_x, new_y, new_x - circle_radius, new_y)
343
+ end
344
+ end
345
+
346
+ class Square
347
+ def render(d, new_x, new_y, circle_radius)
348
+ offset = (circle_radius * 0.8).to_i
349
+ corner_1 = new_x - offset
350
+ corner_2 = new_y - offset
351
+ corner_3 = new_x + offset
352
+ corner_4 = new_y + offset
353
+ d.rectangle(corner_1, corner_2, corner_3, corner_4)
354
+ end
355
+ end
356
+
357
+ def self.renderer(style)
358
+ if style.to_s == 'square'
359
+ Square.new
360
+ else
361
+ Circle.new
362
+ end
363
+ end
364
+ end
365
+ end