gruff 0.0.1
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.
- data/CHANGELOG +6 -0
- data/MIT-LICENSE +21 -0
- data/README +11 -0
- data/lib/gruff.rb +5 -0
- data/lib/gruff/area.rb +15 -0
- data/lib/gruff/bar.rb +15 -0
- data/lib/gruff/base.rb +364 -0
- data/lib/gruff/line.rb +62 -0
- data/lib/gruff/pie.rb +15 -0
- data/rakefile +192 -0
- data/test/line_test.rb +250 -0
- data/test/output/line_37signals.png +0 -0
- data/test/output/line_37signals_small.png +0 -0
- data/test/output/line_font.png +0 -0
- data/test/output/line_keynote.png +0 -0
- data/test/output/line_keynote_small.png +0 -0
- data/test/output/line_large.png +0 -0
- data/test/output/line_many.png +0 -0
- data/test/output/line_odeo.png +0 -0
- data/test/output/line_odeo_small.png +0 -0
- data/test/output/line_rails_keynote.png +0 -0
- data/test/output/line_rails_keynote_small.png +0 -0
- data/test/output/line_small.png +0 -0
- data/test/output/similar_high_end_values.png +0 -0
- metadata +65 -0
data/CHANGELOG
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2005 Geoffrey Grosenbach boss@topfunky.com
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
data/README
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
== Gruff Graphs
|
2
|
+
|
3
|
+
A library for making beautiful graphs.
|
4
|
+
|
5
|
+
See samples at http://nubyonrails.topfunky.com/articles/2005/10/24/gruff-graphing-library-for-ruby
|
6
|
+
|
7
|
+
See the test suite in test/line_test.rb for examples. (More documentation coming soon.)
|
8
|
+
|
9
|
+
== WARNING
|
10
|
+
|
11
|
+
This is alpha-quality software. It works well according to my tests, but the API may change and other features will be added. Backwards compatibility is not guaranteed until the 1.0 release.
|
data/lib/gruff.rb
ADDED
data/lib/gruff/area.rb
ADDED
data/lib/gruff/bar.rb
ADDED
data/lib/gruff/base.rb
ADDED
@@ -0,0 +1,364 @@
|
|
1
|
+
#
|
2
|
+
# = Gruff. Graphs.
|
3
|
+
#
|
4
|
+
# Author:: Geoffrey Grosenbach boss@topfunky.com
|
5
|
+
#
|
6
|
+
# Date:: October 23, 2005
|
7
|
+
#
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'rmagick'
|
11
|
+
require 'yaml'
|
12
|
+
|
13
|
+
module Gruff
|
14
|
+
|
15
|
+
VERSION = '0.0.1'
|
16
|
+
|
17
|
+
class Base
|
18
|
+
|
19
|
+
include Magick
|
20
|
+
|
21
|
+
# A hash of names for the individual columns, where the key is the array index for the column this label represents.
|
22
|
+
#
|
23
|
+
# Not all columns need to be named.
|
24
|
+
#
|
25
|
+
# Example: 0 => 2005, 3 => 2006, 5 => 2007, 7 => 2008
|
26
|
+
attr_accessor :labels
|
27
|
+
|
28
|
+
# The large title of the graph displayed at the top
|
29
|
+
attr_accessor :title
|
30
|
+
|
31
|
+
# Font used for titles, labels, etc. Works best if you provide the full path to the TTF font file.
|
32
|
+
# RMagick must be built with the Freetype libraries for this to work properly.
|
33
|
+
attr_accessor :font
|
34
|
+
|
35
|
+
# Graph is drawn at 4/3 ratio (800x600, 400x300, etc.).
|
36
|
+
#
|
37
|
+
# Looks for Bitstream Vera as the default font. Expects an environment var of MAGICK_FONT_PATH to be set.
|
38
|
+
# (Uses RMagick's default font otherwise.)
|
39
|
+
def initialize(target_width=800)
|
40
|
+
@columns = target_width.to_f
|
41
|
+
@rows = target_width.to_f * 0.75
|
42
|
+
|
43
|
+
# Internal for calculations
|
44
|
+
@font = File.expand_path('Vera.ttf', ENV['MAGICK_FONT_PATH'])
|
45
|
+
@marker_pointsize = 21.0
|
46
|
+
@raw_columns = 800.0
|
47
|
+
@raw_rows = 600.0
|
48
|
+
@column_count = 0
|
49
|
+
@maximum_value = 0
|
50
|
+
@data = Array.new
|
51
|
+
@labels = Hash.new
|
52
|
+
@labels_seen = Hash.new
|
53
|
+
@scale = @columns / @raw_columns
|
54
|
+
|
55
|
+
reset_themes()
|
56
|
+
theme_keynote()
|
57
|
+
end
|
58
|
+
|
59
|
+
# Add a color to the list of available colors for lines.
|
60
|
+
#
|
61
|
+
# Example:
|
62
|
+
# add_color('#c0e9d3')
|
63
|
+
def add_color(colorname)
|
64
|
+
@colors << colorname
|
65
|
+
end
|
66
|
+
|
67
|
+
# Replace the entire color list with a new array of colors. You need to have one more color
|
68
|
+
# than the number of datasets you intend to draw.
|
69
|
+
#
|
70
|
+
# Example:
|
71
|
+
# replace_colors('#cc99cc', '#d9e043', '#34d8a2')
|
72
|
+
def replace_colors(color_list=[])
|
73
|
+
@colors = color_list
|
74
|
+
end
|
75
|
+
|
76
|
+
# A color scheme similar to the popular presentation software.
|
77
|
+
def theme_keynote
|
78
|
+
reset_themes()
|
79
|
+
# Colors
|
80
|
+
@blue = '#6886B4'
|
81
|
+
@yellow = '#FDD84E'
|
82
|
+
@green = '#72AE6E'
|
83
|
+
@red = '#D1695E'
|
84
|
+
@purple = '#8A6EAF'
|
85
|
+
@orange = '#EFAA43'
|
86
|
+
@white = 'white'
|
87
|
+
@colors = [@yellow, @blue, @green, @red, @purple, @orange, @white]
|
88
|
+
|
89
|
+
@marker_color = 'white'
|
90
|
+
|
91
|
+
@base_image = render_gradiated_background('black', '#4a465a')
|
92
|
+
end
|
93
|
+
|
94
|
+
# A color scheme plucked from the colors on the popular usability blog.
|
95
|
+
def theme_37signals
|
96
|
+
reset_themes()
|
97
|
+
# Colors
|
98
|
+
@green = '#339933'
|
99
|
+
@purple = '#cc99cc'
|
100
|
+
@blue = '#336699'
|
101
|
+
@yellow = '#FFF804'
|
102
|
+
@red = '#ff0000'
|
103
|
+
@orange = '#cf5910'
|
104
|
+
@black = 'black'
|
105
|
+
@colors = [@yellow, @blue, @green, @red, @purple, @orange, @black]
|
106
|
+
|
107
|
+
@marker_color = 'black'
|
108
|
+
|
109
|
+
@base_image = render_gradiated_background('#d1edf5', 'white')
|
110
|
+
end
|
111
|
+
|
112
|
+
# A color scheme from the colors used on the 2005 Rails keynote presentation at RubyConf.
|
113
|
+
def theme_rails_keynote
|
114
|
+
reset_themes()
|
115
|
+
# Colors
|
116
|
+
@green = '#00ff00'
|
117
|
+
@grey = '#333333'
|
118
|
+
@orange = '#ff5d00'
|
119
|
+
@red = '#f61100'
|
120
|
+
@white = 'white'
|
121
|
+
@light_grey = '#999999'
|
122
|
+
@black = 'black'
|
123
|
+
@colors = [@green, @grey, @orange, @red, @white, @light_grey, @black]
|
124
|
+
|
125
|
+
@marker_color = 'white'
|
126
|
+
|
127
|
+
@base_image = render_gradiated_background('#0083a3', '#0083a3')
|
128
|
+
end
|
129
|
+
|
130
|
+
# A color scheme similar to that used on the popular podcast site.
|
131
|
+
def theme_odeo
|
132
|
+
reset_themes()
|
133
|
+
# Colors
|
134
|
+
@grey = '#202020'
|
135
|
+
@white = 'white'
|
136
|
+
@dark_pink = '#a21764'
|
137
|
+
@green = '#8ab438'
|
138
|
+
@light_grey = '#999999'
|
139
|
+
@dark_blue = '#3a5b87'
|
140
|
+
@black = 'black'
|
141
|
+
@colors = [@grey, @white, @dark_blue, @dark_pink, @green, @light_grey, @black]
|
142
|
+
|
143
|
+
@marker_color = 'white'
|
144
|
+
|
145
|
+
@base_image = render_gradiated_background('#ff47a4', '#ff1f81')
|
146
|
+
end
|
147
|
+
|
148
|
+
# dataset is an array where the first element is the name of the dataset
|
149
|
+
# and the value is an array of values to plot.
|
150
|
+
#
|
151
|
+
# Can be called multiple times with different datasets
|
152
|
+
# for a multi-valued graph.
|
153
|
+
#
|
154
|
+
# Example:
|
155
|
+
# data("Bart S.", [95, 45, 78, 89, 88, 76])
|
156
|
+
def data(name, data_points=[])
|
157
|
+
@data << [name, data_points]
|
158
|
+
# Set column count if this is larger than previous counts
|
159
|
+
@column_count = (data_points.length > @column_count) ? data_points.length : @column_count
|
160
|
+
|
161
|
+
# Pre-normalize
|
162
|
+
data_points.each do |data_point|
|
163
|
+
@maximum_value = (data_point > @maximum_value) ? data_point : @maximum_value
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Writes the graph to a file. Defaults to 'graph.png'
|
168
|
+
#
|
169
|
+
# Example: write('graphs/my_pretty_graph.png')
|
170
|
+
def write(filename="graph.png")
|
171
|
+
draw()
|
172
|
+
@base_image.write(filename)
|
173
|
+
end
|
174
|
+
|
175
|
+
# TODO Return the graph as a rendered binary blob.
|
176
|
+
def to_blob(filename="graph.png")
|
177
|
+
draw()
|
178
|
+
@base_image = @base_image.to_blob()
|
179
|
+
end
|
180
|
+
|
181
|
+
protected
|
182
|
+
|
183
|
+
# Overridden by subclasses to do the actual plotting of the graph.
|
184
|
+
#
|
185
|
+
# Subclasses should start by calling super() for this method.
|
186
|
+
def draw
|
187
|
+
setup_drawing()
|
188
|
+
|
189
|
+
# Subclasses will do some drawing here...
|
190
|
+
#@d.draw(@base_image)
|
191
|
+
end
|
192
|
+
|
193
|
+
# Draws the decorations.
|
194
|
+
# - line markers
|
195
|
+
# - legend
|
196
|
+
# - title
|
197
|
+
def setup_drawing
|
198
|
+
normalize()
|
199
|
+
|
200
|
+
draw_line_markers()
|
201
|
+
draw_legend()
|
202
|
+
draw_title
|
203
|
+
end
|
204
|
+
|
205
|
+
# Make copy of data with values scaled between 0-100
|
206
|
+
def normalize
|
207
|
+
@norm_data = Array.new
|
208
|
+
@data.each do |data_row|
|
209
|
+
norm_data_points = Array.new
|
210
|
+
data_row[1].each do |data_point|
|
211
|
+
norm_data_points << (data_point.to_f/@maximum_value.to_f)
|
212
|
+
end
|
213
|
+
@norm_data << [data_row[0], norm_data_points]
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# Draws horizontal background lines and labels
|
218
|
+
def draw_line_markers
|
219
|
+
@graph_left = 130.0
|
220
|
+
@graph_right = @raw_columns - 100.0
|
221
|
+
@graph_top = 150.0
|
222
|
+
@graph_bottom = @raw_rows - 110.0
|
223
|
+
@graph_height = @graph_bottom - @graph_top
|
224
|
+
@graph_width = @graph_right - @graph_left
|
225
|
+
|
226
|
+
# Draw horizontal line markers and annotate with numbers
|
227
|
+
@d = @d.stroke(@marker_color)
|
228
|
+
@d = @d.stroke_width 1
|
229
|
+
(0..4).each do |index|
|
230
|
+
#y = ( index.to_f * (@graph_height.to_f/4.0) ) + @graph_top.to_f
|
231
|
+
y = @graph_top + @graph_height - ( index.to_f * (@graph_height.to_f/4.0) )
|
232
|
+
@d = @d.line(@graph_left, y, @graph_right, y)
|
233
|
+
|
234
|
+
marker_label = 0
|
235
|
+
marker_label = @maximum_value.to_f * (index.to_f/4.to_f) if index > 0
|
236
|
+
|
237
|
+
@d.fill = @marker_color
|
238
|
+
@d.font = @font
|
239
|
+
@d.stroke = 'transparent'
|
240
|
+
@d.pointsize = scale_fontsize(@marker_pointsize)
|
241
|
+
@d.gravity = EastGravity
|
242
|
+
@d = @d.annotate_scaled( @base_image,
|
243
|
+
100, 20,
|
244
|
+
-10, y - (@marker_pointsize/2.0),
|
245
|
+
marker_label.to_s, @scale)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
# Draws a legend with the names of the datasets matched to the colors used to draw them.
|
250
|
+
def draw_legend
|
251
|
+
@color_index = 0
|
252
|
+
@legend_labels = @data.collect {|item| item[0] }
|
253
|
+
|
254
|
+
legend_square_width = 20 # small square with color of this item
|
255
|
+
|
256
|
+
metrics = @d.get_type_metrics(@base_image, @legend_labels.join(''))
|
257
|
+
legend_text_width = metrics.width
|
258
|
+
legend_width = legend_text_width + @legend_labels.length * (legend_square_width * 2.7)
|
259
|
+
legend_left = scale(@raw_columns - legend_width) / 2
|
260
|
+
legend_increment = legend_width / @legend_labels.length.to_f
|
261
|
+
|
262
|
+
current_x_offset = legend_left
|
263
|
+
@legend_labels.each_with_index do |legend_label, index|
|
264
|
+
# Draw label
|
265
|
+
@d.fill = @marker_color
|
266
|
+
@d.font = @font
|
267
|
+
@d.pointsize = scale_fontsize(20)
|
268
|
+
@d.stroke = 'transparent'
|
269
|
+
@d.font_weight = NormalWeight
|
270
|
+
@d.gravity = WestGravity
|
271
|
+
@d = @d.annotate_scaled( @base_image,
|
272
|
+
@raw_columns, 24,
|
273
|
+
current_x_offset + (legend_square_width * 1.7), 70,
|
274
|
+
legend_label.to_s, @scale)
|
275
|
+
|
276
|
+
# Now draw box with color of this dataset
|
277
|
+
legend_box_y_offset = 2 # Move box down slightly to center
|
278
|
+
@d = @d.stroke 'transparent'
|
279
|
+
@d = @d.fill current_color
|
280
|
+
@d = @d.rectangle(current_x_offset, 70 + legend_box_y_offset,
|
281
|
+
current_x_offset + legend_square_width, 70 + legend_square_width + legend_box_y_offset)
|
282
|
+
|
283
|
+
increment_color()
|
284
|
+
|
285
|
+
@d.pointsize = 20
|
286
|
+
metrics = @d.get_type_metrics(@base_image, legend_label.to_s)
|
287
|
+
current_string_offset = metrics.width + (legend_square_width * 2.7)
|
288
|
+
current_x_offset += current_string_offset
|
289
|
+
end
|
290
|
+
@color_index = 0
|
291
|
+
end
|
292
|
+
|
293
|
+
def draw_title
|
294
|
+
@d.fill = @marker_color
|
295
|
+
@d.font = @font
|
296
|
+
@d.stroke = 'transparent'
|
297
|
+
@d.pointsize = scale_fontsize(36)
|
298
|
+
@d.font_weight = BoldWeight
|
299
|
+
@d.gravity = CenterGravity
|
300
|
+
@d = @d.annotate_scaled( @base_image,
|
301
|
+
@raw_columns, 50,
|
302
|
+
0, 10,
|
303
|
+
@title, @scale)
|
304
|
+
end
|
305
|
+
|
306
|
+
def render_gradiated_background(top_color, bottom_color)
|
307
|
+
Image.new(@columns, @rows,
|
308
|
+
GradientFill.new(0, 0, 100, 0, top_color, bottom_color))
|
309
|
+
end
|
310
|
+
|
311
|
+
def current_color
|
312
|
+
@colors[@color_index]
|
313
|
+
end
|
314
|
+
|
315
|
+
def increment_color
|
316
|
+
@color_index += 1
|
317
|
+
raise(ColorlistExhaustedException, "There are no more colors left to use.") if @color_index == @colors.length
|
318
|
+
current_color
|
319
|
+
end
|
320
|
+
|
321
|
+
def reset_themes
|
322
|
+
@color_index = 0
|
323
|
+
@labels_seen = Hash.new
|
324
|
+
|
325
|
+
@d = Draw.new
|
326
|
+
# Scale down from 800x600 used to calculate drawing.
|
327
|
+
# NOTE: Font annotation is now affected and has to be done manually.
|
328
|
+
@d = @d.scale(@scale, @scale)
|
329
|
+
end
|
330
|
+
|
331
|
+
def scale(value)
|
332
|
+
value * @scale
|
333
|
+
end
|
334
|
+
|
335
|
+
def scale_fontsize(value)
|
336
|
+
new_fontsize = value * @scale
|
337
|
+
return 10 if new_fontsize < 10
|
338
|
+
return new_fontsize
|
339
|
+
end
|
340
|
+
|
341
|
+
end
|
342
|
+
|
343
|
+
class ColorlistExhaustedException < StandardError; end
|
344
|
+
|
345
|
+
|
346
|
+
end
|
347
|
+
|
348
|
+
|
349
|
+
module Magick
|
350
|
+
class Draw
|
351
|
+
|
352
|
+
# Additional method since Draw.scale doesn't affect annotations.
|
353
|
+
def annotate_scaled(img, width, height, x, y, text, scale)
|
354
|
+
scaled_width = (width * scale) >= 1 ? (width * scale) : 1
|
355
|
+
scaled_height = (height * scale) >= 1 ? (height * scale) : 1
|
356
|
+
|
357
|
+
self.annotate( img,
|
358
|
+
scaled_width, scaled_height,
|
359
|
+
x * scale, y * scale,
|
360
|
+
text)
|
361
|
+
end
|
362
|
+
|
363
|
+
end
|
364
|
+
end
|
data/lib/gruff/line.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
|
2
|
+
require 'gruff/base'
|
3
|
+
|
4
|
+
module Gruff
|
5
|
+
class Line < Base
|
6
|
+
|
7
|
+
def draw
|
8
|
+
super
|
9
|
+
|
10
|
+
@x_increment = @graph_width / (@column_count - 1).to_f
|
11
|
+
circle_radius = 5.0
|
12
|
+
|
13
|
+
@d = @d.stroke_opacity 1.0
|
14
|
+
@d = @d.stroke_width 5.0
|
15
|
+
|
16
|
+
@norm_data.each do |data_row|
|
17
|
+
prev_x = prev_y = 0.0
|
18
|
+
@d = @d.stroke current_color
|
19
|
+
@d = @d.fill current_color
|
20
|
+
|
21
|
+
data_row[1].each_with_index do |data_point, index|
|
22
|
+
# Use incremented x and scaled y
|
23
|
+
new_x = @graph_left + (@x_increment * index)
|
24
|
+
new_y = @graph_top + (@graph_height - data_point * @graph_height)
|
25
|
+
|
26
|
+
@d = @d.line(prev_x, prev_y, new_x, new_y) if (prev_x > 0) && (prev_y > 0)
|
27
|
+
@d = @d.circle(new_x, new_y, new_x - circle_radius, new_y)
|
28
|
+
|
29
|
+
draw_label(new_x, index)
|
30
|
+
|
31
|
+
prev_x = new_x
|
32
|
+
prev_y = new_y
|
33
|
+
end
|
34
|
+
|
35
|
+
increment_color()
|
36
|
+
end
|
37
|
+
|
38
|
+
@d.draw(@base_image)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# Draws column labels below graph
|
44
|
+
def draw_label(x_offset, index)
|
45
|
+
if !@labels[index].nil? && @labels_seen[index].nil?
|
46
|
+
@d.fill = @marker_color
|
47
|
+
@d.font = @font
|
48
|
+
@d.stroke = 'transparent'
|
49
|
+
@d.font_weight = NormalWeight
|
50
|
+
@d.pointsize = scale_fontsize(@marker_pointsize)
|
51
|
+
@d.gravity = CenterGravity
|
52
|
+
@d = @d.annotate_scaled(@base_image,
|
53
|
+
1, 1,
|
54
|
+
#150, 30,
|
55
|
+
x_offset, @raw_rows - 80,
|
56
|
+
@labels[index], @scale)
|
57
|
+
@labels_seen[index] = 1
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
data/lib/gruff/pie.rb
ADDED
data/rakefile
ADDED
@@ -0,0 +1,192 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
require 'rake/packagetask'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
require 'rake/contrib/rubyforgepublisher'
|
8
|
+
|
9
|
+
$:.unshift(File.dirname(__FILE__) + "/lib")
|
10
|
+
require 'gruff'
|
11
|
+
|
12
|
+
PKG_NAME = 'gruff'
|
13
|
+
PKG_VERSION = Gruff::VERSION
|
14
|
+
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
15
|
+
|
16
|
+
RELEASE_NAME = "REL #{PKG_VERSION}"
|
17
|
+
|
18
|
+
RUBY_FORGE_PROJECT = "gruff"
|
19
|
+
RUBY_FORGE_USER = "topfunky"
|
20
|
+
|
21
|
+
desc "Default Task"
|
22
|
+
task :default => [ :test ]
|
23
|
+
|
24
|
+
# Run the unit tests
|
25
|
+
Rake::TestTask.new { |t|
|
26
|
+
t.libs << "test"
|
27
|
+
t.pattern = 'test/*_test.rb'
|
28
|
+
t.verbose = true
|
29
|
+
}
|
30
|
+
|
31
|
+
|
32
|
+
# Genereate the RDoc documentation
|
33
|
+
Rake::RDocTask.new { |rdoc|
|
34
|
+
rdoc.rdoc_dir = 'doc'
|
35
|
+
rdoc.title = "Gruff -- Beautiful graphs"
|
36
|
+
rdoc.options << '--line-numbers --inline-source --main README --accessor adv_attr_accessor=M'
|
37
|
+
rdoc.template = "#{ENV['template']}.rb" if ENV['template']
|
38
|
+
rdoc.rdoc_files.include('README', 'CHANGELOG')
|
39
|
+
rdoc.rdoc_files.include('lib/gruff.rb')
|
40
|
+
rdoc.rdoc_files.include('lib/gruff/*.rb')
|
41
|
+
}
|
42
|
+
|
43
|
+
|
44
|
+
# Create compressed packages
|
45
|
+
spec = Gem::Specification.new do |s|
|
46
|
+
s.platform = Gem::Platform::RUBY
|
47
|
+
s.name = PKG_NAME
|
48
|
+
s.summary = "Beautiful graphs for one or multiple datasets."
|
49
|
+
s.description = %q{Make colorful graphs for use on websites or documents.}
|
50
|
+
s.version = PKG_VERSION
|
51
|
+
|
52
|
+
s.author = "Geoffrey Grosenbach"
|
53
|
+
s.email = "boss@topfunky.com"
|
54
|
+
s.rubyforge_project = RUBY_FORGE_PROJECT
|
55
|
+
s.homepage = "http://www.topfunky.com"
|
56
|
+
|
57
|
+
s.has_rdoc = true
|
58
|
+
s.requirements << 'none'
|
59
|
+
s.require_path = 'lib'
|
60
|
+
s.autorequire = 'gruff'
|
61
|
+
|
62
|
+
s.files = [ "rakefile", "README", "CHANGELOG", "MIT-LICENSE" ]
|
63
|
+
s.files = s.files + Dir.glob( "lib/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
64
|
+
s.files = s.files + Dir.glob( "test/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
65
|
+
end
|
66
|
+
|
67
|
+
Rake::GemPackageTask.new(spec) do |p|
|
68
|
+
p.gem_spec = spec
|
69
|
+
p.need_tar = true
|
70
|
+
p.need_zip = true
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
desc "Publish the API documentation"
|
75
|
+
task :pgem => [:package] do
|
76
|
+
Rake::SshFilePublisher.new("boss@topfunky.com", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
|
77
|
+
end
|
78
|
+
|
79
|
+
desc "Publish the release files to RubyForge."
|
80
|
+
task :release => [:package] do
|
81
|
+
files = ["gem", "tgz", "zip"].map { |ext| "pkg/#{PKG_FILE_NAME}.#{ext}" }
|
82
|
+
|
83
|
+
if RUBY_FORGE_PROJECT then
|
84
|
+
require 'net/http'
|
85
|
+
require 'open-uri'
|
86
|
+
|
87
|
+
project_uri = "http://rubyforge.org/projects/#{RUBY_FORGE_PROJECT}/"
|
88
|
+
project_data = open(project_uri) { |data| data.read }
|
89
|
+
group_id = project_data[/[?&]group_id=(\d+)/, 1]
|
90
|
+
raise "Couldn't get group id" unless group_id
|
91
|
+
|
92
|
+
# This echos password to shell which is a bit sucky
|
93
|
+
if ENV["RUBY_FORGE_PASSWORD"]
|
94
|
+
password = ENV["RUBY_FORGE_PASSWORD"]
|
95
|
+
else
|
96
|
+
print "#{RUBY_FORGE_USER}@rubyforge.org's password: "
|
97
|
+
password = STDIN.gets.chomp
|
98
|
+
end
|
99
|
+
|
100
|
+
login_response = Net::HTTP.start("rubyforge.org", 80) do |http|
|
101
|
+
data = [
|
102
|
+
"login=1",
|
103
|
+
"form_loginname=#{RUBY_FORGE_USER}",
|
104
|
+
"form_pw=#{password}"
|
105
|
+
].join("&")
|
106
|
+
http.post("/account/login.php", data)
|
107
|
+
end
|
108
|
+
|
109
|
+
cookie = login_response["set-cookie"]
|
110
|
+
raise "Login failed" unless cookie
|
111
|
+
headers = { "Cookie" => cookie }
|
112
|
+
|
113
|
+
release_uri = "http://rubyforge.org/frs/admin/?group_id=#{group_id}"
|
114
|
+
release_data = open(release_uri, headers) { |data| data.read }
|
115
|
+
package_id = release_data[/[?&]package_id=(\d+)/, 1]
|
116
|
+
raise "Couldn't get package id" unless package_id
|
117
|
+
|
118
|
+
first_file = true
|
119
|
+
release_id = ""
|
120
|
+
|
121
|
+
files.each do |filename|
|
122
|
+
basename = File.basename(filename)
|
123
|
+
file_ext = File.extname(filename)
|
124
|
+
file_data = File.open(filename, "rb") { |file| file.read }
|
125
|
+
|
126
|
+
puts "Releasing #{basename}..."
|
127
|
+
|
128
|
+
release_response = Net::HTTP.start("rubyforge.org", 80) do |http|
|
129
|
+
release_date = Time.now.strftime("%Y-%m-%d %H:%M")
|
130
|
+
type_map = {
|
131
|
+
".zip" => "3000",
|
132
|
+
".tgz" => "3110",
|
133
|
+
".gz" => "3110",
|
134
|
+
".gem" => "1400"
|
135
|
+
}; type_map.default = "9999"
|
136
|
+
type = type_map[file_ext]
|
137
|
+
boundary = "rubyqMY6QN9bp6e4kS21H4y0zxcvoor"
|
138
|
+
|
139
|
+
query_hash = if first_file then
|
140
|
+
{
|
141
|
+
"group_id" => group_id,
|
142
|
+
"package_id" => package_id,
|
143
|
+
"release_name" => RELEASE_NAME,
|
144
|
+
"release_date" => release_date,
|
145
|
+
"type_id" => type,
|
146
|
+
"processor_id" => "8000", # Any
|
147
|
+
"release_notes" => "",
|
148
|
+
"release_changes" => "",
|
149
|
+
"preformatted" => "1",
|
150
|
+
"submit" => "1"
|
151
|
+
}
|
152
|
+
else
|
153
|
+
{
|
154
|
+
"group_id" => group_id,
|
155
|
+
"release_id" => release_id,
|
156
|
+
"package_id" => package_id,
|
157
|
+
"step2" => "1",
|
158
|
+
"type_id" => type,
|
159
|
+
"processor_id" => "8000", # Any
|
160
|
+
"submit" => "Add This File"
|
161
|
+
}
|
162
|
+
end
|
163
|
+
|
164
|
+
query = "?" + query_hash.map do |(name, value)|
|
165
|
+
[name, URI.encode(value)].join("=")
|
166
|
+
end.join("&")
|
167
|
+
|
168
|
+
data = [
|
169
|
+
"--" + boundary,
|
170
|
+
"Content-Disposition: form-data; name=\"userfile\"; filename=\"#{basename}\"",
|
171
|
+
"Content-Type: application/octet-stream",
|
172
|
+
"Content-Transfer-Encoding: binary",
|
173
|
+
"", file_data, ""
|
174
|
+
].join("\x0D\x0A")
|
175
|
+
|
176
|
+
release_headers = headers.merge(
|
177
|
+
"Content-Type" => "multipart/form-data; boundary=#{boundary}"
|
178
|
+
)
|
179
|
+
|
180
|
+
target = first_file ? "/frs/admin/qrs.php" : "/frs/admin/editrelease.php"
|
181
|
+
http.post(target + query, data, release_headers)
|
182
|
+
end
|
183
|
+
|
184
|
+
if first_file then
|
185
|
+
release_id = release_response.body[/release_id=(\d+)/, 1]
|
186
|
+
raise("Couldn't get release id") unless release_id
|
187
|
+
end
|
188
|
+
|
189
|
+
first_file = false
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
data/test/line_test.rb
ADDED
@@ -0,0 +1,250 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
$:.unshift(File.dirname(__FILE__) + "/../lib/")
|
4
|
+
#$:.unshift File.dirname(__FILE__) + "/fixtures/helpers"
|
5
|
+
|
6
|
+
require 'test/unit'
|
7
|
+
require 'gruff'
|
8
|
+
|
9
|
+
class TestGruffLines < Test::Unit::TestCase
|
10
|
+
|
11
|
+
# TODO Delete old output files once when starting tests
|
12
|
+
|
13
|
+
def setup
|
14
|
+
@datasets = [
|
15
|
+
[:Jimmy, [25, 36, 86, 39, 25, 31, 79, 88]],
|
16
|
+
[:Charles, [80, 54, 67, 54, 68, 70, 90, 95]],
|
17
|
+
[:Julie, [22, 29, 35, 38, 36, 40, 46, 57]],
|
18
|
+
[:Jane, [95, 95, 95, 90, 85, 80, 88, 100]],
|
19
|
+
[:Philip, [90, 34, 23, 12, 78, 89, 98, 88]],
|
20
|
+
["Arthur", [5, 10, 13, 11, 6, 16, 22, 32]],
|
21
|
+
]
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_line_graph_with_themes
|
25
|
+
g = Gruff::Line.new
|
26
|
+
g.title = "Visual Multi-Line Graph Test"
|
27
|
+
g.labels = {
|
28
|
+
0 => '5/6',
|
29
|
+
2 => '5/15',
|
30
|
+
4 => '5/24',
|
31
|
+
6 => '5/30',
|
32
|
+
}
|
33
|
+
@datasets.each do |data|
|
34
|
+
g.data(data[0], data[1])
|
35
|
+
end
|
36
|
+
|
37
|
+
# Default theme
|
38
|
+
g.write("test/output/line_keynote.png")
|
39
|
+
|
40
|
+
g.theme_37signals
|
41
|
+
g.write("test/output/line_37signals.png")
|
42
|
+
|
43
|
+
g.theme_rails_keynote
|
44
|
+
g.write("test/output/line_rails_keynote.png")
|
45
|
+
|
46
|
+
g.theme_odeo
|
47
|
+
g.write("test/output/line_odeo.png")
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_line_small_values
|
51
|
+
@datasets = [
|
52
|
+
[:small, [0.25, 0.14356, 0.0, 0.5674839, 0.456]],
|
53
|
+
[:small2, [0.2, 0.3, 0.1, 0.05, 0.9]]
|
54
|
+
]
|
55
|
+
|
56
|
+
g = Gruff::Line.new
|
57
|
+
g.title = "Small Values Line Graph Test"
|
58
|
+
@datasets.each do |data|
|
59
|
+
g.data(data[0], data[1])
|
60
|
+
end
|
61
|
+
|
62
|
+
g.write("test/output/line_small.png")
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_line_large_values
|
66
|
+
@datasets = [
|
67
|
+
[:large, [100_005, 35_000, 28_000, 27_000]],
|
68
|
+
[:large2, [35_000, 28_000, 27_000, 100_005]],
|
69
|
+
[:large3, [28_000, 27_000, 100_005, 35_000]],
|
70
|
+
[:large4, [1_238, 39_092, 27_938, 48_876]]
|
71
|
+
]
|
72
|
+
|
73
|
+
g = Gruff::Line.new
|
74
|
+
g.title = "Very Large Values Line Graph Test"
|
75
|
+
@datasets.each do |data|
|
76
|
+
g.data(data[0], data[1])
|
77
|
+
end
|
78
|
+
|
79
|
+
g.write("test/output/line_large.png")
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_resize
|
83
|
+
g = Gruff::Line.new(400)
|
84
|
+
g.title = "Small Size Multi-Line Graph Test"
|
85
|
+
g.labels = {
|
86
|
+
0 => '5/6',
|
87
|
+
2 => '5/15',
|
88
|
+
4 => '5/24',
|
89
|
+
6 => '5/30',
|
90
|
+
}
|
91
|
+
@datasets.each do |data|
|
92
|
+
g.data(data[0], data[1])
|
93
|
+
end
|
94
|
+
|
95
|
+
# Default theme
|
96
|
+
g.write("test/output/line_keynote_small.png")
|
97
|
+
|
98
|
+
g.theme_37signals
|
99
|
+
g.write("test/output/line_37signals_small.png")
|
100
|
+
|
101
|
+
g.theme_rails_keynote
|
102
|
+
g.write("test/output/line_rails_keynote_small.png")
|
103
|
+
|
104
|
+
g.theme_odeo
|
105
|
+
g.write("test/output/line_odeo_small.png")
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_long_title
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_add_colors
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_request_too_many_colors
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_add_data
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_many_datapoints
|
125
|
+
g = Gruff::Line.new
|
126
|
+
g.title = "Many Multi-Line Graph Test"
|
127
|
+
g.labels = {
|
128
|
+
0 => 'June',
|
129
|
+
10 => 'July',
|
130
|
+
30 => 'August',
|
131
|
+
50 => 'September',
|
132
|
+
}
|
133
|
+
g.data('many points', (0..50).collect {|i| rand(100) })
|
134
|
+
|
135
|
+
# Default theme
|
136
|
+
g.write("test/output/line_many.png")
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
def test_similar_high_end_values
|
141
|
+
g = Gruff::Line.new
|
142
|
+
g.title = "Similar High End Values Test"
|
143
|
+
g.data('similar points', [100, 98, 99, 97, 96, 95, 96, 97, 98, 99, 100, 96] )
|
144
|
+
|
145
|
+
# Default theme
|
146
|
+
g.write("test/output/similar_high_end_values.png")
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
# Requires the Creative Block font from http://www.blambot.com
|
151
|
+
def test_font
|
152
|
+
g = Gruff::Line.new
|
153
|
+
g.title = "Font Test"
|
154
|
+
g.labels = {
|
155
|
+
0 => '5/6',
|
156
|
+
2 => '5/15',
|
157
|
+
4 => '5/24',
|
158
|
+
6 => '5/30',
|
159
|
+
8 => '6/2',
|
160
|
+
10 => '6/4',
|
161
|
+
}
|
162
|
+
g.data('many points', (0..10).collect {|i| rand(100) })
|
163
|
+
g.font = File.expand_path('CREABBRG.TTF', ENV['MAGICK_FONT_PATH'])
|
164
|
+
|
165
|
+
# Default theme
|
166
|
+
g.write("test/output/line_font.png")
|
167
|
+
end
|
168
|
+
|
169
|
+
=begin
|
170
|
+
def test_very_small_graphs
|
171
|
+
[300, 200, 100].each do |size|
|
172
|
+
g = Gruff::Line.new(size)
|
173
|
+
g.title = "#{size}px Graph Test"
|
174
|
+
g.labels = {
|
175
|
+
0 => '5/6',
|
176
|
+
2 => '5/15',
|
177
|
+
4 => '5/24',
|
178
|
+
6 => '5/30',
|
179
|
+
}
|
180
|
+
@datasets.each do |data|
|
181
|
+
g.data(data[0], data[1])
|
182
|
+
end
|
183
|
+
|
184
|
+
# Default theme
|
185
|
+
g.write("test/output/line_#{size}.png")
|
186
|
+
end
|
187
|
+
end
|
188
|
+
=end
|
189
|
+
|
190
|
+
=begin
|
191
|
+
def test_for_blog
|
192
|
+
g = Gruff::Line.new
|
193
|
+
g.labels = {
|
194
|
+
0 => '2002',
|
195
|
+
2 => '2003',
|
196
|
+
4 => '2004',
|
197
|
+
6 => '2005',
|
198
|
+
}
|
199
|
+
@datasets.each do |data|
|
200
|
+
g.data(data[0], data[1])
|
201
|
+
end
|
202
|
+
|
203
|
+
# Default theme
|
204
|
+
g.title = "Gruff...graphs"
|
205
|
+
g.write("test/output/1.png")
|
206
|
+
|
207
|
+
g.theme_odeo
|
208
|
+
g.title = "Themes, Colors, and Fonts!"
|
209
|
+
g.font = File.expand_path('CREABBRG.TTF', ENV['MAGICK_FONT_PATH'])
|
210
|
+
g.write("test/output/2.png")
|
211
|
+
|
212
|
+
g.theme_rails_keynote
|
213
|
+
g.title = "Source Coming Soon..."
|
214
|
+
g.font = File.expand_path('Vera.ttf', ENV['MAGICK_FONT_PATH'])
|
215
|
+
g.write("test/output/3.png")
|
216
|
+
|
217
|
+
end
|
218
|
+
|
219
|
+
def test_for_blog_small
|
220
|
+
g = Gruff::Line.new(400)
|
221
|
+
g.labels = {
|
222
|
+
0 => '2002',
|
223
|
+
2 => '2003',
|
224
|
+
4 => '2004',
|
225
|
+
6 => '2005',
|
226
|
+
}
|
227
|
+
@datasets.each do |data|
|
228
|
+
g.data(data[0], data[1])
|
229
|
+
end
|
230
|
+
|
231
|
+
# Default theme
|
232
|
+
g.title = "Gruff...graphs"
|
233
|
+
g.write("test/output/1_small.png")
|
234
|
+
|
235
|
+
g.theme_odeo
|
236
|
+
g.title = "Themes, Colors, and Fonts!"
|
237
|
+
g.font = File.expand_path('CREABBRG.TTF', ENV['MAGICK_FONT_PATH'])
|
238
|
+
g.write("test/output/2_small.png")
|
239
|
+
|
240
|
+
g.theme_rails_keynote
|
241
|
+
g.title = "Source Coming Soon..."
|
242
|
+
g.font = File.expand_path('Vera.ttf', ENV['MAGICK_FONT_PATH'])
|
243
|
+
g.write("test/output/3_small.png")
|
244
|
+
|
245
|
+
end
|
246
|
+
=end
|
247
|
+
|
248
|
+
|
249
|
+
end
|
250
|
+
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.11
|
3
|
+
specification_version: 1
|
4
|
+
name: gruff
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.0.1
|
7
|
+
date: 2005-10-25 00:00:00 -07:00
|
8
|
+
summary: Beautiful graphs for one or multiple datasets.
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: boss@topfunky.com
|
12
|
+
homepage: http://www.topfunky.com
|
13
|
+
rubyforge_project: gruff
|
14
|
+
description: Make colorful graphs for use on websites or documents.
|
15
|
+
autorequire: gruff
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
-
|
22
|
+
- ">"
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 0.0.0
|
25
|
+
version:
|
26
|
+
platform: ruby
|
27
|
+
signing_key:
|
28
|
+
cert_chain:
|
29
|
+
authors:
|
30
|
+
- Geoffrey Grosenbach
|
31
|
+
files:
|
32
|
+
- rakefile
|
33
|
+
- README
|
34
|
+
- CHANGELOG
|
35
|
+
- MIT-LICENSE
|
36
|
+
- lib/gruff
|
37
|
+
- lib/gruff.rb
|
38
|
+
- lib/gruff/area.rb
|
39
|
+
- lib/gruff/bar.rb
|
40
|
+
- lib/gruff/base.rb
|
41
|
+
- lib/gruff/line.rb
|
42
|
+
- lib/gruff/pie.rb
|
43
|
+
- test/line_test.rb
|
44
|
+
- test/output
|
45
|
+
- test/output/line_37signals.png
|
46
|
+
- test/output/line_37signals_small.png
|
47
|
+
- test/output/line_font.png
|
48
|
+
- test/output/line_keynote.png
|
49
|
+
- test/output/line_keynote_small.png
|
50
|
+
- test/output/line_large.png
|
51
|
+
- test/output/line_many.png
|
52
|
+
- test/output/line_odeo.png
|
53
|
+
- test/output/line_odeo_small.png
|
54
|
+
- test/output/line_rails_keynote.png
|
55
|
+
- test/output/line_rails_keynote_small.png
|
56
|
+
- test/output/line_small.png
|
57
|
+
- test/output/similar_high_end_values.png
|
58
|
+
test_files: []
|
59
|
+
rdoc_options: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
executables: []
|
62
|
+
extensions: []
|
63
|
+
requirements:
|
64
|
+
- none
|
65
|
+
dependencies: []
|