ruby-graphviz 0.9.16 → 0.9.17
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +1 -0
- data/README.rdoc +64 -0
- data/examples/graphml/attributes.ext.graphml +12 -0
- data/examples/graphml/attributes.graphml +40 -0
- data/examples/graphml/cluster.graphml +75 -0
- data/examples/graphml/hyper.graphml +29 -0
- data/examples/graphml/nested.graphml +54 -0
- data/examples/graphml/port.graphml +32 -0
- data/examples/graphml/simple.graphml +30 -0
- data/examples/sample57.rb +6 -0
- data/examples/theory/PERT.png +0 -0
- data/examples/theory/matrix.png +0 -0
- data/examples/theory/pert.rb +47 -0
- data/examples/theory/tests.rb +81 -0
- data/lib/graphviz.rb +45 -8
- data/lib/graphviz/constants.rb +43 -40
- data/lib/graphviz/edge.rb +22 -5
- data/lib/graphviz/elements.rb +39 -0
- data/lib/graphviz/graphml.rb +218 -0
- data/lib/graphviz/math/matrix.rb +221 -0
- data/lib/graphviz/node.rb +11 -0
- data/lib/graphviz/theory.rb +252 -0
- data/lib/graphviz/types.rb +4 -0
- data/lib/graphviz/types/gv_double.rb +20 -0
- metadata +24 -4
@@ -0,0 +1,221 @@
|
|
1
|
+
class GraphViz
|
2
|
+
class Math
|
3
|
+
def self.Matrix( line, column = 0, val = 0 )
|
4
|
+
GraphViz::Math::Matrix.new(line, column, val)
|
5
|
+
end
|
6
|
+
|
7
|
+
class CoordinateError < RuntimeError
|
8
|
+
end
|
9
|
+
class ValueError < RuntimeError
|
10
|
+
end
|
11
|
+
|
12
|
+
class Matrix
|
13
|
+
def initialize( line_or_array, column = 0, val = 0 )
|
14
|
+
if line_or_array.kind_of?(Array)
|
15
|
+
line = line_or_array.size
|
16
|
+
column = nil
|
17
|
+
line_or_array.size.times do |l|
|
18
|
+
unless line_or_array[l].kind_of?(Array)
|
19
|
+
raise ArgumentError, "Wrong matrix definition"
|
20
|
+
end
|
21
|
+
column = line_or_array[l].size if column.nil?
|
22
|
+
unless line_or_array[l].size == column
|
23
|
+
raise ArgumentError, "Wrong matrix definition"
|
24
|
+
end
|
25
|
+
line_or_array[l].size.times do |c|
|
26
|
+
unless line_or_array[l][c].kind_of?(Numeric)
|
27
|
+
raise ValueError, "Element at [#{l+1}, #{c+1}] is not a number"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
@matrix = line_or_array
|
33
|
+
@line = line
|
34
|
+
@column = column
|
35
|
+
elsif line_or_array.kind_of?(Integer) and column > 0
|
36
|
+
@matrix = Array.new(line_or_array)
|
37
|
+
@matrix.size.times do |l|
|
38
|
+
@matrix[l] = Array.new(column)
|
39
|
+
@matrix[l].size.times do |c|
|
40
|
+
@matrix[l][c] = val
|
41
|
+
end
|
42
|
+
end
|
43
|
+
@line = line_or_array
|
44
|
+
@column = column
|
45
|
+
else
|
46
|
+
raise ArgumentError, "Wrong matrix definition"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def [](line, column)
|
51
|
+
unless (0...@line).to_a.include?(line-1)
|
52
|
+
raise CoordinateError, "Line out of range (#{line} for 1..#{@line})!"
|
53
|
+
end
|
54
|
+
unless (0...@column).to_a.include?(column-1)
|
55
|
+
raise CoordinateError, "Column out of range (#{column} for 1..#{@column})!"
|
56
|
+
end
|
57
|
+
@matrix[line-1][column-1]
|
58
|
+
end
|
59
|
+
|
60
|
+
def []=( line, column, val )
|
61
|
+
unless (0...@line).to_a.include?(line-1)
|
62
|
+
raise CoordinateError, "Line out of range (#{line} for 1..#{@line})!"
|
63
|
+
end
|
64
|
+
unless (0...@column).to_a.include?(column-1)
|
65
|
+
raise CoordinateError, "Column out of range (#{column} for 1..#{@column})!"
|
66
|
+
end
|
67
|
+
@matrix[line-1][column-1] = val
|
68
|
+
end
|
69
|
+
|
70
|
+
def matrix
|
71
|
+
@matrix
|
72
|
+
end
|
73
|
+
alias :to_a :matrix
|
74
|
+
|
75
|
+
def columns
|
76
|
+
@column
|
77
|
+
end
|
78
|
+
|
79
|
+
def lines
|
80
|
+
@line
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_s
|
84
|
+
size = bigger
|
85
|
+
out = ""
|
86
|
+
@line.times do |line|
|
87
|
+
out << "["
|
88
|
+
@column.times do |column|
|
89
|
+
out << sprintf(" %1$*2$s", @matrix[line][column].to_s, size)
|
90
|
+
end
|
91
|
+
out << "]\n"
|
92
|
+
end
|
93
|
+
return out
|
94
|
+
end
|
95
|
+
|
96
|
+
def -(m)
|
97
|
+
matrix = GraphViz::Math::Matrix.new( @line, @column )
|
98
|
+
@line.times do |line|
|
99
|
+
@column.times do |column|
|
100
|
+
matrix[line+1, column+1] = self[line+1, column+1] - m[line+1, column+1]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
return matrix
|
104
|
+
end
|
105
|
+
|
106
|
+
def *(m)
|
107
|
+
matrix = GraphViz::Math::Matrix.new( @line, @line )
|
108
|
+
|
109
|
+
@line.times do |line|
|
110
|
+
@line.times do |column|
|
111
|
+
l = self.line(line+1)
|
112
|
+
c = m.column(column+1)
|
113
|
+
v = 0
|
114
|
+
l.size.times do |i|
|
115
|
+
v += l[i]*c[i]
|
116
|
+
end
|
117
|
+
matrix[line+1,column+1] = v
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
return matrix
|
122
|
+
end
|
123
|
+
|
124
|
+
def line( line )
|
125
|
+
unless (0...@line).to_a.include?(line-1)
|
126
|
+
raise CoordinateError, "Line out of range (#{line} for 1..#{@line})!"
|
127
|
+
end
|
128
|
+
@matrix[line-1]
|
129
|
+
end
|
130
|
+
|
131
|
+
def column( column )
|
132
|
+
col = []
|
133
|
+
unless (0...@column).to_a.include?(column-1)
|
134
|
+
raise CoordinateError, "Column out of range (#{column} for 1..#{@column})!"
|
135
|
+
end
|
136
|
+
@line.times do |line|
|
137
|
+
col << self[line+1, column]
|
138
|
+
end
|
139
|
+
|
140
|
+
return col
|
141
|
+
end
|
142
|
+
|
143
|
+
def transpose
|
144
|
+
matrix = GraphViz::Math::Matrix.new( @column, @line )
|
145
|
+
@line.times do |line|
|
146
|
+
@column.times do |column|
|
147
|
+
matrix[column+1, line+1] = self[line+1, column+1]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
return matrix
|
151
|
+
end
|
152
|
+
|
153
|
+
def ==(m)
|
154
|
+
equal = true
|
155
|
+
@line.times do |line|
|
156
|
+
@column.times do |column|
|
157
|
+
equal &&= (m[line+1, column+1] == self[line+1, column+1])
|
158
|
+
end
|
159
|
+
end
|
160
|
+
return equal
|
161
|
+
end
|
162
|
+
|
163
|
+
def remove_line(n)
|
164
|
+
unless (0...@line).to_a.include?(n-1)
|
165
|
+
raise CoordinateError, "Line out of range (#{n} for 1..#{@line})!"
|
166
|
+
end
|
167
|
+
|
168
|
+
matrix = GraphViz::Math::Matrix.new( @line - 1, @column )
|
169
|
+
nline = 0
|
170
|
+
@line.times do |line|
|
171
|
+
next if line == n - 1
|
172
|
+
@column.times do |column|
|
173
|
+
matrix[nline+1, column+1] = self[line+1, column+1]
|
174
|
+
end
|
175
|
+
nline += 1
|
176
|
+
end
|
177
|
+
return matrix
|
178
|
+
end
|
179
|
+
|
180
|
+
def remove_column(n)
|
181
|
+
unless (0...@column).to_a.include?(n-1)
|
182
|
+
raise CoordinateError, "Column out of range (#{n} for 1..#{@column})!"
|
183
|
+
end
|
184
|
+
|
185
|
+
matrix = GraphViz::Math::Matrix.new( @line, @column - 1 )
|
186
|
+
@line.times do |line|
|
187
|
+
ncolumn = 0
|
188
|
+
@column.times do |column|
|
189
|
+
next if column == n - 1
|
190
|
+
matrix[line+1, ncolumn+1] = self[line+1, column+1]
|
191
|
+
ncolumn += 1
|
192
|
+
end
|
193
|
+
end
|
194
|
+
return matrix
|
195
|
+
end
|
196
|
+
|
197
|
+
def sum_of_column(n)
|
198
|
+
column(n).inject(0){|sum,item| sum + item}
|
199
|
+
end
|
200
|
+
|
201
|
+
def sum_of_line(n)
|
202
|
+
line(n).inject(0){|sum,item| sum + item}
|
203
|
+
end
|
204
|
+
|
205
|
+
def set_matrix(m) #:nodoc:
|
206
|
+
@matrix = m
|
207
|
+
end
|
208
|
+
|
209
|
+
private
|
210
|
+
def bigger
|
211
|
+
b = 0
|
212
|
+
@matrix.each do |line|
|
213
|
+
line.each do |column|
|
214
|
+
b = column.to_s.size if column.to_s.size > b
|
215
|
+
end
|
216
|
+
end
|
217
|
+
return b
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
data/lib/graphviz/node.rb
CHANGED
@@ -35,6 +35,7 @@ class GraphViz
|
|
35
35
|
@xNodeName = xNodeName
|
36
36
|
@oGParrent = oGParrent
|
37
37
|
@oAttrNode = GraphViz::Attrs::new( nil, "node", NODESATTRS )
|
38
|
+
@index = nil
|
38
39
|
end
|
39
40
|
|
40
41
|
#
|
@@ -51,6 +52,16 @@ class GraphViz
|
|
51
52
|
def id
|
52
53
|
@xNodeName.clone
|
53
54
|
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# Return the index of the node
|
58
|
+
#
|
59
|
+
def index
|
60
|
+
@index
|
61
|
+
end
|
62
|
+
def index=(i) #:nodoc:
|
63
|
+
@index = i if @index == nil
|
64
|
+
end
|
54
65
|
|
55
66
|
#
|
56
67
|
# Set value +xAttrValue+ to the node attribut +xAttrName+
|
@@ -0,0 +1,252 @@
|
|
1
|
+
require 'graphviz/math/matrix'
|
2
|
+
|
3
|
+
class GraphViz
|
4
|
+
class Theory
|
5
|
+
def initialize( graph )
|
6
|
+
@graph = graph
|
7
|
+
end
|
8
|
+
|
9
|
+
#
|
10
|
+
# Return the adjancy matrix of the graph
|
11
|
+
#
|
12
|
+
def adjancy_matrix
|
13
|
+
matrix = GraphViz::Math::Matrix.new( @graph.node_count, @graph.node_count )
|
14
|
+
|
15
|
+
@graph.each_edge { |e|
|
16
|
+
x = @graph.get_node(e.node_one( false )).index
|
17
|
+
y = @graph.get_node(e.node_two( false )).index
|
18
|
+
matrix[x+1, y+1] = 1
|
19
|
+
matrix[y+1, x+1] = 1 if @graph.type == "graph"
|
20
|
+
}
|
21
|
+
|
22
|
+
return matrix
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# Return the incidence matrix of the graph
|
27
|
+
#
|
28
|
+
def incidence_matrix
|
29
|
+
tail = (@graph.type == "digraph") ? -1 : 1
|
30
|
+
matrix = GraphViz::Math::Matrix.new( @graph.node_count, @graph.edge_count )
|
31
|
+
|
32
|
+
@graph.each_edge { |e|
|
33
|
+
x = e.index
|
34
|
+
|
35
|
+
nstart = @graph.get_node(e.node_one( false )).index
|
36
|
+
nend = @graph.get_node(e.node_two( false )).index
|
37
|
+
|
38
|
+
matrix[nstart+1, x+1] = 1
|
39
|
+
matrix[nend+1, x+1] = tail
|
40
|
+
matrix[nend+1, x+1] = 2 if nstart == nend
|
41
|
+
}
|
42
|
+
|
43
|
+
return matrix
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# Return the degree of the given node
|
48
|
+
#
|
49
|
+
def degree( node )
|
50
|
+
degree = 0
|
51
|
+
name = node
|
52
|
+
if node.kind_of?(GraphViz::Node)
|
53
|
+
name = node.id
|
54
|
+
end
|
55
|
+
|
56
|
+
@graph.each_edge do |e|
|
57
|
+
degree += 1 if e.node_one(false) == name or e.node_two(false) == name
|
58
|
+
end
|
59
|
+
|
60
|
+
return degree
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
# Return the laplacian matrix of the graph
|
65
|
+
#
|
66
|
+
def laplacian_matrix
|
67
|
+
return degree_matrix - adjancy_matrix
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# Return <tt>true</tt> if the graph if symmetric, <tt>false</tt> otherwise
|
72
|
+
#
|
73
|
+
def symmetric?
|
74
|
+
adjancy_matrix == adjancy_matrix.transpose
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
# moore_dijkstra(source, destination)
|
79
|
+
#
|
80
|
+
def moore_dijkstra( dep, arv )
|
81
|
+
dep = @graph.get_node(dep) unless dep.kind_of?(GraphViz::Node)
|
82
|
+
arv = @graph.get_node(arv) unless arv.kind_of?(GraphViz::Node)
|
83
|
+
|
84
|
+
m = distance_matrix
|
85
|
+
n = @graph.node_count
|
86
|
+
# Table des sommets à choisir
|
87
|
+
c = [dep.index]
|
88
|
+
# Table des distances
|
89
|
+
d = []
|
90
|
+
d[dep.index] = 0
|
91
|
+
|
92
|
+
# Table des predecesseurs
|
93
|
+
pred = []
|
94
|
+
|
95
|
+
@graph.each_node do |name, k|
|
96
|
+
if k != dep
|
97
|
+
d[k.index] = m[dep.index+1,k.index+1]
|
98
|
+
pred[k.index] = dep
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
while c.size < n
|
103
|
+
# trouver y tel que d[y] = min{d[k]; k sommet tel que k n'appartient pas à c}
|
104
|
+
mini = 1.0/0.0
|
105
|
+
y = nil
|
106
|
+
@graph.each_node do |name, k|
|
107
|
+
next if c.include?(k.index)
|
108
|
+
if d[k.index] < mini
|
109
|
+
mini = d[k.index]
|
110
|
+
y = k
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# si ce minimum est ∞ alors sortir de la boucle fin si
|
115
|
+
break unless mini.to_f.infinite?.nil?
|
116
|
+
|
117
|
+
c << y.index
|
118
|
+
@graph.each_node do |name, k|
|
119
|
+
next if c.include?(k.index)
|
120
|
+
if d[k.index] > d[y.index] + m[y.index+1,k.index+1]
|
121
|
+
d[k.index] = d[y.index] + m[y.index+1,k.index+1]
|
122
|
+
pred[k.index] = y
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Construction du chemin le plus court
|
128
|
+
ch = []
|
129
|
+
k = arv
|
130
|
+
while k.index != dep.index
|
131
|
+
ch.unshift(k.id)
|
132
|
+
k = pred[k.index]
|
133
|
+
end
|
134
|
+
ch.unshift(dep.id)
|
135
|
+
|
136
|
+
if d[arv.index].to_f.infinite?
|
137
|
+
return nil
|
138
|
+
else
|
139
|
+
return( {
|
140
|
+
:path => ch,
|
141
|
+
:distance => d[arv.index]
|
142
|
+
} )
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
#
|
147
|
+
# Return a liste of range
|
148
|
+
#
|
149
|
+
# If the returned array include nil values, there is one or more circuits in the graph
|
150
|
+
#
|
151
|
+
def range
|
152
|
+
matrix = adjancy_matrix
|
153
|
+
unseen = (1..matrix.columns).to_a
|
154
|
+
result = Array.new(matrix.columns)
|
155
|
+
r = 0
|
156
|
+
|
157
|
+
range_recursion( matrix, unseen, result, r )
|
158
|
+
end
|
159
|
+
|
160
|
+
#
|
161
|
+
# Return the critical path for a PERT network
|
162
|
+
#
|
163
|
+
# If the given graph is not a PERT network, return nul
|
164
|
+
#
|
165
|
+
def critical_path
|
166
|
+
return nil if range.include?(nil) or @graph.type != "digraph"
|
167
|
+
r = [ [0, [1]] ]
|
168
|
+
|
169
|
+
critical_path_recursion( distance_matrix, adjancy_matrix, r, [], 0 ).inject( {:distance => 0, :path => []} ) { |r, item|
|
170
|
+
(r[:distance] < item[0]) ? { :distance => item[0], :path => item[1] } : r
|
171
|
+
}
|
172
|
+
end
|
173
|
+
|
174
|
+
private
|
175
|
+
def distance_matrix
|
176
|
+
type = @graph.type
|
177
|
+
matrix = GraphViz::Math::Matrix.new( @graph.node_count, @graph.node_count, (1.0/0.0) )
|
178
|
+
|
179
|
+
@graph.each_edge { |e|
|
180
|
+
x = @graph.get_node(e.node_one( false )).index
|
181
|
+
y = @graph.get_node(e.node_two( false )).index
|
182
|
+
unless x == y
|
183
|
+
weight = ((e[:weight].nil?) ? 1 : e[:weight].to_f)
|
184
|
+
matrix[x+1, y+1] = weight
|
185
|
+
matrix[y+1, x+1] = weight if type == "graph"
|
186
|
+
end
|
187
|
+
}
|
188
|
+
|
189
|
+
return matrix
|
190
|
+
end
|
191
|
+
|
192
|
+
def degree_matrix
|
193
|
+
matrix = GraphViz::Math::Matrix.new( @graph.node_count, @graph.node_count )
|
194
|
+
@graph.each_node do |name, node|
|
195
|
+
i = node.index
|
196
|
+
matrix[i+1, i+1] = degree(node)
|
197
|
+
end
|
198
|
+
return matrix
|
199
|
+
end
|
200
|
+
|
201
|
+
def range_recursion(matrix, unseen, result, r)
|
202
|
+
remove = []
|
203
|
+
matrix.columns.times do |c|
|
204
|
+
if matrix.sum_of_column(c+1) == 0
|
205
|
+
result[unseen[c]-1] = r
|
206
|
+
remove.unshift( c + 1 )
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
remove.each do |rem|
|
211
|
+
matrix = matrix.remove_line(rem).remove_column(rem)
|
212
|
+
unseen.delete_at(rem-1)
|
213
|
+
end
|
214
|
+
|
215
|
+
if matrix.columns == 1 and matrix.lines == 1
|
216
|
+
if matrix.sum_of_column(1) == 0
|
217
|
+
result[unseen[0]-1] = r+1
|
218
|
+
end
|
219
|
+
elsif remove.size > 0
|
220
|
+
range_recursion( matrix, unseen, result, r+1 )
|
221
|
+
end
|
222
|
+
|
223
|
+
return result
|
224
|
+
end
|
225
|
+
|
226
|
+
def index_of_item( array, item )
|
227
|
+
array.inject( [0,[]] ){|a,i|
|
228
|
+
a[1] << a[0] if i == item
|
229
|
+
a[0] += 1
|
230
|
+
a
|
231
|
+
}[1]
|
232
|
+
end
|
233
|
+
|
234
|
+
def critical_path_recursion( d, a, r, result, level )
|
235
|
+
r.each do |p|
|
236
|
+
node = p[1][-1]
|
237
|
+
index_of_item( a.line(node), 1 ).each do |c|
|
238
|
+
succ = c+1
|
239
|
+
|
240
|
+
cpath = [ (p[0] + d[node,succ]), (p[1].clone << succ) ]
|
241
|
+
|
242
|
+
if index_of_item( a.line(succ), 1 ).size > 0
|
243
|
+
capth = critical_path_recursion( d, a, [cpath], result, level+1 )
|
244
|
+
else
|
245
|
+
result << cpath
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
return result
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|