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,187 @@
1
+ # encoding: utf-8
2
+
3
+ require 'layout'
4
+
5
+ require 'java'
6
+
7
+
8
+ include_class 'java.awt.Color'
9
+ include_class 'java.awt.Font'
10
+ include_class 'java.awt.geom.Ellipse2D'
11
+ include_class 'java.awt.geom.Line2D'
12
+ include_class 'java.awt.RenderingHints'
13
+ include_class 'java.awt.BasicStroke'
14
+ include_class 'java.awt.Polygon'
15
+ include_class 'java.awt.font.TextLayout'
16
+
17
+ include_class 'java.util.concurrent.ConcurrentLinkedQueue'
18
+
19
+
20
+ class GraphCanvas < java.awt.Canvas
21
+ include java.awt.event.MouseListener
22
+
23
+ attr_accessor :graph, :layout
24
+ attr_accessor :iterative
25
+
26
+ def initialize
27
+ super
28
+ @iterative = true
29
+
30
+ @frame = 0
31
+ @queue = ConcurrentLinkedQueue.new
32
+
33
+ @d = 25
34
+ @poly = Polygon.new
35
+ @poly.addPoint(0, 0)
36
+ @poly.addPoint(6, 14)
37
+ @poly.addPoint(0, 11)
38
+ @poly.addPoint(-6, 14)
39
+ @poly.addPoint(0, 0)
40
+ setBackground(Color::black)
41
+ end
42
+
43
+ def update_layout(layout)
44
+ if @iterative
45
+ if @layout != layout
46
+ @layout = layout
47
+ @queue.clear
48
+ end
49
+ @queue.add(layout.deep_copy)
50
+ repaint unless @painting
51
+ end
52
+ end
53
+
54
+ def paint(g)
55
+ if @queue.size > 0
56
+ @painting = true
57
+ @queued_layout = @queue.poll
58
+ paint_graph(g)
59
+ java.lang.Thread.sleep(20)
60
+ repaint
61
+ elsif @layout
62
+ @queued_layout = @layout
63
+ paint_graph(g)
64
+ @painting = false
65
+ end
66
+ end
67
+
68
+ def paint_graph(g)
69
+ if (@queued_layout.graph)
70
+ paint_info(g)
71
+ paint_edges(g)
72
+ paint_nodes(g)
73
+ end
74
+ end
75
+
76
+ def paint_info(g)
77
+ rh = RenderingHints.new(RenderingHints.new(RenderingHints::KEY_ANTIALIASING, RenderingHints::VALUE_ANTIALIAS_ON))
78
+ g.setRenderingHints(rh);
79
+ g.setStroke(BasicStroke.new(2))
80
+
81
+ finished = ""
82
+ finished = "finished" if @queued_layout.finished?
83
+ g.setColor(Color::white)
84
+ g.drawString("Iteration: #{@queued_layout.iterations}", 10, 20)
85
+ g.drawString("Layout: #{@queued_layout.class.name} #{finished}", 10, self.height - 20)
86
+ end
87
+
88
+ def paint_node(g, node, location)
89
+ shape = Ellipse2D::Double.new(location.x-@d/2.0, location.y-@d/2.0, @d, @d)
90
+ g.setColor(Color::black)
91
+ g.fill(shape)
92
+ if @queued_layout.graph.node_data(:is_clicked?) and @queued_layout.graph.node_data(:is_clicked?)[node]
93
+ g.setColor(Color::blue)
94
+ elsif @queued_layout.graph.node_data(:path) and @queued_layout.graph.node_data(:path)[node]
95
+ g.setColor(Color::red)
96
+ else
97
+ g.setColor(Color::white)
98
+ end
99
+ g.draw(shape)
100
+ end
101
+
102
+ def paint_nodes(g)
103
+ @queued_layout.each_node_location do |node, location|
104
+ paint_node(g, node, location)
105
+ end
106
+ end
107
+
108
+ def paint_edge(g, edge)
109
+ s = @queued_layout[edge.source]
110
+ t = @queued_layout[edge.target]
111
+ line = Line2D::Double.new(s.x, s.y, t.x, t.y)
112
+ if (@queued_layout.graph.has_edge_data_key?(:path) and @queued_layout.graph.edge_data(:path)[edge])
113
+ g.setColor(Color::ORANGE)
114
+ g.setStroke(BasicStroke.new(6))
115
+ g.draw(line)
116
+ end
117
+ if (@queued_layout.graph.edge_data(:is_clicked?) and @queued_layout.graph.edge_data(:is_clicked?)[edge])
118
+ g.setColor(Color::blue)
119
+ else
120
+ g.setColor(Color::white)
121
+ end
122
+ if (edge.directed?)
123
+ paint_arrowhead(g, edge)
124
+ end
125
+ g.setStroke(BasicStroke.new(2))
126
+ g.draw(line)
127
+ end
128
+
129
+ def paint_arrowhead(g, edge)
130
+ a = @queued_layout[edge.source]
131
+ b = @queued_layout[edge.target]
132
+ g.setStroke(BasicStroke.new(2))
133
+ line = Line2D::Double.new(a.x, a.y, b.x, b.y)
134
+ g.draw(line)
135
+ teta = Math::atan2((a.x - b.x), a.y - b.y)
136
+ dist = Math.sqrt((a.x - b.x)**2 + (a.y - b.y)**2)
137
+ frac = @d/2.0 / dist
138
+ px = b.x - frac * (b.x - a.x)
139
+ py = b.y - frac * (b.y - a.y)
140
+
141
+ at = g.getTransform
142
+ g.translate(px, py)
143
+ g.rotate(-teta)
144
+
145
+ if (@queued_layout.graph.has_edge_data_key?(:path) and @queued_layout.graph.edge_data(:path)[edge])
146
+ color = g.getColor
147
+ stroke = g.getStroke
148
+ g.setColor(Color::ORANGE)
149
+ g.setStroke(BasicStroke.new(6))
150
+ g.draw(@poly)
151
+ g.setStroke(stroke)
152
+ g.setColor(color)
153
+ end
154
+ g.draw(@poly)
155
+ g.fill(@poly)
156
+
157
+
158
+ g.setTransform(at)
159
+ end
160
+
161
+ def paint_edges(g)
162
+ g.setColor(Color::white)
163
+ @queued_layout.graph.each_edge do |edge|
164
+ if edge.source == edge.target
165
+ paint_ref_edge(g, edge)
166
+ else
167
+ paint_edge(g, edge)
168
+ end
169
+ end
170
+ end
171
+
172
+ def paint_ref_edges(g, node)
173
+ loc = @layout[node]
174
+ shape = Ellipse2D::Double.new(loc.x, loc.y, 40, 40)
175
+ g.setColor(Color::white)
176
+ g.draw(shape)
177
+ end
178
+
179
+ def width
180
+ return self.getWidth
181
+ end
182
+
183
+ def height
184
+ return self.getHeight
185
+ end
186
+
187
+ end
@@ -0,0 +1,121 @@
1
+ # encoding: utf-8
2
+
3
+ require "facets/array/combination"
4
+
5
+ module Doodl
6
+
7
+ module GraphGenerator
8
+
9
+ def gen_random_graph(number_of_nodes, number_of_links = nil)
10
+ if number_of_links
11
+ raise ArgumentError if directed? and number_of_links > (number_of_nodes*(number_of_nodes-1))
12
+ raise ArgumentError if (not directed? and number_of_links > (number_of_nodes*(number_of_nodes-1)/2))
13
+ else
14
+ if directed?
15
+ number_of_links = rand(number_of_nodes*(number_of_nodes-1)+1)
16
+ else
17
+ number_of_links = rand(number_of_nodes*(number_of_nodes-1)/2+1)
18
+ end
19
+ end
20
+ @nodes = []
21
+ number_of_nodes.times { self.add_node }
22
+ combinations = combinations(@nodes)
23
+ number_of_links.times do
24
+ tupel = combinations.delete_at(rand(combinations.size))
25
+ self.add_edge(tupel[0], tupel[1])
26
+ end
27
+ self.changed
28
+ self.notify_observers(self)
29
+ end
30
+
31
+ def gen_connected_graph(number_of_nodes)
32
+ @nodes = []
33
+ number_of_nodes.times { self.add_node }
34
+
35
+ combinations(@nodes).each { |tupel| add_edge(tupel[0], tupel[1]) }
36
+ self.changed
37
+ self.notify_observers(self)
38
+ end
39
+
40
+ def gen_binary_tree(depth)
41
+ @nodes = []
42
+ ancestors = [self.add_node]
43
+ depth.times do |index|
44
+ children = []
45
+ ancestors.each do |node|
46
+ a = self.add_node
47
+ b = self.add_node
48
+ children << a << b
49
+ self.add_edge(node, a, false)
50
+ self.add_edge(node, b, false)
51
+ end
52
+ ancestors = children
53
+ end
54
+ self.changed
55
+ self.notify_observers(self)
56
+ end
57
+
58
+ def gen_ring_graph(size=3)
59
+ @nodes = []
60
+ link_linear(size)
61
+ if (@nodes.first != @nodes.last and not get_edge(@nodes.last, @nodes.first))
62
+ self.add_edge(@nodes.last, @nodes.first, false)
63
+ end
64
+ self.changed
65
+ self.notify_observers(self)
66
+ end
67
+
68
+ def gen_linear_graph(size)
69
+ @nodes = []
70
+ link_linear(size)
71
+ self.changed
72
+ self.notify_observers(self)
73
+ end
74
+
75
+ def gen_mesh_graph(num_rows, num_columns = nil)
76
+ num_columns = num_rows unless num_columns
77
+ @nodes = []
78
+ rows = []
79
+ num_rows.times { rows << link_linear(num_columns) }
80
+ index = 0
81
+ while index < rows.size - 1
82
+ link_rows(rows[index], rows[index+1])
83
+ index += 1
84
+ end
85
+ self.changed
86
+ self.notify_observers(self)
87
+ end
88
+
89
+ private
90
+
91
+ def combinations(array)
92
+ combinations = []
93
+ array.combination(2).each do |tupel|
94
+ combinations << tupel
95
+ if directed?
96
+ combinations << [tupel.last, tupel.first]
97
+ end
98
+ end
99
+ return combinations
100
+ end
101
+
102
+ def link_linear(num_nodes)
103
+ n = []
104
+ num_nodes.times { n << add_node }
105
+ index = 0
106
+ while index < (n.size - 1)
107
+ add_edge(n[index], n[index+1], false)
108
+ index += 1
109
+ end
110
+ return n
111
+ end
112
+
113
+ def link_rows(one, two)
114
+ one.each_with_index do |node, index|
115
+ add_edge(node, two[index])
116
+ end
117
+ end
118
+
119
+ end
120
+
121
+ end
@@ -0,0 +1,413 @@
1
+ # encoding: utf-8
2
+
3
+ require 'java'
4
+
5
+ include_class 'java.awt.event.MouseMotionAdapter'
6
+ include_class 'java.awt.geom.Point2D'
7
+ include_class 'java.awt.geom.AffineTransform'
8
+ include_class 'java.awt.geom.PathIterator'
9
+ include_class 'java.awt.Color'
10
+ include_class 'java.awt.geom.Ellipse2D'
11
+ include_class 'java.awt.geom.Rectangle2D'
12
+ include_class 'java.awt.Polygon'
13
+ include_class 'java.awt.geom.Point2D'
14
+ include_class 'java.awt.geom.Line2D'
15
+ include_class 'java.awt.BasicStroke'
16
+ include_class 'java.awt.RenderingHints'
17
+ include_class 'javax.swing.SwingUtilities'
18
+ include_class 'java.awt.font.TextLayout'
19
+ include_class 'java.awt.Font'
20
+
21
+ require 'layout/layout'
22
+
23
+ module Doodl
24
+
25
+ class PluggableRenderer < javax.swing.JPanel
26
+
27
+ attr_writer :paint_iterations
28
+ attr_reader :node_shapes, :edge_shapes
29
+
30
+ def initialize
31
+ super
32
+ @paint_iterations = true
33
+ @transform = AffineTransform.new
34
+ @node_shapes, @edge_shapes = [], []
35
+ set_defaults
36
+ setBackground(Color::black)
37
+ setDoubleBuffered(true)
38
+ @tooltip = TooltipMouseListener.new
39
+ addMouseMotionListener(@tooltip)
40
+ end
41
+
42
+ def update_layout(layout)
43
+ if (paint_iterations? or layout.finished?)
44
+ @layout, @graph = layout, layout.graph
45
+ @transform = AffineTransform.new
46
+ if SwingUtilities.isEventDispatchThread
47
+ repaint
48
+ else
49
+ SwingUtilities.invokeAndWait(java.lang.Runnable.impl do
50
+ repaint
51
+ java.lang.Thread.sleep(20)
52
+ end)
53
+ end
54
+ end
55
+ end
56
+
57
+ def paintComponent(g)
58
+ g.setColor(getBackground)
59
+ g.fill(Rectangle2D::Double.new(0, 0, width, height))
60
+ # g.setFont(Font.new("Gill Sans", Font::BOLD, 14))
61
+ rh = RenderingHints.new(
62
+ RenderingHints.new(RenderingHints::KEY_ANTIALIASING, RenderingHints::VALUE_ANTIALIAS_ON))
63
+ g.setTransform(@transform)
64
+ g.setRenderingHints(rh)
65
+ paint_graph(g)
66
+ end
67
+
68
+ def paint_graph(g)
69
+ if @graph and @layout
70
+ @node_shapes.clear
71
+ @edge_shapes.clear
72
+ paint_edges(g)
73
+ paint_nodes(g)
74
+ paint_node_labels(g)
75
+ paint_edge_labels(g)
76
+ paint_tooltip(g)
77
+ end
78
+ end
79
+
80
+ def paint_node(g, node)
81
+ if node_predicate(node)
82
+ shape = node_shape(node)
83
+ @node_shapes << [node, shape]
84
+ g.setColor node_fill_color(node)
85
+ g.fill(shape)
86
+ g.setColor node_stroke_color(node)
87
+ g.setStroke node_stroke(node)
88
+ g.draw(shape)
89
+ end
90
+ end
91
+
92
+ def paint_edge(g, edge)
93
+ if edge_predicate(edge) and node_predicate(edge.source) and node_predicate(edge.target)
94
+ g.setColor edge_stroke_color(edge)
95
+ shape = edge_shape(edge)
96
+ @edge_shapes << [edge, shape]
97
+ g.setStroke edge_stroke(edge)
98
+ g.draw(shape)
99
+ paint_arrow_head(g, edge) if arrow_head_predicate(edge)
100
+ end
101
+ end
102
+
103
+ def paint_arrow_head(g, edge)
104
+ at = g.getTransform
105
+ loc_and_angle = arrow_head_properties(g, edge)
106
+ if loc_and_angle
107
+ g.translate(loc_and_angle[0].x, loc_and_angle[0].y)
108
+ g.rotate(loc_and_angle[1])
109
+ g.setColor arrow_head_fill_color(edge)
110
+ shape = arrow_head_shape(edge)
111
+ g.fill(shape)
112
+ g.setStroke arrow_head_stroke(edge)
113
+ g.setColor arrow_head_stroke_color(edge)
114
+ g.draw(shape)
115
+ g.setTransform(at)
116
+ end
117
+ end
118
+
119
+ def paint_node_label(g, node)
120
+ if node_label_predicate(node) and text = node_label_string(node)
121
+ g.setColor node_label_color(node)
122
+ layout = TextLayout.new(text, g.getFont, g.getFontRenderContext)
123
+ position = node_label_position(node, layout)
124
+ layout.draw(g, position.x, position.y)
125
+ end
126
+ end
127
+
128
+ def paint_edge_label(g, edge)
129
+ if edge_label_predicate(edge) and text = edge_label_string(edge)
130
+ g.setColor edge_label_color(edge)
131
+ layout = TextLayout.new(text, g.getFont, g.getFontRenderContext)
132
+ position = edge_label_position(edge, layout)
133
+ layout.draw(g, position.x, position.y)
134
+ end
135
+ end
136
+
137
+ def paint_tooltip(g)
138
+ element = @tooltip.element
139
+ if element and tooltip_predicate(element)
140
+ temp = g.getTransform
141
+ layout = TextLayout.new(tooltip_string(element), g.getFont, g.getFontRenderContext)
142
+ rect = layout.getBounds
143
+ rect.x -= 3
144
+ rect.y -= 3
145
+ rect.width += 5
146
+ rect.height += 5
147
+ g.translate(@tooltip.position.x + 15, @tooltip.position.y - 15)
148
+ g.setColor(tooltip_fill_color(element))
149
+ g.fill(rect)
150
+ g.setColor(tooltip_stroke_color(element))
151
+ g.setStroke(tooltip_stroke(element))
152
+ g.draw(rect)
153
+ layout.draw(g, 0, 0)
154
+ g.setTransform(temp)
155
+ end
156
+ end
157
+
158
+ def paint_nodes(g)
159
+ @graph.each_node { |node| paint_node(g, node) }
160
+ end
161
+
162
+ def paint_edges(g)
163
+ @graph.each_edge { |edge| paint_edge(g, edge) }
164
+ end
165
+
166
+ def paint_node_labels(g)
167
+ @graph.each_node { |node| paint_node_label(g, node) }
168
+ end
169
+
170
+ def paint_edge_labels(g)
171
+ @graph.each_edge { |edge| paint_edge_label(g, edge) }
172
+ end
173
+
174
+ def paint_iterations?
175
+ @paint_iterations
176
+ end
177
+
178
+ def move(x, y, smooth = true)
179
+ repaint
180
+ end
181
+
182
+ def center_on_node(node)
183
+ @transform.translate(-@transform.getTranslateX, -@transform.getTranslateY)
184
+ location = @layout[node]
185
+ @transform.translate(-location.x + center.x, -location.y + center.y)
186
+ repaint
187
+ end
188
+
189
+ def zoom(f)
190
+ @transform.translate(center.x, center.y)
191
+ @transform.scale(f, f)
192
+ @transform.translate(-center.x, -center.y)
193
+ repaint
194
+ end
195
+
196
+ def zoom_absolute(f)
197
+ @transform.translate(center.x, center.y)
198
+ @transform.scale(f/@transform.getScaleX, f/@transform.getScaleY)
199
+ @transform.translate(-center.x, -center.y)
200
+ repaint
201
+ end
202
+
203
+ def center
204
+ Location.new(width/2.0, height/2.0)
205
+ end
206
+
207
+ def width
208
+ return self.getWidth
209
+ end
210
+
211
+ def height
212
+ return self.getHeight
213
+ end
214
+
215
+ ["shape", "stroke", "stroke_color", "fill_color", "predicate", "label_predicate", "label_string", "label_color"].each do |name|
216
+ ["edge", "node", "arrow_head"].each do |object|
217
+
218
+ define_method("#{object}_#{name}") do |subject|
219
+ instance_variable_get("@#{object}_#{name}_function").call(@graph, @layout, subject)
220
+ end
221
+
222
+ define_method("#{object}_#{name}_function=") do |function|
223
+ instance_variable_set("@#{object}_#{name}_function", function)
224
+ end
225
+
226
+ eval "def #{object}_#{name}_function(&block)
227
+ raise ArgumentError unless block_given?
228
+ @#{object}_#{name}_function = block
229
+ end"
230
+ end
231
+ end
232
+
233
+ ["tooltip_string", "tooltip_stroke_color", "tooltip_fill_color", "tooltip_stroke", "tooltip_predicate"].each do |name|
234
+ define_method("#{name}") do |subject|
235
+ instance_variable_get("@#{name}_function").call(@graph, @layout, subject)
236
+ end
237
+
238
+ define_method("#{name}_function=") do |function|
239
+ instance_variable_set("@#{name}_function", function)
240
+ end
241
+
242
+ eval "def #{name}_function(&block)
243
+ raise ArgumentError unless block_given?
244
+ @#{name}_function = block
245
+ end"
246
+ end
247
+
248
+ def node_label_position_function=(function)
249
+ @node_label_position_function = function
250
+ end
251
+
252
+ def node_label_position_function(&block)
253
+ raise ArgumentError unless block_given?
254
+ @node_label_position_function = block
255
+ end
256
+
257
+ def node_label_position(node, label_layout)
258
+ @node_label_position_function.call(@graph, @layout, node, label_layout)
259
+ end
260
+
261
+ def edge_label_position_function=(function)
262
+ @edge_label_position_function = function
263
+ end
264
+
265
+ def edge_label_position_function(&block)
266
+ raise ArgumentError unless block_given?
267
+ @edge_label_position_function = block
268
+ end
269
+
270
+ def edge_label_position(edge, label_layout)
271
+ @edge_label_position_function.call(@graph, @layout, edge, label_layout)
272
+ end
273
+
274
+ def set_defaults
275
+ node_shape_function { |g, l, n| Ellipse2D::Double.new(l[n].x-10, l[n].y-10, 20, 20) }
276
+ node_stroke_function { BasicStroke.new(2) }
277
+ node_predicate_function { true }
278
+ node_stroke_color_function { Color::white }
279
+ node_fill_color_function { Color::black }
280
+ node_label_predicate_function { false }
281
+ node_label_string_function { |g, l, n| n.to_s }
282
+ node_label_position_function do |g, l, node, layout|
283
+ nb = node_shape(node).getBounds2D
284
+ lb = layout.getBounds
285
+ Point2D::Double.new(nb.getCenterX-lb.getCenterX, nb.getCenterY-lb.getCenterY)
286
+ end
287
+ node_label_color_function { Color::white }
288
+
289
+ edge_shape_function { |g, l, e| Line2D::Double.new(l[e.source].x, l[e.source].y, l[e.target].x, l[e.target].y) }
290
+ edge_predicate_function { true }
291
+ edge_stroke_function { |g, l, e| BasicStroke.new(2) }
292
+ edge_stroke_color_function { Color::white }
293
+ edge_fill_color_function { Color::white }
294
+
295
+ edge_label_predicate_function { true }
296
+ edge_label_string_function { |g, l, e| g.edge_data(:text)[e] if g.has_edge_data_key?(:text) }
297
+ edge_label_color_function { Color::white }
298
+ edge_label_position_function do |g, l, edge, layout|
299
+ shape = edge_shape(edge)
300
+ edge_bounds = shape.getBounds2D
301
+ label_bounds = layout.getBounds
302
+ ex = edge_bounds.getCenterX
303
+ ey = edge_bounds.getCenterY
304
+ lx = label_bounds.getCenterX
305
+ ly = label_bounds.getCenterY
306
+ v = Location.new(l[edge.source], l[edge.target])
307
+ v.normalize!
308
+ v.rotate!(90)
309
+ v.mul!(15)
310
+ v.add!(ex-lx, ey-ly)
311
+ end
312
+
313
+ arrow_head_shape_function { Polygon.new([0, 4, 0, -4, 0].to_java(:int), [0, 14, 11, 14, 0].to_java(:int), 5) }
314
+ arrow_head_predicate_function { |g, l, e| e.directed? }
315
+ arrow_head_stroke_function { BasicStroke.new(2) }
316
+ arrow_head_stroke_color_function { Color::white }
317
+ arrow_head_fill_color_function { Color::white }
318
+
319
+ tooltip_string_function { |g, l, element| element.to_s }
320
+ tooltip_stroke_function { |g, l, element| BasicStroke.new(1) }
321
+ tooltip_stroke_color_function { |g, l, element| Color::WHITE }
322
+ tooltip_fill_color_function { |g, l, element| Color::BLACK }
323
+ tooltip_predicate_function { true }
324
+ end
325
+
326
+ private
327
+
328
+ def arrow_head_properties(g, edge)
329
+ get_lines(node_shape(edge.target)).each do |node_line|
330
+ get_lines(edge_shape(edge)).each do |edge_line|
331
+ if node_line.intersectsLine(edge_line)
332
+ location = get_intersection(node_line, edge_line)
333
+ angle = get_angle(edge_line)
334
+ return [location, angle]
335
+ end
336
+ end
337
+ end
338
+ return nil
339
+ end
340
+
341
+ def get_lines(shape)
342
+ lines = []
343
+ p = shape.getPathIterator(nil, 2.0)
344
+ until p.isDone
345
+ a = Array.new(6).to_java(:double)
346
+ type = p.currentSegment(a)
347
+ case type
348
+ when PathIterator::SEG_MOVETO
349
+ prev = a
350
+ when PathIterator::SEG_LINETO
351
+ lines << Line2D::Double.new(prev[0], prev[1], a[0], a[1])
352
+ prev = a
353
+ end
354
+ p.next
355
+ end
356
+ return lines
357
+ end
358
+
359
+ def get_intersection(line_a, line_b)
360
+ y_a = line_a.getY1
361
+ y_b = line_b.getY1
362
+ x_a = line_a.getX1
363
+ x_b = line_b.getX1
364
+ if (line_a.getX1 == line_a.getX2)
365
+ m_b = (line_b.getY2 - line_b.getY1) / (line_b.getX2 - line_b.getX1)
366
+ x = line_a.getX1
367
+ y = m_b * (x - x_b) + y_b
368
+ elsif (line_b.getX1 == line_b.getX2)
369
+ m_a = (line_a.getY2 - line_a.getY1) / (line_a.getX2 - line_a.getX1)
370
+ x = line_b.getX1
371
+ y = m_a * (x - x_a) + y_a
372
+ else
373
+ m_a = (line_a.getY2 - line_a.getY1) / (line_a.getX2 - line_a.getX1)
374
+ m_b = (line_b.getY2 - line_b.getY1) / (line_b.getX2 - line_b.getX1)
375
+ x = (y_b - y_a + (m_a * x_a) - (m_b * x_b)) / (m_a - m_b)
376
+ y = m_a * (x - x_a) + y_a
377
+ end
378
+ return Point2D::Double.new(x, y)
379
+ end
380
+
381
+ def get_angle(line)
382
+ Math::atan2((line.getX2 - line.getX1), line.getY1 - line.getY2)
383
+ end
384
+
385
+ end
386
+
387
+ class TooltipMouseListener < MouseMotionAdapter
388
+
389
+ attr_reader :position, :element
390
+
391
+ def mouseMoved(event)
392
+ result = event.getSource.node_shapes.select { |tupel| tupel[1].contains(event.getPoint)}.first
393
+ if result == nil
394
+ result = event.getSource.edge_shapes.select { |tupel| tupel[1].ptLineDist(event.getPoint) < 5.0 }.first
395
+ end
396
+ if result
397
+ if @element != result[0]
398
+ @element = result[0]
399
+ @position = event.getPoint
400
+ event.getSource.repaint
401
+ end
402
+ else
403
+ if @element
404
+ @element = nil
405
+ @position = nil
406
+ event.getSource.repaint
407
+ end
408
+ end
409
+ end
410
+
411
+ end
412
+
413
+ end