public
Description: Code as Art, Art as Code. Processing and Ruby are meant for each other.
Homepage: http://github.com/jashkenas/ruby-processing/wikis
Clone URL: git://github.com/jashkenas/ruby-processing.git
Search Repo:
Removing NodeGraph from the source until it's in a more ready state.
jashkenas (author)
Mon Apr 21 09:23:34 -0700 2008
commit  8fdc557a77970a9123d02266c647ec8c4eda9c16
tree    c76c2ce8f2f7ada8e287efdaa1248f75b7ad304d
parent  6c5e8e6c3073a0f9dfcf26dd42636dd26b0fa663
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0
0
@@ -1,45 +0,0 @@
0
-# Set theory, cliques and subgraphs...
0
-# by Tom de Smedt
0
-#
0
-# -- omygawshkenas
0
-
0
-module NodeGraph
0
- module Cluster
0
-
0
- # SET THEORY
0
-
0
- def self.flatten(node, distance=1)
0
- # Recursively lists the node's links, out to a given distance.
0
- return node.nodes.map {|n| n.id} if node.respond_to?(:nodes)
0
- all = [node].flatten
0
- if distance >= 1
0
- node.links.each do |n|
0
- all << flatten(n, distance-1)
0
- end
0
- end
0
- return all.uniq
0
- end
0
-
0
- def self.intersection(a, b)
0
- # Returns elements that appear in both a and b.
0
- return a & b
0
- end
0
-
0
- def self.union(a, b)
0
- # Return all the elements from a and all from b.
0
- return a | b
0
- end
0
-
0
- def difference(a, b)
0
- # Return all a that don't appear in b.
0
- return a.reject {|el| b.include?(el) }
0
- end
0
-
0
- # SUBGRAPHS
0
-
0
- def subgraph(graph, id, distance=1)
0
- # Creates the subgraph from the node with the given id(s).
0
- g = graph.copy(true)
0
-
0
- end
0
-end
0
\ No newline at end of file
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0
@@ -1,384 +0,0 @@
0
-# Graph library for Ruby-Processing
0
-# After Tom de Smedt's version for NodeBox.
0
-# -- omygawshkenas
0
-
0
-# WARNING: Not yet ready for any sort of use!
0
-
0
-module NodeGraph
0
- LAYOUT_CIRCLE = :circle
0
- LAYOUT_SPRING = :spring
0
- RAD = 8
0
-
0
- class Node
0
- include Math
0
-
0
- def initialize(graph, opts={})
0
- @app = Processing::App.current
0
- defaults = {:graph => graph,
0
- :id => "",
0
- :radius => RAD,
0
- :style => Style::DEFAULT,
0
- :category => "",
0
- :label => nil,
0
- :vx => 0, :vy => 0 }.merge(opts)
0
- defaults.each_key {|k| instance_variable_set(k, defaults[k])}
0
- @label ||= @id
0
- @links = links()
0
- @force = layout.point(0, 0)
0
- @betweenness, @eigenvalue = nil, nil
0
- end
0
-
0
- def get_edges
0
- @links.edges.values
0
- end
0
-
0
- def get_betweenness
0
- @betweenness || @graph.betweenness_centrality
0
- end
0
-
0
- def get_eigenvalue
0
- @eigenvalue || @graph.eigenvector_centrality
0
- end
0
-
0
- def x
0
- @vx * @graph.d
0
- end
0
-
0
- def y
0
- @vy * @graph.d
0
- end
0
-
0
- def contains(point)
0
- # True if the point is in this node's absolute position
0
- return true if abs(@graph.x + x - point.x) < @radius * 2 && abs(@graph.y + y - point.y) < @radius * 2
0
- return false
0
- end
0
- end
0
-
0
- class Links
0
- def initialize
0
- @edges = {}
0
- @links = []
0
- end
0
-
0
- def append(node, edge = nil)
0
- @edges[node.id] = edge if edge
0
- @links << node
0
- end
0
-
0
- def remove(node)
0
- @edges.delete(node.id) if @edges[node.id]
0
- @links.delete(node)
0
- end
0
-
0
- def edge(id)
0
- id = id.id if id.is_a? Node
0
- @edges[id]
0
- end
0
- end
0
-
0
- class Edge
0
- attr_accessor :node1, :node2, :weight, :label
0
- def initialize(node1, node2, weight=0.0, label="")
0
- @node1, @node2, @weight, @label = node1, node2, weight, label
0
- end
0
- end
0
-
0
- class Graph
0
- MULT = 2.5
0
-
0
- def initialize(iterations=1000, distance=1.0, layout=LAYOUT_SPRING)
0
- @graph = {}
0
- @nodes, @edges = [], []
0
- @root = nil
0
- @layout = # TODO
0
- @d = RAD * MULT * distance
0
- @styles = # Todo
0
- @alpha = 0
0
- end
0
-
0
- def get_distance
0
- @d / RAD * 2.5
0
- end
0
-
0
- def set_distance(val)
0
- @d = RAD * 2.5 * val
0
- end
0
-
0
- def copy( empty=false )
0
- g = Graph.new(@layout.n, @d, @layout.type)
0
- g.layout = @layout.copy(g)
0
- g.styles = @styles.copy(g)
0
- unless empty
0
- @nodes.each {|n| g.add_node(n.id, n.r, n.style, n.category, n.label, (n == @root))}
0
- @edges.each {|e| g.add_edge(e.node1.id, e.node2.id, e.weight, e.label)}
0
- end
0
- return g
0
- end
0
-
0
- def clear
0
- @graph.clear
0
- @nodes, @edges = [], []
0
- @root = nil
0
- @layout.i = 0
0
- @alpha = 0
0
- end
0
-
0
- def add_node(id, radius=8, style=Style::DEFAULT, category="", label=nil, root=false)
0
- return @graph[id] if @graph[id]
0
- style = style.name if style.is_a? Style
0
- n = Node.new(id, radius, style, category, label)
0
- @graph[n.id] = n
0
- @nodes << n
0
- @root = n if root
0
- return n
0
- end
0
-
0
- def add_nodes(nodes)
0
- nodes.each {|n| self.add_node(n) }
0
- end
0
-
0
- def add_edge(id1, id2, weight=0.0, label="")
0
- # The weight of the edge represents importance, not cost. (0.0..1.0)
0
- return nil if id1 == id2
0
- self.add_node(id1) unless @graph.has_key?(id1)
0
- self.add_node(id2) unless @graph.has_key?(id2)
0
- n1, n2 = @graph[id1], @graph[id2]
0
-
0
- # Don't duplicate a -> b, but b -> a is ok.
0
- return self.edge(id1, id2) if n2.links.include?(n1) && n2.links.edge(n1).node1 == n1
0
-
0
- weight = [0.0, [weight, 1.0].min ].max
0
- e = Edge.new(n1, n2, weight, label)
0
- @edges << e
0
- n1.links.append(n2, e)
0
- n2.links.append(n1, e)
0
- return e
0
- end
0
-
0
- def remove_node(id)
0
- if @graph.has_key?(id)
0
- n = @graph[id]
0
- @nodes.delete(n)
0
- @graph.delete(id)
0
-
0
- # Then remove all edges and links.
0
- @edges.each do |e|
0
- nodes = [e.node1, e.node2]
0
- if nodes.include?(n)
0
- nodes.each {|other| other.links.remove(n) if other.links.include?(n) }
0
- end
0
- @edges.delete(e)
0
- end
0
- end
0
- end
0
-
0
- def remove_edge(id1, id2)
0
- @edges.each do |e|
0
- ids = [e.node1.id, e.node2.id]
0
- if ids.include?(id1) && ids.include?(id2)
0
- e.node1.links.remove(e.node2)
0
- e.node2.links.remove(e.node1)
0
- @edges.delete(e)
0
- end
0
- end
0
- end
0
-
0
- def node(id)
0
- return @graph[id]
0
- end
0
-
0
- def edge(id1, id2)
0
- links = @graph[id1].links
0
- links.include?(id2) ? links.edge(id2) : nil
0
- end
0
-
0
- def update(iterations=10)
0
- # Iterates the layout, updates node positions
0
- @alpha += 0.05
0
- @alpha = 1.0 if @alpha > 1.0
0
-
0
- # Each step, the bounds are recalculated.
0
- i = @layout.i
0
- if i == 0
0
- @layout.prepare
0
- @layout.i += 1
0
- elsif i == 1
0
- @layout.iterate
0
- elsif i < @layout.n
0
- n = [iterations, (i / 10 + 1)].min
0
- n.times { @layout.iterate }
0
- end
0
-
0
- # Calculate the absolute center.
0
- min, max = @layout.bounds
0
- @x = (@app.width - max.x*@d - min.x*@d) / 2
0
- @y = (@app.height - max.y*@d - min.y*@d) / 2
0
- return !(@layout.done?)
0
- end
0
-
0
- def solve
0
- @layout.solve
0
- @alpha = 1.0
0
- end
0
-
0
- def done?
0
- @layout.done?
0
- end
0
-
0
- def offset(node)
0
- [@x + node.x - @app.width / 2, @y + node.y - @app.height / 2]
0
- end
0
-
0
- def draw(opts)
0
- # Layout the graph in increments.
0
- opts = {:dx => 0, :dy => 0,
0
- :weighted => false,
0
- :directed => false,
0
- :highlight => [],
0
- :traffic => nil }.merge(opts)
0
- self.update
0
- s = @styles.default
0
- s.graph_background(s)
0
-
0
- # Center the graph on the page.
0
- @app.push_matrix
0
- @app.translate(@x + opts[:dx], @y + opts[:dy])
0
-
0
- # Indicate betweenness centrality ???
0
- traffic = opts[:traffic]
0
- if traffic
0
- traffic = 5 if traffic == true
0
- self.node_by_betweenness(traffic).each do |node|
0
- s = @styles[n.style] || @styles.default
0
- s.graph_traffic(s, n, @alpha) if s.graph_traffic
0
- end
0
- end
0
-
0
- # Draw the edges and edge labels.
0
- s = @styles.default
0
- s.edges(s, @edges, @alpha, opts[:weighted], opts[:directed]) if s.edges
0
-
0
- # Draw each node, with style.
0
- @nodes.each do |n|
0
- s = @styles[n.style] || @styles.default
0
- s.node(s, n, @alpha) if s.node
0
- s.node_label(s, n, @alpha) if s.node_label
0
- end
0
-
0
- # Highlight the shortest path.
0
- s = @styles.highlight || @styles.default
0
- s.path(s, self, highlight) if s.path
0
- end
0
-
0
- def prune(depth=0)
0
- @nodes.each {|n| self.remove_node(n.id) if n.links.size <= depth }
0
- end
0
- alias_method :trim, :prune
0
-
0
- def shortest_path(id1, id2, heuristic=nil)
0
- Proximity.dijkstra_shortest_path(self, id1, id2, heuristic)
0
- end
0
-
0
- def betweenness_centrality(normalized=true)
0
- # Calculates and returns node id => betweenness
0
- bc = Proximity.brandes_betweenness_centrality(self, normalized)
0
- bc.iter_items.each {|k, v| @graph[k].betweenness = v }
0
- return bc
0
- end
0
-
0
- def eigenvector_centrality( normalized=true, reversed=true, rating={},
0
- start=nil, iterations=100, tolerance=0.0001)
0
- ec = Proximity.eigenvector_centrality(self, normalized, reversed, rating, start, iterations, tolerance)
0
- ec.iter_items.each {|k, v| @graph[k].eigenvalue = v}
0
- return ec
0
- end
0
-
0
- def nodes_by_betweenness(threshold=0.0)
0
- # Heavily traffiked nodes come first.
0
- nodes = @nodes.select {|n| n.betweenness > threshold }
0
- nodes = nodes.sort {|a, b| b.betweenness <=> a.betweenness }
0
- end
0
- alias_method :nodes_by_traffic, :nodes_by_betweenness
0
-
0
- def nodes_by_eigenvalue(threshold=0.0)
0
- # Heavily traffiked nodes (by eigenvalue) come first.
0
- nodes = @nodes.select{|n| n.eigenvalue > threshold}
0
- nodes = nodes.sort {|a, b| b.eigenvalue <=> a.eigenvalue }
0
- end
0
- alias_method :nodes_by_weight, :nodes_by_eigenvalue
0
-
0
- def nodes_by_category(category)
0
- @nodes.select {|n| n.category == category}
0
- end
0
-
0
- def density
0
- # The number of edges in relation to the total possible edges.
0
- 2.0 * @edges.size / (@nodes.size * (@nodes.size - 1))
0
- end
0
-
0
- def is_complete; self.density == 1.0; end
0
- def is_dense; self.density > 0.65; end
0
- def is_sparse; self.density < 0.35; end
0
-
0
- def sub(id, distance=1)
0
- Cluster.subgraph(self, id, distance)
0
- end
0
- alias_method :subgraph, :sub
0
-
0
- def and(graph)
0
- nodes = Cluster.intersection(Cluster.flatten(self), Cluster.flatten(graph))
0
- all = @graph | graph.graph
0
- return Cluster.subgraph(all, nodes, 0)
0
- end
0
- alias_method :intersect, :and
0
-
0
- def or(graph)
0
- g = self.dup
0
- graph.nodes.each {|n| g.add_node(n.id, n.r, n.style, n.category, n.label, (g.root == nil && g.root == n))}
0
- graph.edges.each {|e| g.add_edge(e.node1.id, e.node2.id, e.weight, e.label) }
0
- return g
0
- end
0
- alias_method :join, :or
0
-
0
- def sub(graph)
0
- nodes = Cluster.difference(Cluster.flatten(self), Cluster.flatten(graph))
0
- all = @graph | graph.graph
0
- return Cluster.subgraph(all, nodes, 0)
0
- end
0
- alias_method :subtract, :sub
0
-
0
- def is_clique
0
- Cluster.is_clique(self)
0
- end
0
-
0
- def clique(id, distance=0)
0
- Cluster.subgraph(self, Cluster.clique(self, id), distance)
0
- end
0
-
0
- def cliques(threshold=3, distance=0)
0
- g = []
0
- c = Cluster.cliques(self, threshold)
0
- c.each {|nodes| g << Cluster.subgraph(self, nodes, distance) }
0
- return g
0
- end
0
-
0
- def split
0
- Cluster.partition(self)
0
- end
0
-
0
- # And now for the class method to kick it all off. Kick it out!
0
- def self.create(iterations=1000, distance=1.0, layout=LAYOUT_SPRING, depth=true)
0
- g = Graph.new(iterations, distance, layout)
0
- s = Style.style
0
- g.styles.append(Style.new(Style.light, @app, app.color(0.0, 0.0, 0.0, 0.20)))
0
-
0
- end
0
- end
0
-
0
-end
0
-
0
-
0
-
0
-
0
-

Comments

    No one has commented yet.