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.
- data/lib/app/selfrunning.rb +185 -0
- data/lib/app/simple_app.rb +49 -0
- data/lib/app/simple_controller.rb +584 -0
- data/lib/app/simple_model.rb +292 -0
- data/lib/app/simple_view.rb +148 -0
- data/lib/breadth_first_search.rb +69 -0
- data/lib/connected_components.rb +29 -0
- data/lib/depth_first_search.rb +73 -0
- data/lib/edge.rb +57 -0
- data/lib/graph.rb +365 -0
- data/lib/graph_canvas.rb +187 -0
- data/lib/graph_generator.rb +121 -0
- data/lib/jruby/renderer.rb +413 -0
- data/lib/layout/collapse_layout.rb +23 -0
- data/lib/layout/fr_layout.rb +105 -0
- data/lib/layout/isom_layout.rb +77 -0
- data/lib/layout/kk_layout.rb +203 -0
- data/lib/layout/layout.rb +240 -0
- data/lib/layout/morph_layout.rb +65 -0
- data/lib/node.rb +57 -0
- data/lib/shortest_path/all_pair.rb +35 -0
- data/lib/shortest_path/bellman_ford.rb +60 -0
- data/lib/shortest_path/dijkstra.rb +74 -0
- data/lib/shortest_path/floyd_warshall.rb +68 -0
- data/lib/shortest_path/johnson_all_pair.rb +64 -0
- data/lib/shortest_path/single_source.rb +32 -0
- data/spec/breadth_first_search_spec.rb +145 -0
- data/spec/connected_components_spec.rb +50 -0
- data/spec/depth_first_search_spec.rb +89 -0
- data/spec/edge_spec.rb +58 -0
- data/spec/graph_generator_spec.rb +277 -0
- data/spec/graph_spec.rb +269 -0
- data/spec/jruby/renderer_spec.rb +214 -0
- data/spec/layout/layout_spec.rb +146 -0
- data/spec/node_spec.rb +179 -0
- data/spec/rspec_helper.rb +9 -0
- data/spec/rspec_suite.rb +12 -0
- data/spec/shortest_path/bellman_ford_spec.rb +101 -0
- data/spec/shortest_path/dijkstra_spec.rb +133 -0
- data/spec/shortest_path/floyd_warshall_spec.rb +84 -0
- data/spec/shortest_path/johnson_all_pair_spec.rb +90 -0
- 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
|