sankey 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.rdoc +11 -0
- data/Rakefile +23 -0
- data/VERSION +1 -0
- data/examples/coffee.rb +23 -0
- data/lib/generator.rb +233 -0
- data/lib/imagedata.rb +11 -0
- data/lib/point.rb +20 -0
- data/lib/process.rb +83 -0
- data/lib/processvertex.rb +18 -0
- data/lib/reagent.rb +40 -0
- data/lib/renderers/renderer.rb +7 -0
- data/lib/renderers/rvg.rb +65 -0
- data/lib/sankey.rb +3 -0
- data/lib/vertex.rb +9 -0
- data/sankey.gemspec +59 -0
- metadata +80 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Jan Stępień
|
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.
|
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
desc "Run rdoc"
|
5
|
+
task :rdoc do
|
6
|
+
print `rdoc -a -S -N -w 2 -U -o doc/rdoc README.rdoc lib`
|
7
|
+
end
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'jeweler'
|
11
|
+
Jeweler::Tasks.new do |gemspec|
|
12
|
+
gemspec.name = 'sankey'
|
13
|
+
gemspec.summary = 'Sankey diagrams generator'
|
14
|
+
gemspec.description = 'A small tool which generates Sankey diagrams.'
|
15
|
+
gemspec.email = 'jstepien@users.sourceforge.net'
|
16
|
+
gemspec.homepage = "http://github.com/jstepien/sankey"
|
17
|
+
gemspec.authors = ['Jan Stępień']
|
18
|
+
gemspec.files.exclude '.gitignore'
|
19
|
+
gemspec.add_dependency 'rmagick'
|
20
|
+
end
|
21
|
+
rescue LoadError
|
22
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
23
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.0
|
data/examples/coffee.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/ruby -I../lib
|
2
|
+
require 'sankey'
|
3
|
+
require 'renderers/rvg'
|
4
|
+
|
5
|
+
g = Sankey::Generator.new
|
6
|
+
beans = Sankey::Reagent.new 100
|
7
|
+
ground_coffee = Sankey::Reagent.new 100
|
8
|
+
water = Sankey::Reagent.new 200
|
9
|
+
beverage = Sankey::Reagent.new 280
|
10
|
+
crema = Sankey::Reagent.new 20
|
11
|
+
espresso_machine = Sankey::Process.new
|
12
|
+
grinder = Sankey::Process.new
|
13
|
+
beans.drain = grinder
|
14
|
+
water.drain = espresso_machine
|
15
|
+
ground_coffee.drain = espresso_machine
|
16
|
+
ground_coffee.source = grinder
|
17
|
+
beverage.source = espresso_machine
|
18
|
+
crema.source = espresso_machine
|
19
|
+
g.processes.push espresso_machine
|
20
|
+
g.processes.push grinder
|
21
|
+
g.go!
|
22
|
+
p = Sankey::Renderers::RVG.new g.data, "espresso.png"
|
23
|
+
p.render
|
data/lib/generator.rb
ADDED
@@ -0,0 +1,233 @@
|
|
1
|
+
require 'processvertex'
|
2
|
+
require 'point'
|
3
|
+
require 'imagedata'
|
4
|
+
|
5
|
+
module Sankey
|
6
|
+
class Generator
|
7
|
+
attr_reader :processes, :data
|
8
|
+
|
9
|
+
ProcessWidth = 40
|
10
|
+
ProcessLayerStep = 100
|
11
|
+
ProcessSideStep = 50
|
12
|
+
Margin = 5
|
13
|
+
InputReagentsStep = 20
|
14
|
+
OutputReagentsStep = 20
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@processes = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def go!
|
21
|
+
@processes_vertices = {}
|
22
|
+
@drawn_reagents = []
|
23
|
+
@vertices = []
|
24
|
+
@input_reagent_offset = Margin
|
25
|
+
@below_next_process_offset = {}
|
26
|
+
@process_input_offset = {}
|
27
|
+
@process_output_left_offset = {}
|
28
|
+
@process_output_right_offset = {}
|
29
|
+
get_max_distances_to_processes
|
30
|
+
to_be_drawn = []
|
31
|
+
input_reagents.each { |r| to_be_drawn.push r }
|
32
|
+
until to_be_drawn.empty? do
|
33
|
+
STDERR.puts "to draw"
|
34
|
+
to_be_drawn.each { |r| STDERR.puts " " + r.to_s + " " + r.name }
|
35
|
+
not_drawn = []
|
36
|
+
to_be_drawn.each { |r| not_drawn += recursively_draw r }
|
37
|
+
to_be_drawn = not_drawn.uniq
|
38
|
+
end
|
39
|
+
make_coordinates_positive
|
40
|
+
w, h = get_size
|
41
|
+
@data = ImageData.new @vertices, w, h
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# Moves all vertices so that there are no negative coordinates.
|
47
|
+
def make_coordinates_positive
|
48
|
+
min_y = @vertices[0].points[0].y
|
49
|
+
min_x = @vertices[0].points[0].x
|
50
|
+
@vertices.each do |v|
|
51
|
+
v.points.each do |p|
|
52
|
+
min_y = [min_y, p.y].min
|
53
|
+
min_x = [min_x, p.x].min
|
54
|
+
end
|
55
|
+
end
|
56
|
+
min_x -= Margin
|
57
|
+
STDERR.puts "Vector: #{-min_x} -#{-min_y}."
|
58
|
+
vector = [-min_x, -min_y]
|
59
|
+
@vertices.each do |v|
|
60
|
+
v.points.map! do |p|
|
61
|
+
p + vector
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def get_max_distances_to_processes(reagent = nil, level = 0)
|
67
|
+
if reagent.nil?
|
68
|
+
@max_distances_to_processes = {}
|
69
|
+
input_reagents.each { |r| get_max_distances_to_processes r }
|
70
|
+
elsif !reagent.drain.nil?
|
71
|
+
new_level = level + 1
|
72
|
+
if @max_distances_to_processes[reagent.drain].nil? or
|
73
|
+
@max_distances_to_processes[reagent.drain] < new_level
|
74
|
+
@max_distances_to_processes[reagent.drain] = new_level
|
75
|
+
end
|
76
|
+
reagent.drain.output.each do |r|
|
77
|
+
get_max_distances_to_processes r, new_level
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def draw_process(process)
|
83
|
+
layer = @max_distances_to_processes[process] - 1
|
84
|
+
height = process.total_reagents_mass
|
85
|
+
corner = Point.new (layer+1)*(ProcessLayerStep+ProcessWidth) + Margin,
|
86
|
+
Margin
|
87
|
+
v = ProcessVertex.new corner, corner + [0, height],
|
88
|
+
corner + [ProcessWidth, height], corner + [ProcessWidth, 0]
|
89
|
+
@vertices.push v
|
90
|
+
@processes_vertices[process] = v
|
91
|
+
# Initialize @below_next_process_offset
|
92
|
+
process.input.each do |r|
|
93
|
+
unless r.source.nil?
|
94
|
+
@below_next_process_offset[r.source] = Margin + height
|
95
|
+
break
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def recursively_draw(reagent)
|
101
|
+
return [] if @drawn_reagents.include? reagent
|
102
|
+
STDERR.puts " drawing " + reagent.name if reagent.name != ""
|
103
|
+
if reagent.drain.nil?
|
104
|
+
if reagent.source.is_first_in_output_queue reagent
|
105
|
+
draw_output_reagent reagent
|
106
|
+
reagent.source.remove_from_output_queue reagent
|
107
|
+
else
|
108
|
+
STDERR.puts " later"
|
109
|
+
return [reagent]
|
110
|
+
end
|
111
|
+
elsif reagent.source.nil?
|
112
|
+
if reagent.drain.is_first_in_input_queue reagent
|
113
|
+
draw_input_reagent reagent
|
114
|
+
reagent.drain.remove_from_input_queue reagent
|
115
|
+
else
|
116
|
+
STDERR.puts " later"
|
117
|
+
return [reagent]
|
118
|
+
end
|
119
|
+
else
|
120
|
+
if reagent.drain.is_first_in_input_queue(reagent) and
|
121
|
+
reagent.source.is_first_in_output_queue(reagent)
|
122
|
+
draw_middle_reagent reagent
|
123
|
+
reagent.source.remove_from_output_queue reagent
|
124
|
+
reagent.drain.remove_from_input_queue reagent
|
125
|
+
else
|
126
|
+
#STDERR.puts reagent.drain.is_first_in_input_queue(reagent)
|
127
|
+
#STDERR.puts reagent.source.is_first_in_output_queue(reagent)
|
128
|
+
STDERR.puts " later"
|
129
|
+
return [reagent]
|
130
|
+
end
|
131
|
+
end
|
132
|
+
@drawn_reagents.push reagent
|
133
|
+
to_draw_later = []
|
134
|
+
unless reagent.drain.nil?
|
135
|
+
reagent.drain.output.each { |r| to_draw_later += recursively_draw r }
|
136
|
+
end
|
137
|
+
to_draw_later
|
138
|
+
end
|
139
|
+
|
140
|
+
def draw_middle_reagent(reagent)
|
141
|
+
left_height = reagent.source.total_reagents_mass *
|
142
|
+
reagent.source.fraction(reagent)
|
143
|
+
right_height = reagent.drain.total_reagents_mass *
|
144
|
+
reagent.drain.fraction(reagent)
|
145
|
+
[reagent.source, reagent.drain].each do |p|
|
146
|
+
draw_process p unless @processes_vertices.include? p
|
147
|
+
end
|
148
|
+
@process_input_offset[reagent.drain] ||= 0
|
149
|
+
@process_output_left_offset[reagent.source] ||= 0
|
150
|
+
left_corner = @processes_vertices[reagent.source].output_edge[:top] +
|
151
|
+
[0, @process_output_left_offset[reagent.source]]
|
152
|
+
right_corner = @processes_vertices[reagent.drain].input_edge[:top] +
|
153
|
+
[0, @process_input_offset[reagent.drain]]
|
154
|
+
v = Vertex.new
|
155
|
+
v.points.push left_corner
|
156
|
+
v.points.push left_corner + [0, left_height]
|
157
|
+
v.points.push right_corner + [0, right_height]
|
158
|
+
v.points.push right_corner
|
159
|
+
@vertices.push v
|
160
|
+
@process_input_offset[reagent.drain] += right_height
|
161
|
+
@process_output_left_offset[reagent.source] += left_height
|
162
|
+
end
|
163
|
+
|
164
|
+
def draw_input_reagent(reagent)
|
165
|
+
draw_process reagent.drain unless @processes_vertices.include? reagent.drain
|
166
|
+
height = reagent.drain.total_reagents_mass * reagent.drain.fraction(reagent)
|
167
|
+
@process_input_offset[reagent.drain] ||= 0
|
168
|
+
v = Vertex.new
|
169
|
+
if @process_input_offset[reagent.drain] == 0 and reagent.joins_later
|
170
|
+
left_corner = @processes_vertices[reagent.drain].input_edge[:top] +
|
171
|
+
[-ProcessSideStep, -height]
|
172
|
+
else
|
173
|
+
left_corner = Point.new Margin, @input_reagent_offset
|
174
|
+
end
|
175
|
+
v.points.push left_corner
|
176
|
+
v.points.push left_corner + [5, height / 2]
|
177
|
+
v.points.push left_corner + [0, height]
|
178
|
+
v.points.push @processes_vertices[reagent.drain].input_edge[:top] +
|
179
|
+
[0, @process_input_offset[reagent.drain] + height]
|
180
|
+
v.points.push @processes_vertices[reagent.drain].input_edge[:top] +
|
181
|
+
[0, @process_input_offset[reagent.drain]]
|
182
|
+
@vertices.push v
|
183
|
+
@input_reagent_offset += height + InputReagentsStep
|
184
|
+
@process_input_offset[reagent.drain] += height
|
185
|
+
end
|
186
|
+
|
187
|
+
def draw_output_reagent(reagent)
|
188
|
+
height = reagent.source.total_reagents_mass *
|
189
|
+
reagent.source.fraction(reagent)
|
190
|
+
@process_output_left_offset[reagent.source] ||= 0
|
191
|
+
@process_output_right_offset[reagent.source] ||= 0
|
192
|
+
right_offset = @process_output_right_offset[reagent.source] +
|
193
|
+
(@below_next_process_offset[reagent.source] ||= 0)
|
194
|
+
left_corner = @processes_vertices[reagent.source].output_edge[:top] +
|
195
|
+
[0, @process_output_left_offset[reagent.source]]
|
196
|
+
v = Vertex.new
|
197
|
+
v.points.push left_corner
|
198
|
+
v.points.push left_corner + [0, height]
|
199
|
+
v.points.push left_corner +
|
200
|
+
[ProcessLayerStep, right_offset + height]
|
201
|
+
v.points.push left_corner +
|
202
|
+
[ProcessLayerStep + 5, right_offset + height / 2]
|
203
|
+
v.points.push left_corner +
|
204
|
+
[ProcessLayerStep, right_offset]
|
205
|
+
@vertices.push v
|
206
|
+
@process_output_left_offset[reagent.source] += height
|
207
|
+
@process_output_right_offset[reagent.source] += height + InputReagentsStep
|
208
|
+
end
|
209
|
+
|
210
|
+
def input_reagents
|
211
|
+
return @input_reagents if defined? @input_reagents
|
212
|
+
@input_reagents = []
|
213
|
+
@processes.each do |process|
|
214
|
+
process.input.each do |reagent|
|
215
|
+
@input_reagents.push reagent if reagent.source.nil?
|
216
|
+
end
|
217
|
+
end
|
218
|
+
@input_reagents.uniq!
|
219
|
+
@input_reagents
|
220
|
+
end
|
221
|
+
|
222
|
+
def get_size
|
223
|
+
w = h = 0
|
224
|
+
@vertices.each do |vertex|
|
225
|
+
vertex.points.each do |point|
|
226
|
+
w = point.x + Margin if point.x + Margin > w
|
227
|
+
h = point.y + Margin if point.y + Margin > h
|
228
|
+
end
|
229
|
+
end
|
230
|
+
return w, h
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
data/lib/imagedata.rb
ADDED
data/lib/point.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module Sankey
|
2
|
+
class Point
|
3
|
+
attr_reader :x, :y
|
4
|
+
|
5
|
+
def initialize(x, y)
|
6
|
+
@x = x
|
7
|
+
@y = y
|
8
|
+
end
|
9
|
+
|
10
|
+
def +(val)
|
11
|
+
if val.is_a? Array and 2 == val.count
|
12
|
+
x = val[0]
|
13
|
+
y = val[1]
|
14
|
+
else
|
15
|
+
throw "Invalid argument"
|
16
|
+
end
|
17
|
+
Point.new @x + x, @y + y
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/process.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
module Sankey
|
2
|
+
class Process
|
3
|
+
attr_reader :input, :output
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@input = []
|
7
|
+
@output = []
|
8
|
+
@input_reagents_queue = {}
|
9
|
+
@output_reagents_queue = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def fraction(reagent)
|
13
|
+
side = nil
|
14
|
+
[@output, @input].each { |io| side = io if io.include? reagent }
|
15
|
+
throw "Reagent isn't present in this process" if side.nil?
|
16
|
+
sum = 0
|
17
|
+
side.each { |r| sum += r.size }
|
18
|
+
reagent.size * 1.0 / sum
|
19
|
+
end
|
20
|
+
|
21
|
+
def total_reagents_mass
|
22
|
+
check_conservation_of_mass
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_output(reagent, order = nil)
|
26
|
+
@output.push reagent
|
27
|
+
@output_reagents_queue[order] = reagent unless order.nil?
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_input(reagent, order = nil)
|
31
|
+
@input.push reagent
|
32
|
+
@input_reagents_queue[order] = reagent unless order.nil?
|
33
|
+
end
|
34
|
+
|
35
|
+
def remove_from_input_queue(reagent)
|
36
|
+
key_to_be_removed = nil
|
37
|
+
@input_reagents_queue.each do |key, val|
|
38
|
+
key_to_be_removed = key if val == reagent
|
39
|
+
end
|
40
|
+
@input_reagents_queue.delete key_to_be_removed
|
41
|
+
end
|
42
|
+
|
43
|
+
def remove_from_output_queue(reagent)
|
44
|
+
key_to_be_removed = nil
|
45
|
+
@output_reagents_queue.each do |key, val|
|
46
|
+
key_to_be_removed = key if val == reagent
|
47
|
+
end
|
48
|
+
@output_reagents_queue.delete key_to_be_removed
|
49
|
+
end
|
50
|
+
|
51
|
+
def is_first_in_input_queue(reagent)
|
52
|
+
min_key = nil
|
53
|
+
return true unless @input_reagents_queue.values.include? reagent
|
54
|
+
@input_reagents_queue.each do |key, val|
|
55
|
+
#STDERR.puts "#{key} -> #{val.name}"
|
56
|
+
min_key ||= key
|
57
|
+
min_key = [min_key, key].min
|
58
|
+
end
|
59
|
+
@input_reagents_queue[min_key] == reagent
|
60
|
+
end
|
61
|
+
|
62
|
+
def is_first_in_output_queue(reagent)
|
63
|
+
min_key = nil
|
64
|
+
return true unless @output_reagents_queue.values.include? reagent
|
65
|
+
@output_reagents_queue.each do |key, val|
|
66
|
+
min_key ||= key
|
67
|
+
min_key = [min_key, key].min
|
68
|
+
end
|
69
|
+
@output_reagents_queue[min_key] == reagent
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def check_conservation_of_mass
|
75
|
+
input_sum = output_sum = 0
|
76
|
+
@input.each { |r| input_sum += r.size }
|
77
|
+
@output.each { |r| output_sum += r.size }
|
78
|
+
throw 'Conservation of mass constraint failed: ' +
|
79
|
+
"#{input_sum} != #{output_sum}" if input_sum != output_sum
|
80
|
+
input_sum
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'vertex'
|
2
|
+
|
3
|
+
module Sankey
|
4
|
+
class ProcessVertex < Vertex
|
5
|
+
def initialize(a, b, c, d)
|
6
|
+
super()
|
7
|
+
[a, b, c, d].each { |x| @points.push x }
|
8
|
+
end
|
9
|
+
|
10
|
+
def input_edge
|
11
|
+
{:bottom => @points[1], :top => @points[0]}
|
12
|
+
end
|
13
|
+
|
14
|
+
def output_edge
|
15
|
+
{:bottom => @points[2], :top => @points[3]}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/reagent.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
module Sankey
|
2
|
+
class Reagent
|
3
|
+
attr_reader :size, :source, :drain, :name
|
4
|
+
|
5
|
+
def initialize(size, name = '')
|
6
|
+
@size = size
|
7
|
+
@name = name
|
8
|
+
end
|
9
|
+
|
10
|
+
def source=(process)
|
11
|
+
process.add_output self
|
12
|
+
@source = process
|
13
|
+
end
|
14
|
+
|
15
|
+
def drain=(process)
|
16
|
+
process.add_input self
|
17
|
+
@drain = process
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_drain(process, order = nil)
|
21
|
+
process.add_input self, order
|
22
|
+
@drain = process
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_source(process, order = nil)
|
26
|
+
process.add_output self, order
|
27
|
+
@source= process
|
28
|
+
end
|
29
|
+
|
30
|
+
def joins_later
|
31
|
+
drain.input.each do |r|
|
32
|
+
next if r == self
|
33
|
+
if r.source != nil
|
34
|
+
return true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'renderers/renderer'
|
2
|
+
require 'rvg/rvg'
|
3
|
+
require 'imagedata'
|
4
|
+
|
5
|
+
module Sankey::Renderers
|
6
|
+
class RVG
|
7
|
+
include Renderer
|
8
|
+
include Sankey
|
9
|
+
|
10
|
+
def initialize(data, filename = nil, args = {})
|
11
|
+
throw "data isn't ImageData" unless data.is_a? ImageData
|
12
|
+
@grid = args[:grid] || false
|
13
|
+
@vertices = data.vertices
|
14
|
+
@filename = filename
|
15
|
+
@width = args[:width] || data.width
|
16
|
+
@height = args[:height] || data.height
|
17
|
+
@width_ratio = 1.0 * @width / data.width
|
18
|
+
@height_ratio = 1.0 * @height / data.height
|
19
|
+
end
|
20
|
+
|
21
|
+
def render
|
22
|
+
canvas = Magick::ImageList.new
|
23
|
+
canvas.new_image(@width, @height)
|
24
|
+
draw_grid canvas if @grid
|
25
|
+
@vertices.each { |v| draw_vertex canvas, v }
|
26
|
+
if @filename.nil?
|
27
|
+
return canvas.to_blob { self.format = 'PNG' }
|
28
|
+
else
|
29
|
+
canvas.write @filename
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def draw_grid(canvas)
|
36
|
+
grid = Magick::Draw.new
|
37
|
+
grid.stroke = 'lightgray'
|
38
|
+
grid.stroke_width = 1
|
39
|
+
0.upto @width/@grid/@width_ratio do |x|
|
40
|
+
grid.line @width_ratio*(x*@grid+@grid-1), 0,
|
41
|
+
(x*@grid+@grid-1)*@width_ratio, @height
|
42
|
+
end
|
43
|
+
0.upto @height/@grid/@height_ratio do |x|
|
44
|
+
grid.line 0, (x*@grid+@grid-1)*@height_ratio, @width,
|
45
|
+
(x*@grid+@grid-1)*@height_ratio
|
46
|
+
end
|
47
|
+
grid.draw canvas
|
48
|
+
end
|
49
|
+
|
50
|
+
def draw_vertex(canvas, v)
|
51
|
+
vertex = Magick::Draw.new
|
52
|
+
vertex.stroke = 'black'
|
53
|
+
vertex.stroke_width = 1
|
54
|
+
first_x = prev_x = v.points.first.x * @width_ratio
|
55
|
+
first_y = prev_y = v.points.first.y * @height_ratio
|
56
|
+
v.points.each do |p|
|
57
|
+
vertex.line prev_x, prev_y, p.x * @width_ratio, p.y * @height_ratio
|
58
|
+
prev_x = p.x * @width_ratio
|
59
|
+
prev_y = p.y * @height_ratio
|
60
|
+
end
|
61
|
+
vertex.line prev_x, prev_y, first_x, first_y
|
62
|
+
vertex.draw canvas
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/sankey.rb
ADDED
data/lib/vertex.rb
ADDED
data/sankey.gemspec
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{sankey}
|
8
|
+
s.version = "0.0.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Jan Stępień"]
|
12
|
+
s.date = %q{2010-02-18}
|
13
|
+
s.description = %q{A small tool which generates Sankey diagrams.}
|
14
|
+
s.email = %q{jstepien@users.sourceforge.net}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
"LICENSE",
|
21
|
+
"README.rdoc",
|
22
|
+
"Rakefile",
|
23
|
+
"VERSION",
|
24
|
+
"examples/coffee.rb",
|
25
|
+
"lib/generator.rb",
|
26
|
+
"lib/imagedata.rb",
|
27
|
+
"lib/point.rb",
|
28
|
+
"lib/process.rb",
|
29
|
+
"lib/processvertex.rb",
|
30
|
+
"lib/reagent.rb",
|
31
|
+
"lib/renderers/renderer.rb",
|
32
|
+
"lib/renderers/rvg.rb",
|
33
|
+
"lib/sankey.rb",
|
34
|
+
"lib/vertex.rb",
|
35
|
+
"sankey.gemspec"
|
36
|
+
]
|
37
|
+
s.homepage = %q{http://github.com/jstepien/sankey}
|
38
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
39
|
+
s.require_paths = ["lib"]
|
40
|
+
s.rubygems_version = %q{1.3.5}
|
41
|
+
s.summary = %q{Sankey diagrams generator}
|
42
|
+
s.test_files = [
|
43
|
+
"examples/coffee.rb"
|
44
|
+
]
|
45
|
+
|
46
|
+
if s.respond_to? :specification_version then
|
47
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
48
|
+
s.specification_version = 3
|
49
|
+
|
50
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
51
|
+
s.add_runtime_dependency(%q<rmagick>, [">= 0"])
|
52
|
+
else
|
53
|
+
s.add_dependency(%q<rmagick>, [">= 0"])
|
54
|
+
end
|
55
|
+
else
|
56
|
+
s.add_dependency(%q<rmagick>, [">= 0"])
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sankey
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- "Jan St\xC4\x99pie\xC5\x84"
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-02-18 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rmagick
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description: A small tool which generates Sankey diagrams.
|
26
|
+
email: jstepien@users.sourceforge.net
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- LICENSE
|
33
|
+
- README.rdoc
|
34
|
+
files:
|
35
|
+
- LICENSE
|
36
|
+
- README.rdoc
|
37
|
+
- Rakefile
|
38
|
+
- VERSION
|
39
|
+
- examples/coffee.rb
|
40
|
+
- lib/generator.rb
|
41
|
+
- lib/imagedata.rb
|
42
|
+
- lib/point.rb
|
43
|
+
- lib/process.rb
|
44
|
+
- lib/processvertex.rb
|
45
|
+
- lib/reagent.rb
|
46
|
+
- lib/renderers/renderer.rb
|
47
|
+
- lib/renderers/rvg.rb
|
48
|
+
- lib/sankey.rb
|
49
|
+
- lib/vertex.rb
|
50
|
+
- sankey.gemspec
|
51
|
+
has_rdoc: true
|
52
|
+
homepage: http://github.com/jstepien/sankey
|
53
|
+
licenses: []
|
54
|
+
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options:
|
57
|
+
- --charset=UTF-8
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: "0"
|
65
|
+
version:
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: "0"
|
71
|
+
version:
|
72
|
+
requirements: []
|
73
|
+
|
74
|
+
rubyforge_project:
|
75
|
+
rubygems_version: 1.3.5
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: Sankey diagrams generator
|
79
|
+
test_files:
|
80
|
+
- examples/coffee.rb
|