gruff 0.0.6 → 0.0.7
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/assets/plastik-yellow.png +0 -0
- data/lib/gruff.rb +1 -0
- data/lib/gruff/base.rb +42 -12
- data/lib/gruff/line.rb +27 -5
- data/lib/gruff/stacked_bar.rb +59 -0
- data/test/line_test.rb +59 -51
- data/test/stacked_bar_test.rb +116 -0
- metadata +5 -2
Binary file
|
data/lib/gruff.rb
CHANGED
data/lib/gruff/base.rb
CHANGED
@@ -7,17 +7,13 @@
|
|
7
7
|
#
|
8
8
|
#
|
9
9
|
|
10
|
-
|
11
|
-
require 'rmagick'
|
12
|
-
rescue LoadError
|
13
|
-
require 'RMagick' # capitalized on Windows
|
14
|
-
end
|
10
|
+
require 'RMagick'
|
15
11
|
|
16
12
|
require 'yaml'
|
17
13
|
|
18
14
|
module Gruff
|
19
15
|
|
20
|
-
VERSION = '0.0.
|
16
|
+
VERSION = '0.0.7'
|
21
17
|
|
22
18
|
class Base
|
23
19
|
|
@@ -170,8 +166,11 @@ module Gruff
|
|
170
166
|
@column_count = (data_points.length > @column_count) ? data_points.length : @column_count
|
171
167
|
|
172
168
|
# Pre-normalize
|
173
|
-
data_points.
|
174
|
-
|
169
|
+
data_points.each_with_index do |data_point, index|
|
170
|
+
next if data_point.nil?
|
171
|
+
|
172
|
+
@maximum_value = larger_than_max?(data_point, index) ? max(data_point, index) : @maximum_value
|
173
|
+
# @maximum_value = (data_point > @maximum_value) ? data_point : @maximum_value
|
175
174
|
if @maximum_value > 0
|
176
175
|
@has_data = true
|
177
176
|
end
|
@@ -211,7 +210,11 @@ protected
|
|
211
210
|
# - legend
|
212
211
|
# - title
|
213
212
|
def setup_drawing
|
214
|
-
|
213
|
+
# Maybe should be done in one of the following functions for more granularity.
|
214
|
+
unless @has_data
|
215
|
+
draw_no_data()
|
216
|
+
return
|
217
|
+
end
|
215
218
|
|
216
219
|
normalize()
|
217
220
|
setup_graph_measurements()
|
@@ -230,7 +233,11 @@ protected
|
|
230
233
|
@data.each do |data_row|
|
231
234
|
norm_data_points = Array.new
|
232
235
|
data_row[1].each do |data_point|
|
233
|
-
|
236
|
+
if data_point.nil?
|
237
|
+
norm_data_points << nil
|
238
|
+
else
|
239
|
+
norm_data_points << (data_point.to_f/@maximum_value.to_f)
|
240
|
+
end
|
234
241
|
end
|
235
242
|
@norm_data << [data_row[0], norm_data_points]
|
236
243
|
end
|
@@ -248,7 +255,7 @@ protected
|
|
248
255
|
# Draws horizontal background lines and labels
|
249
256
|
def draw_line_markers
|
250
257
|
return unless @show_line_markers
|
251
|
-
|
258
|
+
|
252
259
|
# Draw horizontal line markers and annotate with numbers
|
253
260
|
@d = @d.stroke(@marker_color)
|
254
261
|
@d = @d.stroke_width 1
|
@@ -362,6 +369,19 @@ protected
|
|
362
369
|
end
|
363
370
|
end
|
364
371
|
|
372
|
+
def draw_no_data
|
373
|
+
@d.fill = @marker_color
|
374
|
+
@d.font = @font
|
375
|
+
@d.stroke = 'transparent'
|
376
|
+
@d.font_weight = NormalWeight
|
377
|
+
@d.pointsize = scale_fontsize(80)
|
378
|
+
@d.gravity = CenterGravity
|
379
|
+
@d = @d.annotate_scaled( @base_image,
|
380
|
+
@raw_columns, @raw_rows/2.0,
|
381
|
+
0, 10,
|
382
|
+
'No Data', @scale)
|
383
|
+
end
|
384
|
+
|
365
385
|
# Use with a theme definition method to draw a gradiated (or solid color) background.
|
366
386
|
def render_gradiated_background(top_color, bottom_color)
|
367
387
|
Image.new(@columns, @rows,
|
@@ -411,6 +431,16 @@ protected
|
|
411
431
|
(value > max_value) ? max_value : value
|
412
432
|
end
|
413
433
|
|
434
|
+
# Overridden by subclasses such as stacked bar.
|
435
|
+
def larger_than_max?(data_point, index=0)
|
436
|
+
data_point > @maximum_value
|
437
|
+
end
|
438
|
+
|
439
|
+
def max(data_point, index)
|
440
|
+
data_point
|
441
|
+
end
|
442
|
+
|
443
|
+
|
414
444
|
# round down to significant digits
|
415
445
|
def significant(inc)
|
416
446
|
factor = 1.0
|
@@ -440,7 +470,7 @@ protected
|
|
440
470
|
|
441
471
|
def sums(data_set)
|
442
472
|
total_sum = 0
|
443
|
-
data_set.collect {|num| total_sum += num }
|
473
|
+
data_set.collect {|num| total_sum += num.to_f }
|
444
474
|
total_sum
|
445
475
|
end
|
446
476
|
|
data/lib/gruff/line.rb
CHANGED
@@ -3,6 +3,12 @@ require File.dirname(__FILE__) + '/base'
|
|
3
3
|
|
4
4
|
class Gruff::Line < Gruff::Base
|
5
5
|
|
6
|
+
# Draw a dashed line at the given value
|
7
|
+
attr_accessor :baseline_value
|
8
|
+
|
9
|
+
# Color of the baseline
|
10
|
+
attr_accessor :baseline_color
|
11
|
+
|
6
12
|
#
|
7
13
|
# Call with target pixel width of graph (800, 400, 300), and/or 'false' to omit lines (points only).
|
8
14
|
#
|
@@ -19,6 +25,7 @@ class Gruff::Line < Gruff::Base
|
|
19
25
|
super args.shift
|
20
26
|
end
|
21
27
|
@lines = args.empty? || args.shift # draw lines by default
|
28
|
+
@baseline_color = 'red'
|
22
29
|
end
|
23
30
|
|
24
31
|
def draw
|
@@ -31,15 +38,29 @@ class Gruff::Line < Gruff::Base
|
|
31
38
|
|
32
39
|
@d = @d.stroke_opacity 1.0
|
33
40
|
@d = @d.stroke_width clip_value_if_greater_than(@columns / (@norm_data.first[1].size * 4), 5.0)
|
34
|
-
|
41
|
+
|
42
|
+
if (defined?(@norm_baseline)) then
|
43
|
+
level = @graph_top + (@graph_height - @norm_baseline * @graph_height)
|
44
|
+
@d = @d.push
|
45
|
+
@d.stroke_color @baseline_color
|
46
|
+
@d.fill_opacity 0.0
|
47
|
+
@d.stroke_dasharray(10, 20)
|
48
|
+
@d.stroke_width 5
|
49
|
+
@d.line(@graph_left, level, @graph_left + @graph_width, level)
|
50
|
+
@d = @d.pop
|
51
|
+
end
|
52
|
+
|
35
53
|
@norm_data.each do |data_row|
|
36
54
|
prev_x = prev_y = 0.0
|
37
55
|
@d = @d.stroke current_color
|
38
56
|
@d = @d.fill current_color
|
39
57
|
|
40
58
|
data_row[1].each_with_index do |data_point, index|
|
41
|
-
# Use incremented x and scaled y
|
42
59
|
new_x = @graph_left + (@x_increment * index)
|
60
|
+
draw_label(new_x, index)
|
61
|
+
|
62
|
+
next if data_point.nil?
|
63
|
+
|
43
64
|
new_y = @graph_top + (@graph_height - data_point * @graph_height)
|
44
65
|
|
45
66
|
if @lines and prev_x > 0 and prev_y > 0 then
|
@@ -47,8 +68,6 @@ class Gruff::Line < Gruff::Base
|
|
47
68
|
end
|
48
69
|
@d = @d.circle(new_x, new_y, new_x - circle_radius, new_y)
|
49
70
|
|
50
|
-
draw_label(new_x, index)
|
51
|
-
|
52
71
|
prev_x = new_x
|
53
72
|
prev_y = new_y
|
54
73
|
end
|
@@ -59,5 +78,8 @@ class Gruff::Line < Gruff::Base
|
|
59
78
|
@d.draw(@base_image)
|
60
79
|
end
|
61
80
|
|
62
|
-
|
81
|
+
def normalize
|
82
|
+
super
|
83
|
+
@norm_baseline = (@baseline_value.to_f / @maximum_value.to_f) if @baseline_value
|
84
|
+
end
|
63
85
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
|
2
|
+
require File.dirname(__FILE__) + '/base'
|
3
|
+
|
4
|
+
class Gruff::StackedBar < Gruff::Base
|
5
|
+
|
6
|
+
def draw
|
7
|
+
super
|
8
|
+
|
9
|
+
return unless @has_data
|
10
|
+
|
11
|
+
# Setup spacing.
|
12
|
+
#
|
13
|
+
# Columns sit stacked.
|
14
|
+
spacing_factor = 0.9
|
15
|
+
@bar_width = @graph_width / @column_count.to_f
|
16
|
+
|
17
|
+
@d = @d.stroke_opacity 0.0
|
18
|
+
|
19
|
+
height = Array.new(@column_count, 0)
|
20
|
+
|
21
|
+
@norm_data.each_with_index do |data_row, row_index|
|
22
|
+
@d = @d.fill current_color
|
23
|
+
|
24
|
+
data_row[1].each_with_index do |data_point, point_index|
|
25
|
+
# Use incremented x and scaled y
|
26
|
+
left_x = @graph_left + (@bar_width * point_index)
|
27
|
+
left_y = @graph_top + (@graph_height -
|
28
|
+
data_point * @graph_height -
|
29
|
+
height[point_index]) + 1
|
30
|
+
right_x = left_x + @bar_width * spacing_factor
|
31
|
+
right_y = @graph_top + @graph_height - height[point_index] - 1
|
32
|
+
|
33
|
+
# update the total height of the current stacked bar
|
34
|
+
height[point_index] += (data_point * @graph_height - 2)
|
35
|
+
|
36
|
+
@d = @d.rectangle(left_x, left_y, right_x, right_y)
|
37
|
+
|
38
|
+
# Calculate center based on bar_width and current row
|
39
|
+
label_center = @graph_left + (@bar_width * point_index) + (@bar_width * spacing_factor / 2.0)
|
40
|
+
draw_label(label_center, point_index)
|
41
|
+
end
|
42
|
+
|
43
|
+
increment_color()
|
44
|
+
end
|
45
|
+
|
46
|
+
@d.draw(@base_image)
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
def larger_than_max?(data_point, index=0)
|
52
|
+
max(data_point, index) > @maximum_value
|
53
|
+
end
|
54
|
+
|
55
|
+
def max(data_point, index)
|
56
|
+
@data.inject(0) {|sum, item| sum + item[1][index]}
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
data/test/line_test.rb
CHANGED
@@ -22,29 +22,8 @@ class TestGruffLine < Test::Unit::TestCase
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def test_line_graph_with_themes
|
25
|
-
|
26
|
-
|
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")
|
25
|
+
line_graph_with_themes()
|
26
|
+
line_graph_with_themes(400)
|
48
27
|
end
|
49
28
|
|
50
29
|
def test_line_small_values
|
@@ -80,39 +59,15 @@ class TestGruffLine < Test::Unit::TestCase
|
|
80
59
|
|
81
60
|
g = Gruff::Line.new
|
82
61
|
g.title = "Very Large Values Line Graph Test"
|
62
|
+
g.baseline_value = 50_000
|
63
|
+
g.baseline_color = 'green'
|
83
64
|
@datasets.each do |data|
|
84
65
|
g.data(data[0], data[1])
|
85
66
|
end
|
86
67
|
|
87
68
|
g.write("test/output/line_large.png")
|
88
69
|
end
|
89
|
-
|
90
|
-
def test_resize
|
91
|
-
g = Gruff::Line.new(400)
|
92
|
-
g.title = "Small Size Multi-Line Graph Test"
|
93
|
-
g.labels = {
|
94
|
-
0 => '5/6',
|
95
|
-
2 => '5/15',
|
96
|
-
4 => '5/24',
|
97
|
-
6 => '5/30',
|
98
|
-
}
|
99
|
-
@datasets.each do |data|
|
100
|
-
g.data(data[0], data[1])
|
101
|
-
end
|
102
|
-
|
103
|
-
# Default theme
|
104
|
-
g.write("test/output/line_keynote_small.png")
|
105
70
|
|
106
|
-
g.theme_37signals
|
107
|
-
g.write("test/output/line_37signals_small.png")
|
108
|
-
|
109
|
-
g.theme_rails_keynote
|
110
|
-
g.write("test/output/line_rails_keynote_small.png")
|
111
|
-
|
112
|
-
g.theme_odeo
|
113
|
-
g.write("test/output/line_odeo_small.png")
|
114
|
-
end
|
115
|
-
|
116
71
|
def test_long_title
|
117
72
|
|
118
73
|
end
|
@@ -260,9 +215,9 @@ class TestGruffLine < Test::Unit::TestCase
|
|
260
215
|
end
|
261
216
|
|
262
217
|
|
263
|
-
def
|
218
|
+
def test_all_zeros
|
264
219
|
g = Gruff::Line.new(400)
|
265
|
-
g.title = "
|
220
|
+
g.title = "All Zeros"
|
266
221
|
|
267
222
|
g.data(:gus, [0,0,0,0])
|
268
223
|
|
@@ -270,6 +225,59 @@ class TestGruffLine < Test::Unit::TestCase
|
|
270
225
|
g.write("test/output/line_no_data_other.png")
|
271
226
|
end
|
272
227
|
|
228
|
+
|
229
|
+
def test_some_nil_points
|
230
|
+
g = Gruff::Line.new
|
231
|
+
g.title = "Some Nil Points"
|
232
|
+
|
233
|
+
@datasets = [
|
234
|
+
[:data1, [1, 2, 3, nil, 3, 5, 6]],
|
235
|
+
[:data2, [5, nil, nil, nil, nil, nil, 5]],
|
236
|
+
[:data3, [4, nil, 2, 1, 0]]
|
237
|
+
]
|
238
|
+
|
239
|
+
@datasets.each do |data|
|
240
|
+
g.data(*data)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Default theme
|
244
|
+
g.write("test/output/line_some_nil_points.png")
|
245
|
+
end
|
246
|
+
|
247
|
+
protected
|
248
|
+
|
249
|
+
def line_graph_with_themes(size=nil)
|
250
|
+
if !size.nil?
|
251
|
+
g = Gruff::Line.new(size)
|
252
|
+
else
|
253
|
+
g = Gruff::Line.new
|
254
|
+
end
|
255
|
+
|
256
|
+
g.title = "Multi-Line Graph Test #{size}"
|
257
|
+
g.labels = {
|
258
|
+
0 => '5/6',
|
259
|
+
2 => '5/15',
|
260
|
+
4 => '5/24',
|
261
|
+
6 => '5/30',
|
262
|
+
}
|
263
|
+
@datasets.each do |data|
|
264
|
+
g.data(data[0], data[1])
|
265
|
+
end
|
266
|
+
|
267
|
+
g.baseline_value = 90
|
268
|
+
|
269
|
+
# Default theme
|
270
|
+
g.write("test/output/line_theme_keynote_#{size}.png")
|
271
|
+
|
272
|
+
g.theme_37signals
|
273
|
+
g.write("test/output/line_theme_37signals_#{size}.png")
|
274
|
+
|
275
|
+
g.theme_rails_keynote
|
276
|
+
g.write("test/output/line_theme_rails_keynote_#{size}.png")
|
277
|
+
|
278
|
+
g.theme_odeo
|
279
|
+
g.write("test/output/line_theme_odeo_#{size}.png")
|
280
|
+
end
|
273
281
|
|
274
282
|
end
|
275
283
|
|
@@ -0,0 +1,116 @@
|
|
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 TestGruffStackedBar < 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]],
|
16
|
+
[:Charles, [80, 54, 67, 54]],
|
17
|
+
[:Julie, [22, 29, 35, 38]],
|
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_bar_graph
|
25
|
+
g = Gruff::StackedBar.new
|
26
|
+
g.title = "Visual Stacked Bar Graph Test"
|
27
|
+
g.labels = {
|
28
|
+
0 => '5/6',
|
29
|
+
1 => '5/15',
|
30
|
+
2 => '5/24',
|
31
|
+
3 => '5/30',
|
32
|
+
}
|
33
|
+
@datasets.each do |data|
|
34
|
+
g.data(data[0], data[1])
|
35
|
+
end
|
36
|
+
|
37
|
+
g.write("test/output/stacked_bar_keynote.png")
|
38
|
+
|
39
|
+
g.theme_rails_keynote
|
40
|
+
g.write("test/output/stacked_bar_rails_keynote.png")
|
41
|
+
|
42
|
+
g.theme_odeo
|
43
|
+
g.write("test/output/stacked_bar_odeo.png")
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
def test_bar_graph_small
|
48
|
+
g = Gruff::StackedBar.new(400)
|
49
|
+
g.title = "Visual Stacked Bar Graph Test"
|
50
|
+
g.labels = {
|
51
|
+
0 => '5/6',
|
52
|
+
1 => '5/15',
|
53
|
+
2 => '5/24',
|
54
|
+
3 => '5/30',
|
55
|
+
}
|
56
|
+
@datasets.each do |data|
|
57
|
+
g.data(data[0], data[1])
|
58
|
+
end
|
59
|
+
|
60
|
+
g.write("test/output/stacked_bar_keynote_small.png")
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_bar_image_bg
|
64
|
+
g = Gruff::StackedBar.new
|
65
|
+
g.title = "Stacked Bar Graph With Image Background"
|
66
|
+
g.labels = {
|
67
|
+
0 => '5/6',
|
68
|
+
1 => '5/15',
|
69
|
+
2 => '5/24',
|
70
|
+
3 => '5/30',
|
71
|
+
}
|
72
|
+
@datasets.each do |data|
|
73
|
+
g.data(data[0], data[1])
|
74
|
+
end
|
75
|
+
|
76
|
+
g.theme_image_based
|
77
|
+
g.write("test/output/stacked_bar_image.png")
|
78
|
+
|
79
|
+
g = Gruff::StackedBar.new(400)
|
80
|
+
g.title = "Stacked Bar Graph With Image Background Small"
|
81
|
+
g.labels = {
|
82
|
+
0 => '5/6',
|
83
|
+
1 => '5/15',
|
84
|
+
2 => '5/24',
|
85
|
+
3 => '5/30',
|
86
|
+
}
|
87
|
+
@datasets.each do |data|
|
88
|
+
g.data(data[0], data[1])
|
89
|
+
end
|
90
|
+
|
91
|
+
g.theme_image_based
|
92
|
+
g.write("test/output/stacked_bar_image_small.png")
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
class Gruff::Base
|
100
|
+
def theme_image_based
|
101
|
+
reset_themes()
|
102
|
+
# Colors
|
103
|
+
@green = '#00ff00'
|
104
|
+
@grey = '#333333'
|
105
|
+
@orange = '#ff5d00'
|
106
|
+
@red = '#f61100'
|
107
|
+
@white = 'white'
|
108
|
+
@light_grey = '#999999'
|
109
|
+
@black = 'black'
|
110
|
+
@colors = [@green, @grey, @orange, @red, @white, @light_grey, @black]
|
111
|
+
|
112
|
+
@marker_color = 'white'
|
113
|
+
|
114
|
+
@base_image = render_image_background('assets/bubble.png')
|
115
|
+
end
|
116
|
+
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
|
|
3
3
|
specification_version: 1
|
4
4
|
name: gruff
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.0.
|
7
|
-
date: 2005-
|
6
|
+
version: 0.0.7
|
7
|
+
date: 2005-12-04 00:00:00 -08:00
|
8
8
|
summary: Beautiful graphs for one or multiple datasets.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -40,13 +40,16 @@ files:
|
|
40
40
|
- lib/gruff/base.rb
|
41
41
|
- lib/gruff/line.rb
|
42
42
|
- lib/gruff/pie.rb
|
43
|
+
- lib/gruff/stacked_bar.rb
|
43
44
|
- assets/bubble.png
|
44
45
|
- assets/plastik
|
46
|
+
- assets/plastik-yellow.png
|
45
47
|
- test/area_test.rb
|
46
48
|
- test/bar_test.rb
|
47
49
|
- test/line_test.rb
|
48
50
|
- test/output
|
49
51
|
- test/pie_test.rb
|
52
|
+
- test/stacked_bar_test.rb
|
50
53
|
test_files: []
|
51
54
|
rdoc_options: []
|
52
55
|
extra_rdoc_files: []
|