iconofthestoneage-doodl 0.0.2 → 0.0.4

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.
Files changed (42) hide show
  1. data/lib/app/selfrunning.rb +185 -0
  2. data/lib/app/simple_app.rb +49 -0
  3. data/lib/app/simple_controller.rb +584 -0
  4. data/lib/app/simple_model.rb +292 -0
  5. data/lib/app/simple_view.rb +148 -0
  6. data/lib/breadth_first_search.rb +69 -0
  7. data/lib/connected_components.rb +29 -0
  8. data/lib/depth_first_search.rb +73 -0
  9. data/lib/edge.rb +57 -0
  10. data/lib/graph.rb +365 -0
  11. data/lib/graph_canvas.rb +187 -0
  12. data/lib/graph_generator.rb +121 -0
  13. data/lib/jruby/renderer.rb +413 -0
  14. data/lib/layout/collapse_layout.rb +23 -0
  15. data/lib/layout/fr_layout.rb +105 -0
  16. data/lib/layout/isom_layout.rb +77 -0
  17. data/lib/layout/kk_layout.rb +203 -0
  18. data/lib/layout/layout.rb +240 -0
  19. data/lib/layout/morph_layout.rb +65 -0
  20. data/lib/node.rb +57 -0
  21. data/lib/shortest_path/all_pair.rb +35 -0
  22. data/lib/shortest_path/bellman_ford.rb +60 -0
  23. data/lib/shortest_path/dijkstra.rb +74 -0
  24. data/lib/shortest_path/floyd_warshall.rb +68 -0
  25. data/lib/shortest_path/johnson_all_pair.rb +64 -0
  26. data/lib/shortest_path/single_source.rb +32 -0
  27. data/spec/breadth_first_search_spec.rb +145 -0
  28. data/spec/connected_components_spec.rb +50 -0
  29. data/spec/depth_first_search_spec.rb +89 -0
  30. data/spec/edge_spec.rb +58 -0
  31. data/spec/graph_generator_spec.rb +277 -0
  32. data/spec/graph_spec.rb +269 -0
  33. data/spec/jruby/renderer_spec.rb +214 -0
  34. data/spec/layout/layout_spec.rb +146 -0
  35. data/spec/node_spec.rb +179 -0
  36. data/spec/rspec_helper.rb +9 -0
  37. data/spec/rspec_suite.rb +12 -0
  38. data/spec/shortest_path/bellman_ford_spec.rb +101 -0
  39. data/spec/shortest_path/dijkstra_spec.rb +133 -0
  40. data/spec/shortest_path/floyd_warshall_spec.rb +84 -0
  41. data/spec/shortest_path/johnson_all_pair_spec.rb +90 -0
  42. metadata +43 -2
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+
3
+ require 'layout/layout'
4
+
5
+ module Doodl
6
+ class CollapseLayout < Layout
7
+
8
+ def initialize(view)
9
+ super(view)
10
+ @steps = 45
11
+ end
12
+
13
+ def dolayout(graph)
14
+ graph.each_node do |node|
15
+ @locations[node] = Location.new(@view.width / 2, @view.height / 2)
16
+ end
17
+ changed
18
+ notify_observers(self)
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,105 @@
1
+ # encoding: utf-8
2
+
3
+ require "layout/layout"
4
+
5
+ module Doodl
6
+
7
+ class FRLayout < Layout
8
+
9
+ def initialize(view)
10
+ super(view)
11
+ @max_iterations = 500
12
+ @max_dimension = [view.width, view.height].max
13
+ end
14
+
15
+ def dolayout(graph)
16
+ setup(graph)
17
+ while (@iterations < @max_iterations and @temperature > 1.0/@max_dimension )
18
+ step(graph)
19
+ changed
20
+ notify_observers(self)
21
+ end
22
+ end
23
+
24
+ def setup(graph)
25
+ @temperature = @max_dimension / 10.0
26
+ @force_constant = Math.sqrt(@view.width * @view.height / graph.num_nodes) * 0.75
27
+ @disp = Hash.new
28
+ graph.each_node { |n| @disp[n] = Location.new(0, 0) }
29
+
30
+ randomize_locations(graph)
31
+ end
32
+
33
+ private
34
+
35
+ def randomize_locations(graph)
36
+ random = RandomLayout.new(@view)
37
+ random.graph = graph
38
+ random.layout
39
+ @locations = random.locations
40
+ changed
41
+ notify_observers(self)
42
+ end
43
+
44
+ def step(graph)
45
+ @iterations += 1
46
+ calc_repulsive_forces(graph)
47
+ calc_attractive_forces(graph)
48
+ calc_positions(graph)
49
+ cool
50
+ end
51
+
52
+ def calc_repulsive_forces(graph)
53
+ graph.each_node do |n|
54
+ graph.each_node do |m|
55
+ if (n != m)
56
+ delta = @locations[n] - @locations[m]
57
+ distance = delta.safedistance
58
+ force = (@force_constant ** 2) / distance
59
+ @disp[n] += delta * force / distance
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ def calc_attractive_forces(graph)
66
+ graph.each_edge do |edge|
67
+ if (edge.source != edge.target)
68
+ delta = @locations[edge.source] - @locations[edge.target]
69
+ distance = delta.safedistance
70
+ force = distance ** 2 / @force_constant
71
+ @disp[edge.source] -= delta * force / distance
72
+ @disp[edge.target] += delta * force / distance
73
+ end
74
+ end
75
+ end
76
+
77
+ def calc_positions(graph)
78
+ graph.each_node do |n|
79
+ nl = @locations[n]
80
+ distance = @disp[n].safedistance
81
+ x = nl.x + (@disp[n].x / distance) * [@temperature, @disp[n].x.abs].min
82
+ y = nl.y + (@disp[n].y / distance) * [@temperature, @disp[n].y.abs].min
83
+ @locations[n] = wall_bounce(Location.new(x, y))
84
+ end
85
+ end
86
+
87
+ def wall_bounce(location)
88
+ location.x = [[@view.width * 0.1, location.x].max, @view.width * 0.9].min
89
+ location.y = [[@view.height * 0.1, location.y].max, @view.height * 0.9].min
90
+ return location
91
+ end
92
+
93
+ def cool
94
+ @temperature = @temperature * (1 - @iterations.to_f / @max_iterations)
95
+ end
96
+
97
+ end
98
+
99
+ class Location
100
+ def safedistance
101
+ [self.length, Float::EPSILON].max
102
+ end
103
+ end
104
+
105
+ end
@@ -0,0 +1,77 @@
1
+ # encoding: utf-8
2
+
3
+ require 'layout/layout'
4
+
5
+ require 'depth_first_search'
6
+
7
+ module Doodl
8
+
9
+ class ISOMLayout < Layout
10
+ def initialize(view)
11
+ super(view)
12
+ @interval = 0
13
+ @finished = false
14
+ @max_epoch = 700
15
+ @initial_adaption = 0.9
16
+ @min_adaption = 0
17
+ @cooling_factor = 2.0
18
+ @finished = false
19
+
20
+ @epoch = 1
21
+ @interval = 100
22
+ @radius = 5
23
+ @minimal_radius = 1
24
+ @adaption = @initial_adaption
25
+ end
26
+
27
+ def dolayout(graph)
28
+ $LOG.info "Started ISOMLayout" if $LOG
29
+ setup(graph)
30
+ changed
31
+ notify_observers(self)
32
+ while (@epoch <= @max_epoch)
33
+ step(graph)
34
+ @epoch += 1
35
+ changed
36
+ notify_observers(self)
37
+ end
38
+ $LOG.info "Finished ISOMLayout" if $LOG
39
+ end
40
+
41
+ def setup(graph)
42
+ random = RandomLayout.new(@view)
43
+ random.graph=(graph)
44
+ random.layout
45
+ @locations = random.locations
46
+ end
47
+
48
+ def step(graph)
49
+ adjust(graph)
50
+ calc_adaption(graph)
51
+ @iterations += 1
52
+ @radius -= 1 if (@epoch % @interval == 0)
53
+ end
54
+
55
+ def calc_adaption(graph)
56
+ @adaption = [@min_adaption, @initial_adaption* Math.exp(-1 * @cooling_factor.to_f*(@epoch.to_f/@max_epoch))].max
57
+ end
58
+
59
+ def adjust(graph)
60
+ rloc = Location.new(rand(@view.width), rand(@view.height))
61
+ winner = graph.nodes.min { |a, b| @locations[a].dist(rloc) <=> @locations[b].dist(rloc) }
62
+ dfs = DepthFirstSearch.new(graph, winner)
63
+ graph.each_node do |node|
64
+ nloc = @locations[node]
65
+ distance = dfs.dist[node]
66
+ if (distance < @radius)
67
+ delta_x = 2 ** (-distance) * @adaption*(nloc.x-rloc.x)
68
+ delta_y = 2 ** (-distance) * @adaption*(nloc.y-rloc.y)
69
+ delta = Location.new(delta_x, delta_y)
70
+ @locations[node] -= delta
71
+ end
72
+ end
73
+ end
74
+
75
+ end
76
+
77
+ end
@@ -0,0 +1,203 @@
1
+ # encoding: utf-8
2
+
3
+ require "layout/layout"
4
+
5
+ require "facets/array/combination"
6
+ require "shortest_path/johnson_all_pair"
7
+
8
+ module Doodl
9
+
10
+ class KKLayout < Layout
11
+ attr_reader :dm
12
+
13
+ K = 1.0
14
+ EPSILON = 0.1
15
+ LENGHT_FACTOR = 0.9
16
+ MAXITERATIONS = 2000
17
+ DISCONNECTED_MULTIPLIER = 0.5
18
+
19
+ def initialize(view)
20
+ super(view)
21
+ @adjust_for_gravity = true
22
+ @exchange_vertices = true
23
+ end
24
+
25
+ def dolayout(graph)
26
+ $LOG.info "Started KKLayout" if $LOG
27
+ setup(graph)
28
+ while (not @stabilized and @iterations < MAXITERATIONS)
29
+ step(graph)
30
+ @iterations += 1
31
+ changed
32
+ notify_observers(self)
33
+ end
34
+ $LOG.info "Finished KKLayout" if $LOG
35
+ end
36
+
37
+ def setup(graph)
38
+ initial = CircleLayout.new(@view)
39
+ initial.graph = graph
40
+ initial.layout
41
+ @locations = initial.locations
42
+ changed
43
+ notify_observers(self)
44
+ $LOG.info "Finished Randomizing"
45
+
46
+ @stabilized = false
47
+ @nodes = graph.nodes
48
+ japsp = JohnsonAllPairShortestPaths.new(graph)
49
+ distance = japsp.dist
50
+ diameter = japsp.diameter
51
+ @L0 = [@view.width, @view.height].min
52
+ @L = @L0 / diameter * LENGHT_FACTOR
53
+ @dm = {}
54
+ walk do |i, j|
55
+ d_ij = distance[[i, j]]
56
+ d_ji = distance[[j, i]]
57
+ d = [diameter * DISCONNECTED_MULTIPLIER, d_ij, d_ji].min
58
+ @dm[[i, j]] = d
59
+ @dm[[j, i]] = d
60
+ end
61
+
62
+ end
63
+
64
+
65
+ def step(graph)
66
+ energy = calculate_energy
67
+ old_locations = @locations.clone
68
+ pm = @nodes.max { |x, y| calculate_delta_m(x) <=> calculate_delta_m(y) }
69
+ i = 0
70
+ 100.times do |i|
71
+ @locations[pm] = @locations[pm] + calculate_delta_xy(pm)
72
+ deltam = calculate_delta_m(pm)
73
+ break if (deltam < EPSILON)
74
+ end
75
+
76
+ adjust_for_gravity if @adjust_for_gravity
77
+
78
+ if (@exchange_vertices and calculate_delta_m(pm) < EPSILON)
79
+ energy = calculate_energy
80
+ walk do |i, j|
81
+ xenergy = calculate_energy_if_exchanged(i, j)
82
+ if (energy > xenergy)
83
+ li = locations[i]
84
+ lj = locations[j]
85
+ temp = li
86
+ li = lj
87
+ lj = temp
88
+ break
89
+ end
90
+ end
91
+ end
92
+ diff = 0
93
+ @locations.each_key { |key| diff += (@locations[key] - old_locations[key]).length }
94
+ @stabilized = true if (diff < graph.num_nodes)
95
+ end
96
+
97
+ def calculate_energy
98
+ energy = 0
99
+ walk do |i, j|
100
+ dist = dm[[i, j]];
101
+ l_ij = @L * dist;
102
+ k_ij = K / (dist * dist)
103
+ delta = locations[i] - locations[j]
104
+ d = delta.length
105
+
106
+ energy += k_ij / 2 * (delta.x * delta.x + delta.y * delta.y + l_ij * l_ij -
107
+ 2 * l_ij * d)
108
+ end
109
+
110
+ return energy
111
+ end
112
+
113
+ def calculate_delta_m(m)
114
+ dEdm = Location.new(0, 0)
115
+ @nodes.each do |i|
116
+ if (i != m)
117
+ dist = dm[[m,i]]
118
+ l_mi = @L * dist
119
+ k_mi = K / (dist * dist)
120
+
121
+ delta = locations[m] - locations[i]
122
+ d = delta.length
123
+ common = k_mi * (1 - l_mi / d)
124
+ dEdm += delta * common
125
+ end
126
+ end
127
+ return dEdm.length
128
+ end
129
+
130
+ def calculate_delta_xy(m)
131
+ dE_dxm = 0
132
+ dE_dym = 0
133
+ d2E_d2xm = 0
134
+ d2E_dxmdym = 0
135
+ d2E_dymdxm = 0
136
+ d2E_d2ym = 0
137
+
138
+ @nodes.each do |i|
139
+ if (i != m)
140
+ dist = dm[[m, i]]
141
+ l_mi = @L * dist
142
+ k_mi = K / (dist * dist)
143
+ d = locations[m] - locations[i]
144
+ dist = d.length
145
+ ddd = dist * dist * dist
146
+
147
+ dE_dxm += k_mi * (1 - l_mi / dist) * d.x
148
+ dE_dym += k_mi * (1 - l_mi / dist) * d.y
149
+ d2E_d2xm += k_mi * (1 - l_mi * d.y * d.y / ddd)
150
+ d2E_dxmdym += k_mi * l_mi * d.x * d.y / ddd
151
+ d2E_d2ym += k_mi * (1 - l_mi * d.x * d.x / ddd)
152
+ end
153
+
154
+ end
155
+ d2E_dymdxm = d2E_dxmdym
156
+
157
+ denomi = d2E_d2xm * d2E_d2ym - d2E_dxmdym * d2E_dymdxm
158
+ delta_x = (d2E_dxmdym * dE_dym - d2E_d2ym * dE_dxm) / denomi
159
+ delta_y = (d2E_dymdxm * dE_dxm - d2E_d2xm * dE_dym) / denomi
160
+ Location.new(delta_x, delta_y)
161
+ end
162
+
163
+ def calculate_energy_if_exchanged(p, q)
164
+ energy = 0
165
+ walk do |i, j|
166
+ ii = i;
167
+ jj = j;
168
+ ii = q if (i == p)
169
+ jj = p if (j == q)
170
+
171
+ dist = dm[[i,j]]
172
+ l_ij = @L * dist
173
+ k_ij = K / (dist * dist)
174
+ delta = locations[ii] - locations[jj]
175
+ d = Math.sqrt(delta.x * delta.x + delta.y * delta.y)
176
+
177
+ energy += k_ij / 2 * (delta.x * delta.x + delta.y * delta.y + l_ij * l_ij -
178
+ 2 * l_ij * d)
179
+ end
180
+ return energy
181
+ end
182
+
183
+ def adjust_for_gravity
184
+ gcenter = Location.new(0, 0)
185
+ @locations.each_value do |loc|
186
+ gcenter += loc
187
+ end
188
+ gcenter /= @locations.size
189
+ @locations.each_pair do |key, value|
190
+ @locations[key] = value + (@view.center - gcenter)
191
+ end
192
+ end
193
+
194
+ private
195
+
196
+ def walk
197
+ @nodes.combination(2).to_a.each do |tupel|
198
+ yield(tupel[0], tupel[1])
199
+ end
200
+ end
201
+
202
+ end #end class
203
+ end #end modulse
@@ -0,0 +1,240 @@
1
+ # encoding: utf-8
2
+
3
+ require 'forwardable'
4
+ require 'observer'
5
+
6
+ module Doodl
7
+
8
+ class Layout
9
+ extend Forwardable
10
+ include Observable
11
+
12
+ attr_reader :iterations, :locations, :graph
13
+
14
+ def initialize(view)
15
+ @view = view
16
+ @locations = Hash.new
17
+ end
18
+
19
+ def graph=(g)
20
+ @graph = g
21
+ @locations.clear
22
+ end
23
+
24
+ def init(graph)
25
+ end
26
+
27
+ def finished?
28
+ @finished
29
+ end
30
+
31
+ def layout(g = nil)
32
+ if g
33
+ self.graph = g
34
+ end
35
+ if @graph
36
+ @finished = false
37
+ @iterations = 0
38
+ init(@graph)
39
+ dolayout(@graph)
40
+ @finished = true
41
+ changed
42
+ notify_observers(self)
43
+ end
44
+ end
45
+
46
+ def_delegators(:@locations, :each, :[], :[]=, :delete)
47
+
48
+ def_delegator(:@locations, :each_pair, :each_node_location)
49
+
50
+ def get_nearest_node(x, y)
51
+ origin = Location.new(x, y)
52
+ nearest = @locations.inject do |memo, cmp|
53
+ memo[1].dist(origin) < cmp[1].dist(origin) ? memo : cmp
54
+ end
55
+ return nearest[0]
56
+ end
57
+
58
+ def get_nearest_node_within(x, y, radius)
59
+ node = get_nearest_node(x, y)
60
+ if @locations[node].dist(Location.new(x, y)) < radius
61
+ return node
62
+ else
63
+ return nil
64
+ end
65
+ end
66
+
67
+ def set_location(node, x, y, notify = false)
68
+ @locations[node] = Location.new(x, y)
69
+ if notify
70
+ changed
71
+ notify_observers(nil)
72
+ end
73
+ end
74
+
75
+ def rotate(theta)
76
+ center = Location.new(@view.width/2, @view.height/2)
77
+ @graph.each_node do |node|
78
+ vector = @locations[node] - center
79
+ vector.rotate!(theta)
80
+ @locations[node] = vector.add!(center)
81
+ end
82
+ end
83
+
84
+ def move(pad_x, pad_y)
85
+ @locations.each_value { |location| location.add!(pad_x, pad_y ) }
86
+ end
87
+
88
+ def center_x
89
+ pad_l = @locations.values.min { |a, b| a.x <=> b.x }.x
90
+ pad_r = @view.width - @locations.values.max { |a, b| a.x <=> b.x }.x
91
+ pad = ((pad_l + pad_r) / 2.0) - pad_l
92
+ @locations.each_value { |location| location.add!(pad, 0) }
93
+ end
94
+
95
+ def center_y
96
+ pad_t = @locations.values.min { |a, b| a.y <=> b.y }.y
97
+ pad_b = @view.height - @locations.values.max { |a, b| a.y <=> b.y }.y
98
+ pad = ((pad_t + pad_b) / 2.0) - pad_t
99
+ @locations.each_value { |location| location.add!(0, pad) }
100
+ end
101
+
102
+ def deep_copy
103
+ clone = self.clone
104
+ clone.locations = self.locations.clone
105
+ return clone
106
+ end
107
+
108
+ end
109
+
110
+ class RandomLayout < Layout
111
+
112
+ # def initialize(view)
113
+ # super(view)
114
+ # end
115
+
116
+ def dolayout(graph)
117
+ $LOG.info "Started RandomLayout" if $LOG
118
+ width = @view.width * 0.9
119
+ height = @view.height * 0.9
120
+ graph.each_node do |node|
121
+ @locations[node] = Location.new(rand(width), rand(height))
122
+ end
123
+ $LOG.info "Finished RandomLayout" if $LOG
124
+ end
125
+
126
+ end
127
+
128
+ class CircleLayout < Layout
129
+
130
+ def initialize(view)
131
+ super(view)
132
+ end
133
+
134
+ def dolayout(graph)
135
+ $LOG.info "Started CircleLayout" if $LOG
136
+ center = @view.center
137
+ r = [center.x, center.y].min * 0.75
138
+ main = 2 * Math::PI / graph.num_nodes
139
+ graph.each_node_with_index do |node, index|
140
+ x = center.x + r * Math.sin(index * main)
141
+ y = center.y + r * Math.cos(index * main)
142
+ @locations[node] = Location.new(x, y)
143
+ end
144
+ $LOG.info "Finished CircleLayout" if $LOG
145
+ end
146
+
147
+ end
148
+
149
+ class Location
150
+ attr_accessor :x, :y
151
+
152
+ def initialize(a, b)
153
+ if a.is_a?(Numeric) and b.is_a?(Numeric)
154
+ @x, @y = a, b
155
+ else
156
+ @x = b.x - a.x
157
+ @y = b.y - a.y
158
+ end
159
+ end
160
+
161
+ def dist(other)
162
+ Math.sqrt((@x - other.x) ** 2 + (@y - other.y) ** 2)
163
+ end
164
+
165
+ def -@
166
+ self * (-1)
167
+ end
168
+
169
+ def +(other)
170
+ Location.new(@x + other.x, @y + other.y)
171
+ end
172
+
173
+ def -(other)
174
+ Location.new(@x - other.x, @y - other.y)
175
+ end
176
+
177
+ def *(scalar)
178
+ Location.new(scalar * @x, scalar * @y)
179
+ end
180
+
181
+ def /(scalar)
182
+ Location.new(@x / scalar, @y / scalar)
183
+ end
184
+
185
+ def div!(scalar)
186
+ @x /= scalar
187
+ @y /= scalar
188
+ return self
189
+ end
190
+
191
+ def mul!(scalar)
192
+ @x *= scalar
193
+ @y *= scalar
194
+ return self
195
+ end
196
+
197
+ def add!(a, b = nil)
198
+ if b
199
+ @x += a
200
+ @y += b
201
+ else
202
+ @x += a.x
203
+ @y += a.y
204
+ end
205
+ return self
206
+ end
207
+
208
+ def sub!(a, b = nil)
209
+ if b
210
+ add!(-a, -b)
211
+ else
212
+ add!(-a)
213
+ end
214
+ end
215
+
216
+ def length
217
+ Math.sqrt(@x**2 + @y**2)
218
+ end
219
+
220
+ def normalize!
221
+ m = length
222
+ div!(length)
223
+ return self
224
+ end
225
+
226
+ def rotate_radians!(theta)
227
+ m = length
228
+ a = Math.cos(theta) * @x - Math.sin(theta) * @y
229
+ b = Math.sin(theta) * @x + Math.cos(theta) * @y
230
+ @x, @y = a, b
231
+ return self
232
+ end
233
+
234
+ def rotate!(theta)
235
+ self.rotate_radians!((Math::PI/180.0) * theta)
236
+ end
237
+
238
+ end
239
+
240
+ end