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.
- checksums.yaml +4 -4
- data/lib/gruffy.rb +32 -0
- data/lib/gruffy/accumulator_bar.rb +18 -0
- data/lib/gruffy/area.rb +51 -0
- data/lib/gruffy/bar.rb +108 -0
- data/lib/gruffy/bar_conversion.rb +46 -0
- data/lib/gruffy/base.rb +1201 -0
- data/lib/gruffy/bezier.rb +46 -0
- data/lib/gruffy/bullet.rb +111 -0
- data/lib/gruffy/deprecated.rb +39 -0
- data/lib/gruffy/dot.rb +125 -0
- data/lib/gruffy/line.rb +365 -0
- data/lib/gruffy/mini/bar.rb +37 -0
- data/lib/gruffy/mini/legend.rb +114 -0
- data/lib/gruffy/mini/pie.rb +36 -0
- data/lib/gruffy/mini/side_bar.rb +35 -0
- data/lib/gruffy/net.rb +127 -0
- data/lib/gruffy/photo_bar.rb +100 -0
- data/lib/gruffy/pie.rb +271 -0
- data/lib/gruffy/scatter.rb +314 -0
- data/lib/gruffy/scene.rb +209 -0
- data/lib/gruffy/side_bar.rb +138 -0
- data/lib/gruffy/side_stacked_bar.rb +97 -0
- data/lib/gruffy/spider.rb +125 -0
- data/lib/gruffy/stacked_area.rb +67 -0
- data/lib/gruffy/stacked_bar.rb +61 -0
- data/lib/gruffy/stacked_mixin.rb +23 -0
- data/lib/gruffy/themes.rb +102 -0
- data/lib/gruffy/version.rb +3 -0
- data/rails_generators/gruffy/gruffy_generator.rb +63 -0
- data/rails_generators/gruffy/templates/controller.rb +32 -0
- data/rails_generators/gruffy/templates/functional_test.rb +24 -0
- metadata +33 -2
data/lib/gruffy/pie.rb
ADDED
@@ -0,0 +1,271 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/base'
|
2
|
+
|
3
|
+
##
|
4
|
+
# Here's how to make a Pie graph:
|
5
|
+
#
|
6
|
+
# g = Gruffy::Pie.new
|
7
|
+
# g.title = "Visual Pie Graph Test"
|
8
|
+
# g.data 'Fries', 20
|
9
|
+
# g.data 'Hamburgers', 50
|
10
|
+
# g.write("test/output/pie_keynote.png")
|
11
|
+
#
|
12
|
+
# To control where the pie chart starts creating slices, use #zero_degree.
|
13
|
+
|
14
|
+
class Gruffy::Pie < Gruffy::Base
|
15
|
+
|
16
|
+
DEFAULT_TEXT_OFFSET_PERCENTAGE = 0.15
|
17
|
+
|
18
|
+
# Can be used to make the pie start cutting slices at the top (-90.0)
|
19
|
+
# or at another angle. Default is 0.0, which starts at 3 o'clock.
|
20
|
+
attr_writer :zero_degree
|
21
|
+
|
22
|
+
# Do not show labels for slices that are less than this percent. Use 0 to always show all labels.
|
23
|
+
# Defaults to 0
|
24
|
+
attr_writer :hide_labels_less_than
|
25
|
+
|
26
|
+
# Affect the distance between the percentages and the pie chart
|
27
|
+
# Defaults to 0.15
|
28
|
+
attr_writer :text_offset_percentage
|
29
|
+
|
30
|
+
## Use values instead of percentages
|
31
|
+
attr_accessor :show_values_as_labels
|
32
|
+
|
33
|
+
def initialize_ivars
|
34
|
+
super
|
35
|
+
|
36
|
+
@show_values_as_labels = false
|
37
|
+
end
|
38
|
+
|
39
|
+
def zero_degree
|
40
|
+
@zero_degree ||= 0.0
|
41
|
+
end
|
42
|
+
|
43
|
+
def hide_labels_less_than
|
44
|
+
@hide_labels_less_than ||= 0.0
|
45
|
+
end
|
46
|
+
|
47
|
+
def text_offset_percentage
|
48
|
+
@text_offset_percentage ||= DEFAULT_TEXT_OFFSET_PERCENTAGE
|
49
|
+
end
|
50
|
+
|
51
|
+
def options
|
52
|
+
{
|
53
|
+
:zero_degree => zero_degree,
|
54
|
+
:hide_labels_less_than => hide_labels_less_than,
|
55
|
+
:text_offset_percentage => text_offset_percentage,
|
56
|
+
:show_values_as_labels => show_values_as_labels
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
def draw
|
61
|
+
hide_line_markers
|
62
|
+
|
63
|
+
super
|
64
|
+
|
65
|
+
return unless data_given?
|
66
|
+
|
67
|
+
slices.each do |slice|
|
68
|
+
if slice.value > 0
|
69
|
+
set_stroke_color slice
|
70
|
+
set_fill_color
|
71
|
+
set_stroke_width
|
72
|
+
set_drawing_points_for slice
|
73
|
+
process_label_for slice
|
74
|
+
update_chart_degrees_with slice.degrees
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
trigger_final_draw
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def slices
|
84
|
+
@slices ||= begin
|
85
|
+
slices = @data.map { |data| slice_class.new(data, options) }
|
86
|
+
|
87
|
+
slices.sort_by(&:value) if @sort
|
88
|
+
|
89
|
+
total = slices.map(&:value).inject(:+).to_f
|
90
|
+
slices.each { |slice| slice.total = total }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# General Helper Methods
|
95
|
+
|
96
|
+
def hide_line_markers
|
97
|
+
@hide_line_markers = true
|
98
|
+
end
|
99
|
+
|
100
|
+
def data_given?
|
101
|
+
@has_data
|
102
|
+
end
|
103
|
+
|
104
|
+
def update_chart_degrees_with(degrees)
|
105
|
+
@chart_degrees = chart_degrees + degrees
|
106
|
+
end
|
107
|
+
|
108
|
+
def slice_class
|
109
|
+
PieSlice
|
110
|
+
end
|
111
|
+
|
112
|
+
# Spatial Value-Related Methods
|
113
|
+
|
114
|
+
def chart_degrees
|
115
|
+
@chart_degrees ||= zero_degree
|
116
|
+
end
|
117
|
+
|
118
|
+
def graph_height
|
119
|
+
@graph_height
|
120
|
+
end
|
121
|
+
|
122
|
+
def graph_width
|
123
|
+
@graph_width
|
124
|
+
end
|
125
|
+
|
126
|
+
def diameter
|
127
|
+
graph_height
|
128
|
+
end
|
129
|
+
|
130
|
+
def half_width
|
131
|
+
graph_width / 2.0
|
132
|
+
end
|
133
|
+
|
134
|
+
def half_height
|
135
|
+
graph_height / 2.0
|
136
|
+
end
|
137
|
+
|
138
|
+
def radius
|
139
|
+
@radius ||= ([graph_width, graph_height].min / 2.0) * 0.8
|
140
|
+
end
|
141
|
+
|
142
|
+
def center_x
|
143
|
+
@center_x ||= @graph_left + half_width
|
144
|
+
end
|
145
|
+
|
146
|
+
def center_y
|
147
|
+
@center_y ||= @graph_top + half_height - 10
|
148
|
+
end
|
149
|
+
|
150
|
+
def distance_from_center
|
151
|
+
20.0
|
152
|
+
end
|
153
|
+
|
154
|
+
def radius_offset
|
155
|
+
radius + (radius * text_offset_percentage) + distance_from_center
|
156
|
+
end
|
157
|
+
|
158
|
+
def ellipse_factor
|
159
|
+
radius_offset * text_offset_percentage
|
160
|
+
end
|
161
|
+
|
162
|
+
# Label-Related Methods
|
163
|
+
|
164
|
+
def process_label_for(slice)
|
165
|
+
if slice.percentage >= hide_labels_less_than
|
166
|
+
x, y = label_coordinates_for slice
|
167
|
+
|
168
|
+
@d = draw_label(x, y, slice.label)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def label_coordinates_for(slice)
|
173
|
+
angle = chart_degrees + slice.degrees / 2
|
174
|
+
|
175
|
+
[x_label_coordinate(angle), y_label_coordinate(angle)]
|
176
|
+
end
|
177
|
+
|
178
|
+
def x_label_coordinate(angle)
|
179
|
+
center_x + ((radius_offset + ellipse_factor) * Math.cos(deg2rad(angle)))
|
180
|
+
end
|
181
|
+
|
182
|
+
def y_label_coordinate(angle)
|
183
|
+
center_y + (radius_offset * Math.sin(deg2rad(angle)))
|
184
|
+
end
|
185
|
+
|
186
|
+
# Drawing-Related Methods
|
187
|
+
|
188
|
+
def set_stroke_width
|
189
|
+
@d.stroke_width(radius)
|
190
|
+
end
|
191
|
+
|
192
|
+
def set_stroke_color(slice)
|
193
|
+
@d = @d.stroke slice.color
|
194
|
+
end
|
195
|
+
|
196
|
+
def set_fill_color
|
197
|
+
@d = @d.fill 'transparent'
|
198
|
+
end
|
199
|
+
|
200
|
+
def set_drawing_points_for(slice)
|
201
|
+
@d = @d.ellipse(
|
202
|
+
center_x,
|
203
|
+
center_y,
|
204
|
+
radius / 2.0,
|
205
|
+
radius / 2.0,
|
206
|
+
chart_degrees,
|
207
|
+
chart_degrees + slice.degrees + 0.5
|
208
|
+
)
|
209
|
+
end
|
210
|
+
|
211
|
+
def trigger_final_draw
|
212
|
+
@d.draw(@base_image)
|
213
|
+
end
|
214
|
+
|
215
|
+
def configure_label_styling
|
216
|
+
@d.fill = @font_color
|
217
|
+
@d.font = @font if @font
|
218
|
+
@d.pointsize = scale_fontsize(@marker_font_size)
|
219
|
+
@d.stroke = 'transparent'
|
220
|
+
@d.font_weight = BoldWeight
|
221
|
+
@d.gravity = CenterGravity
|
222
|
+
end
|
223
|
+
|
224
|
+
def draw_label(x, y, value)
|
225
|
+
configure_label_styling
|
226
|
+
|
227
|
+
@d.annotate_scaled(
|
228
|
+
@base_image,
|
229
|
+
0,
|
230
|
+
0,
|
231
|
+
x,
|
232
|
+
y,
|
233
|
+
value,
|
234
|
+
@scale
|
235
|
+
)
|
236
|
+
end
|
237
|
+
|
238
|
+
# Helper Classes
|
239
|
+
|
240
|
+
class PieSlice < Struct.new(:data_array, :options)
|
241
|
+
attr_accessor :total
|
242
|
+
|
243
|
+
def name
|
244
|
+
data_array[0]
|
245
|
+
end
|
246
|
+
|
247
|
+
def value
|
248
|
+
data_array[1].first
|
249
|
+
end
|
250
|
+
|
251
|
+
def color
|
252
|
+
data_array[2]
|
253
|
+
end
|
254
|
+
|
255
|
+
def size
|
256
|
+
@size ||= value / total
|
257
|
+
end
|
258
|
+
|
259
|
+
def percentage
|
260
|
+
@percentage ||= (size * 100.0).round
|
261
|
+
end
|
262
|
+
|
263
|
+
def degrees
|
264
|
+
@degrees ||= size * 360.0
|
265
|
+
end
|
266
|
+
|
267
|
+
def label
|
268
|
+
options[:show_values_as_labels] ? value.to_s : "#{percentage}%"
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
@@ -0,0 +1,314 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/base'
|
2
|
+
|
3
|
+
# Here's how to set up an XY Scatter Chart
|
4
|
+
#
|
5
|
+
# g = Gruffy::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
|
+
#
|
10
|
+
#
|
11
|
+
class Gruffy::Scatter < Gruffy::Base
|
12
|
+
|
13
|
+
# Maximum X Value. The value will get overwritten by the max in the
|
14
|
+
# datasets.
|
15
|
+
attr_accessor :maximum_x_value
|
16
|
+
|
17
|
+
# Minimum X Value. The value will get overwritten by the min in the
|
18
|
+
# datasets.
|
19
|
+
attr_accessor :minimum_x_value
|
20
|
+
|
21
|
+
# The number of vertical lines shown for reference
|
22
|
+
attr_accessor :marker_x_count
|
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
|
37
|
+
attr_accessor :circle_radius
|
38
|
+
attr_accessor :stroke_width
|
39
|
+
|
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)
|
42
|
+
attr_accessor :disable_significant_rounding_x_axis
|
43
|
+
|
44
|
+
# Allow enabling vertical lines. When you have a lot of data, they can work great
|
45
|
+
attr_accessor :enable_vertical_line_markers
|
46
|
+
|
47
|
+
# Allow using vertical labels in the X axis (and setting the label margin)
|
48
|
+
attr_accessor :x_label_margin
|
49
|
+
attr_accessor :use_vertical_x_labels
|
50
|
+
|
51
|
+
# Allow passing lambdas to format labels
|
52
|
+
attr_accessor :y_axis_label_format
|
53
|
+
attr_accessor :x_axis_label_format
|
54
|
+
|
55
|
+
|
56
|
+
# Gruffy::Scatter takes the same parameters as the Gruffy::Line graph
|
57
|
+
#
|
58
|
+
# ==== Example
|
59
|
+
#
|
60
|
+
# g = Gruffy::Scatter.new
|
61
|
+
#
|
62
|
+
def initialize(*)
|
63
|
+
super
|
64
|
+
|
65
|
+
@baseline_x_color = @baseline_y_color = 'red'
|
66
|
+
@baseline_x_value = @baseline_y_value = nil
|
67
|
+
@circle_radius = nil
|
68
|
+
@disable_significant_rounding_x_axis = false
|
69
|
+
@enable_vertical_line_markers = false
|
70
|
+
@marker_x_count = nil
|
71
|
+
@maximum_x_value = @minimum_x_value = nil
|
72
|
+
@stroke_width = nil
|
73
|
+
@use_vertical_x_labels = false
|
74
|
+
@x_axis_label_format = nil
|
75
|
+
@x_label_margin = nil
|
76
|
+
@y_axis_label_format = nil
|
77
|
+
end
|
78
|
+
|
79
|
+
def setup_drawing
|
80
|
+
# TODO Need to get x-axis labels working. Current behavior will be to not allow.
|
81
|
+
@labels = {}
|
82
|
+
|
83
|
+
super
|
84
|
+
|
85
|
+
# Translate our values so that we can use the base methods for drawing
|
86
|
+
# the standard chart stuff
|
87
|
+
@column_count = @x_spread
|
88
|
+
end
|
89
|
+
|
90
|
+
def draw
|
91
|
+
super
|
92
|
+
return unless @has_data
|
93
|
+
|
94
|
+
# Check to see if more than one datapoint was given. NaN can result otherwise.
|
95
|
+
@x_increment = (@column_count > 1) ? (@graph_width / (@column_count - 1).to_f) : @graph_width
|
96
|
+
|
97
|
+
#~ if (defined?(@norm_y_baseline)) then
|
98
|
+
#~ level = @graph_top + (@graph_height - @norm_baseline * @graph_height)
|
99
|
+
#~ @d = @d.push
|
100
|
+
#~ @d.stroke_color @baseline_color
|
101
|
+
#~ @d.fill_opacity 0.0
|
102
|
+
#~ @d.stroke_dasharray(10, 20)
|
103
|
+
#~ @d.stroke_width 5
|
104
|
+
#~ @d.line(@graph_left, level, @graph_left + @graph_width, level)
|
105
|
+
#~ @d = @d.pop
|
106
|
+
#~ end
|
107
|
+
|
108
|
+
#~ if (defined?(@norm_x_baseline)) then
|
109
|
+
|
110
|
+
#~ end
|
111
|
+
|
112
|
+
@norm_data.each do |data_row|
|
113
|
+
data_row[DATA_VALUES_INDEX].each_with_index do |data_point, index|
|
114
|
+
x_value = data_row[DATA_VALUES_X_INDEX][index]
|
115
|
+
next if data_point.nil? || x_value.nil?
|
116
|
+
|
117
|
+
new_x = get_x_coord(x_value, @graph_width, @graph_left)
|
118
|
+
new_y = @graph_top + (@graph_height - data_point * @graph_height)
|
119
|
+
|
120
|
+
# Reset each time to avoid thin-line errors
|
121
|
+
@d = @d.stroke data_row[DATA_COLOR_INDEX]
|
122
|
+
@d = @d.fill data_row[DATA_COLOR_INDEX]
|
123
|
+
@d = @d.stroke_opacity 1.0
|
124
|
+
@d = @d.stroke_width @stroke_width || clip_value_if_greater_than(@columns / (@norm_data.first[1].size * 4), 5.0)
|
125
|
+
|
126
|
+
circle_radius = @circle_radius || clip_value_if_greater_than(@columns / (@norm_data.first[1].size * 2.5), 5.0)
|
127
|
+
@d = @d.circle(new_x, new_y, new_x - circle_radius, new_y)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
@d.draw(@base_image)
|
132
|
+
end
|
133
|
+
|
134
|
+
# The first parameter is the name of the dataset. The next two are the
|
135
|
+
# x and y axis data points contain in their own array in that respective
|
136
|
+
# order. The final parameter is the color.
|
137
|
+
#
|
138
|
+
# Can be called multiple times with different datasets for a multi-valued
|
139
|
+
# graph.
|
140
|
+
#
|
141
|
+
# If the color argument is nil, the next color from the default theme will
|
142
|
+
# be used.
|
143
|
+
#
|
144
|
+
# NOTE: If you want to use a preset theme, you must set it before calling
|
145
|
+
# data().
|
146
|
+
#
|
147
|
+
# ==== Parameters
|
148
|
+
# name:: String or Symbol containing the name of the dataset.
|
149
|
+
# x_data_points:: An Array of of x-axis data points.
|
150
|
+
# y_data_points:: An Array of of y-axis data points.
|
151
|
+
# color:: The hex string for the color of the dataset. Defaults to nil.
|
152
|
+
#
|
153
|
+
# ==== Exceptions
|
154
|
+
# Data points contain nil values::
|
155
|
+
# This error will get raised if either the x or y axis data points array
|
156
|
+
# contains a <tt>nil</tt> value. The graph will not make an assumption
|
157
|
+
# as how to graph <tt>nil</tt>
|
158
|
+
# x_data_points is empty::
|
159
|
+
# This error is raised when the array for the x-axis points are empty
|
160
|
+
# y_data_points is empty::
|
161
|
+
# This error is raised when the array for the y-axis points are empty
|
162
|
+
# x_data_points.length != y_data_points.length::
|
163
|
+
# Error means that the x and y axis point arrays do not match in length
|
164
|
+
#
|
165
|
+
# ==== Examples
|
166
|
+
# g = Gruffy::Scatter.new
|
167
|
+
# g.data(:apples, [1,2,3], [3,2,1])
|
168
|
+
# g.data('oranges', [1,1,1], [2,3,4])
|
169
|
+
# g.data('bitter_melon', [3,5,6], [6,7,8], '#000000')
|
170
|
+
#
|
171
|
+
def data(name, x_data_points=[], y_data_points=[], color=nil)
|
172
|
+
|
173
|
+
raise ArgumentError, 'Data Points contain nil Value!' if x_data_points.include?(nil) || y_data_points.include?(nil)
|
174
|
+
raise ArgumentError, 'x_data_points is empty!' if x_data_points.empty?
|
175
|
+
raise ArgumentError, 'y_data_points is empty!' if y_data_points.empty?
|
176
|
+
raise ArgumentError, 'x_data_points.length != y_data_points.length!' if x_data_points.length != y_data_points.length
|
177
|
+
|
178
|
+
# Call the existing data routine for the y axis data
|
179
|
+
super(name, y_data_points, color)
|
180
|
+
|
181
|
+
#append the x data to the last entry that was just added in the @data member
|
182
|
+
last_elem = @data.length()-1
|
183
|
+
@data[last_elem] << x_data_points
|
184
|
+
|
185
|
+
if @maximum_x_value.nil? && @minimum_x_value.nil?
|
186
|
+
@maximum_x_value = @minimum_x_value = x_data_points.first
|
187
|
+
end
|
188
|
+
|
189
|
+
@maximum_x_value = x_data_points.max > @maximum_x_value ?
|
190
|
+
x_data_points.max : @maximum_x_value
|
191
|
+
@minimum_x_value = x_data_points.min < @minimum_x_value ?
|
192
|
+
x_data_points.min : @minimum_x_value
|
193
|
+
end
|
194
|
+
|
195
|
+
protected
|
196
|
+
|
197
|
+
def calculate_spread #:nodoc:
|
198
|
+
super
|
199
|
+
@x_spread = @maximum_x_value.to_f - @minimum_x_value.to_f
|
200
|
+
@x_spread = @x_spread > 0 ? @x_spread : 1
|
201
|
+
end
|
202
|
+
|
203
|
+
def normalize(force=nil)
|
204
|
+
if @norm_data.nil? || force
|
205
|
+
@norm_data = []
|
206
|
+
return unless @has_data
|
207
|
+
|
208
|
+
@data.each do |data_row|
|
209
|
+
norm_data_points = [data_row[DATA_LABEL_INDEX]]
|
210
|
+
norm_data_points << data_row[DATA_VALUES_INDEX].map do |r|
|
211
|
+
(r.to_f - @minimum_value.to_f) / @spread
|
212
|
+
end
|
213
|
+
norm_data_points << data_row[DATA_COLOR_INDEX]
|
214
|
+
norm_data_points << data_row[DATA_VALUES_X_INDEX].map do |r|
|
215
|
+
(r.to_f - @minimum_x_value.to_f) / @x_spread
|
216
|
+
end
|
217
|
+
@norm_data << norm_data_points
|
218
|
+
end
|
219
|
+
end
|
220
|
+
#~ @norm_y_baseline = (@baseline_y_value.to_f / @maximum_value.to_f) if @baseline_y_value
|
221
|
+
#~ @norm_x_baseline = (@baseline_x_value.to_f / @maximum_x_value.to_f) if @baseline_x_value
|
222
|
+
end
|
223
|
+
|
224
|
+
def draw_line_markers
|
225
|
+
# do all of the stuff for the horizontal lines on the y-axis
|
226
|
+
super
|
227
|
+
return if @hide_line_markers
|
228
|
+
|
229
|
+
@d = @d.stroke_antialias false
|
230
|
+
|
231
|
+
if @x_axis_increment.nil?
|
232
|
+
# TODO Do the same for larger numbers...100, 75, 50, 25
|
233
|
+
if @marker_x_count.nil?
|
234
|
+
(3..7).each do |lines|
|
235
|
+
if @x_spread % lines == 0.0
|
236
|
+
@marker_x_count = lines
|
237
|
+
break
|
238
|
+
end
|
239
|
+
end
|
240
|
+
@marker_x_count ||= 4
|
241
|
+
end
|
242
|
+
@x_increment = (@x_spread > 0) ? (@x_spread / @marker_x_count) : 1
|
243
|
+
unless @disable_significant_rounding_x_axis
|
244
|
+
@x_increment = significant(@x_increment)
|
245
|
+
end
|
246
|
+
else
|
247
|
+
# TODO Make this work for negative values
|
248
|
+
@maximum_x_value = [@maximum_value.ceil, @x_axis_increment].max
|
249
|
+
@minimum_x_value = @minimum_x_value.floor
|
250
|
+
calculate_spread
|
251
|
+
normalize(true)
|
252
|
+
|
253
|
+
@marker_count = (@x_spread / @x_axis_increment).to_i
|
254
|
+
@x_increment = @x_axis_increment
|
255
|
+
end
|
256
|
+
@increment_x_scaled = @graph_width.to_f / (@x_spread / @x_increment)
|
257
|
+
|
258
|
+
# Draw vertical line markers and annotate with numbers
|
259
|
+
(0..@marker_x_count).each do |index|
|
260
|
+
|
261
|
+
# TODO Fix the vertical lines, and enable them by default. Not pretty when they don't match up with top y-axis line
|
262
|
+
if @enable_vertical_line_markers
|
263
|
+
x = @graph_left + @graph_width - index.to_f * @increment_x_scaled
|
264
|
+
@d = @d.stroke(@marker_color)
|
265
|
+
@d = @d.stroke_width 1
|
266
|
+
@d = @d.line(x, @graph_top, x, @graph_bottom)
|
267
|
+
end
|
268
|
+
|
269
|
+
unless @hide_line_numbers
|
270
|
+
marker_label = index * @x_increment + @minimum_x_value.to_f
|
271
|
+
y_offset = @graph_bottom + (@x_label_margin || LABEL_MARGIN)
|
272
|
+
x_offset = get_x_coord(index.to_f, @increment_x_scaled, @graph_left)
|
273
|
+
|
274
|
+
@d.fill = @font_color
|
275
|
+
@d.font = @font if @font
|
276
|
+
@d.stroke('transparent')
|
277
|
+
@d.pointsize = scale_fontsize(@marker_font_size)
|
278
|
+
@d.gravity = NorthGravity
|
279
|
+
@d.rotation = -90.0 if @use_vertical_x_labels
|
280
|
+
@d = @d.annotate_scaled(@base_image,
|
281
|
+
1.0, 1.0,
|
282
|
+
x_offset, y_offset,
|
283
|
+
vertical_label(marker_label, @x_increment), @scale)
|
284
|
+
@d.rotation = 90.0 if @use_vertical_x_labels
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
@d = @d.stroke_antialias true
|
289
|
+
end
|
290
|
+
|
291
|
+
|
292
|
+
def label(value, increment)
|
293
|
+
if @y_axis_label_format
|
294
|
+
@y_axis_label_format.call(value)
|
295
|
+
else
|
296
|
+
super
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def vertical_label(value, increment)
|
301
|
+
if @x_axis_label_format
|
302
|
+
@x_axis_label_format.call(value)
|
303
|
+
else
|
304
|
+
label(value, increment)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
private
|
309
|
+
|
310
|
+
def get_x_coord(x_data_point, width, offset) #:nodoc:
|
311
|
+
x_data_point * width + offset
|
312
|
+
end
|
313
|
+
|
314
|
+
end # end Gruffy::Scatter
|