elkrb 1.0.0
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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +11 -0
- data/Gemfile +13 -0
- data/README.adoc +1028 -0
- data/Rakefile +64 -0
- data/benchmarks/README.md +172 -0
- data/benchmarks/elkjs_benchmark.js +140 -0
- data/benchmarks/elkrb_benchmark.rb +145 -0
- data/benchmarks/fixtures/graphs.json +10777 -0
- data/benchmarks/generate_report.rb +241 -0
- data/benchmarks/generate_test_graphs.rb +154 -0
- data/benchmarks/results/elkrb_results.json +280 -0
- data/benchmarks/results/elkrb_summary.json +285 -0
- data/elkrb.gemspec +39 -0
- data/examples/dot_export_demo.rb +133 -0
- data/examples/hierarchical_graph.rb +19 -0
- data/examples/layout_constraints_demo.rb +272 -0
- data/examples/port_constraints_demo.rb +291 -0
- data/examples/self_loop_demo.rb +391 -0
- data/examples/simple_graph.rb +50 -0
- data/examples/spline_routing_demo.rb +235 -0
- data/exe/elkrb +8 -0
- data/lib/elkrb/cli.rb +224 -0
- data/lib/elkrb/commands/batch_command.rb +66 -0
- data/lib/elkrb/commands/convert_command.rb +130 -0
- data/lib/elkrb/commands/diagram_command.rb +208 -0
- data/lib/elkrb/commands/render_command.rb +52 -0
- data/lib/elkrb/commands/validate_command.rb +241 -0
- data/lib/elkrb/errors.rb +30 -0
- data/lib/elkrb/geometry/bezier.rb +163 -0
- data/lib/elkrb/geometry/dimension.rb +32 -0
- data/lib/elkrb/geometry/point.rb +68 -0
- data/lib/elkrb/geometry/rectangle.rb +86 -0
- data/lib/elkrb/geometry/vector.rb +67 -0
- data/lib/elkrb/graph/edge.rb +95 -0
- data/lib/elkrb/graph/graph.rb +90 -0
- data/lib/elkrb/graph/label.rb +45 -0
- data/lib/elkrb/graph/layout_options.rb +247 -0
- data/lib/elkrb/graph/node.rb +79 -0
- data/lib/elkrb/graph/node_constraints.rb +107 -0
- data/lib/elkrb/graph/port.rb +104 -0
- data/lib/elkrb/graphviz_wrapper.rb +133 -0
- data/lib/elkrb/layout/algorithm_registry.rb +57 -0
- data/lib/elkrb/layout/algorithms/base_algorithm.rb +208 -0
- data/lib/elkrb/layout/algorithms/box.rb +47 -0
- data/lib/elkrb/layout/algorithms/disco.rb +206 -0
- data/lib/elkrb/layout/algorithms/fixed.rb +32 -0
- data/lib/elkrb/layout/algorithms/force.rb +165 -0
- data/lib/elkrb/layout/algorithms/layered/cycle_breaker.rb +86 -0
- data/lib/elkrb/layout/algorithms/layered/layer_assigner.rb +96 -0
- data/lib/elkrb/layout/algorithms/layered/node_placer.rb +77 -0
- data/lib/elkrb/layout/algorithms/layered.rb +49 -0
- data/lib/elkrb/layout/algorithms/libavoid.rb +389 -0
- data/lib/elkrb/layout/algorithms/mrtree.rb +144 -0
- data/lib/elkrb/layout/algorithms/radial.rb +64 -0
- data/lib/elkrb/layout/algorithms/random.rb +43 -0
- data/lib/elkrb/layout/algorithms/rectpacking.rb +93 -0
- data/lib/elkrb/layout/algorithms/spore_compaction.rb +139 -0
- data/lib/elkrb/layout/algorithms/spore_overlap.rb +117 -0
- data/lib/elkrb/layout/algorithms/stress.rb +176 -0
- data/lib/elkrb/layout/algorithms/topdown_packing.rb +183 -0
- data/lib/elkrb/layout/algorithms/vertiflex.rb +174 -0
- data/lib/elkrb/layout/constraints/alignment_constraint.rb +150 -0
- data/lib/elkrb/layout/constraints/base_constraint.rb +72 -0
- data/lib/elkrb/layout/constraints/constraint_processor.rb +134 -0
- data/lib/elkrb/layout/constraints/fixed_position_constraint.rb +87 -0
- data/lib/elkrb/layout/constraints/layer_constraint.rb +71 -0
- data/lib/elkrb/layout/constraints/relative_position_constraint.rb +110 -0
- data/lib/elkrb/layout/edge_router.rb +935 -0
- data/lib/elkrb/layout/hierarchical_processor.rb +299 -0
- data/lib/elkrb/layout/label_placer.rb +338 -0
- data/lib/elkrb/layout/layout_engine.rb +170 -0
- data/lib/elkrb/layout/port_constraint_processor.rb +173 -0
- data/lib/elkrb/options/elk_padding.rb +94 -0
- data/lib/elkrb/options/k_vector.rb +100 -0
- data/lib/elkrb/options/k_vector_chain.rb +135 -0
- data/lib/elkrb/parsers/elkt_parser.rb +248 -0
- data/lib/elkrb/serializers/dot_serializer.rb +339 -0
- data/lib/elkrb/serializers/elkt_serializer.rb +236 -0
- data/lib/elkrb/version.rb +5 -0
- data/lib/elkrb.rb +509 -0
- data/sig/elkrb/constraints.rbs +114 -0
- data/sig/elkrb/geometry.rbs +61 -0
- data/sig/elkrb/graph.rbs +112 -0
- data/sig/elkrb/layout.rbs +107 -0
- data/sig/elkrb/options.rbs +81 -0
- data/sig/elkrb.rbs +32 -0
- metadata +179 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "../lib/elkrb"
|
|
5
|
+
|
|
6
|
+
# Example 1: Simple Self-loop
|
|
7
|
+
puts "=" * 70
|
|
8
|
+
puts "Example 1: Simple Self-loop with Default Settings"
|
|
9
|
+
puts "=" * 70
|
|
10
|
+
|
|
11
|
+
graph1 = Elkrb::Graph::Graph.new(
|
|
12
|
+
id: "simple_self_loop",
|
|
13
|
+
children: [
|
|
14
|
+
Elkrb::Graph::Node.new(
|
|
15
|
+
id: "state1",
|
|
16
|
+
width: 100.0,
|
|
17
|
+
height: 60.0,
|
|
18
|
+
),
|
|
19
|
+
],
|
|
20
|
+
edges: [
|
|
21
|
+
Elkrb::Graph::Edge.new(
|
|
22
|
+
id: "loop1",
|
|
23
|
+
sources: ["state1"],
|
|
24
|
+
targets: ["state1"],
|
|
25
|
+
),
|
|
26
|
+
],
|
|
27
|
+
layout_options: Elkrb::Graph::LayoutOptions.new(
|
|
28
|
+
algorithm: "layered",
|
|
29
|
+
),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
result1 = Elkrb.layout(graph1)
|
|
33
|
+
puts "\nNode position: (#{result1.children[0].x}, #{result1.children[0].y})"
|
|
34
|
+
puts "Self-loop section: #{result1.edges[0].sections.length} section(s)"
|
|
35
|
+
puts "Bend points: #{result1.edges[0].sections[0].bend_points.length}"
|
|
36
|
+
puts "Start: (#{result1.edges[0].sections[0].start_point.x.round(1)}, " \
|
|
37
|
+
"#{result1.edges[0].sections[0].start_point.y.round(1)})"
|
|
38
|
+
puts "End: (#{result1.edges[0].sections[0].end_point.x.round(1)}, " \
|
|
39
|
+
"#{result1.edges[0].sections[0].end_point.y.round(1)})"
|
|
40
|
+
|
|
41
|
+
# Example 2: Self-loop with Custom Side
|
|
42
|
+
puts "\n#{'=' * 70}"
|
|
43
|
+
puts "Example 2: Self-loop on Different Sides"
|
|
44
|
+
puts "=" * 70
|
|
45
|
+
|
|
46
|
+
%w[EAST WEST NORTH SOUTH].each do |side|
|
|
47
|
+
graph = Elkrb::Graph::Graph.new(
|
|
48
|
+
id: "self_loop_#{side.downcase}",
|
|
49
|
+
children: [
|
|
50
|
+
Elkrb::Graph::Node.new(
|
|
51
|
+
id: "node",
|
|
52
|
+
width: 80.0,
|
|
53
|
+
height: 60.0,
|
|
54
|
+
),
|
|
55
|
+
],
|
|
56
|
+
edges: [
|
|
57
|
+
Elkrb::Graph::Edge.new(
|
|
58
|
+
id: "loop",
|
|
59
|
+
sources: ["node"],
|
|
60
|
+
targets: ["node"],
|
|
61
|
+
layout_options: Elkrb::Graph::LayoutOptions.new(
|
|
62
|
+
"elk.selfLoopSide" => side,
|
|
63
|
+
),
|
|
64
|
+
),
|
|
65
|
+
],
|
|
66
|
+
layout_options: Elkrb::Graph::LayoutOptions.new(
|
|
67
|
+
algorithm: "layered",
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
result = Elkrb.layout(graph)
|
|
72
|
+
section = result.edges[0].sections[0]
|
|
73
|
+
puts "\n#{side} side:"
|
|
74
|
+
puts " Start: (#{section.start_point.x.round(1)}, " \
|
|
75
|
+
"#{section.start_point.y.round(1)})"
|
|
76
|
+
puts " Bend points: #{section.bend_points.length}"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Example 3: Multiple Self-loops on Same Node
|
|
80
|
+
puts "\n#{'=' * 70}"
|
|
81
|
+
puts "Example 3: Multiple Self-loops on Same Node"
|
|
82
|
+
puts "=" * 70
|
|
83
|
+
|
|
84
|
+
graph3 = Elkrb::Graph::Graph.new(
|
|
85
|
+
id: "multiple_self_loops",
|
|
86
|
+
children: [
|
|
87
|
+
Elkrb::Graph::Node.new(
|
|
88
|
+
id: "state",
|
|
89
|
+
width: 100.0,
|
|
90
|
+
height: 80.0,
|
|
91
|
+
),
|
|
92
|
+
],
|
|
93
|
+
edges: [
|
|
94
|
+
Elkrb::Graph::Edge.new(
|
|
95
|
+
id: "loop1",
|
|
96
|
+
sources: ["state"],
|
|
97
|
+
targets: ["state"],
|
|
98
|
+
),
|
|
99
|
+
Elkrb::Graph::Edge.new(
|
|
100
|
+
id: "loop2",
|
|
101
|
+
sources: ["state"],
|
|
102
|
+
targets: ["state"],
|
|
103
|
+
),
|
|
104
|
+
Elkrb::Graph::Edge.new(
|
|
105
|
+
id: "loop3",
|
|
106
|
+
sources: ["state"],
|
|
107
|
+
targets: ["state"],
|
|
108
|
+
),
|
|
109
|
+
],
|
|
110
|
+
layout_options: Elkrb::Graph::LayoutOptions.new(
|
|
111
|
+
algorithm: "layered",
|
|
112
|
+
),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
result3 = Elkrb.layout(graph3)
|
|
116
|
+
result3.edges.each_with_index do |edge, i|
|
|
117
|
+
section = edge.sections[0]
|
|
118
|
+
first_bend = section.bend_points[0]
|
|
119
|
+
puts "\nLoop #{i + 1} (#{edge.id}):"
|
|
120
|
+
puts " First bend point x: #{first_bend.x.round(1)}"
|
|
121
|
+
puts " Offset from node edge: " \
|
|
122
|
+
"#{(first_bend.x - result3.children[0].x -
|
|
123
|
+
result3.children[0].width).round(1)}"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Example 4: Self-loop with Spline Routing
|
|
127
|
+
puts "\n#{'=' * 70}"
|
|
128
|
+
puts "Example 4: Self-loop with Spline (Curved) Routing"
|
|
129
|
+
puts "=" * 70
|
|
130
|
+
|
|
131
|
+
graph4 = Elkrb::Graph::Graph.new(
|
|
132
|
+
id: "spline_self_loop",
|
|
133
|
+
children: [
|
|
134
|
+
Elkrb::Graph::Node.new(
|
|
135
|
+
id: "node",
|
|
136
|
+
width: 100.0,
|
|
137
|
+
height: 60.0,
|
|
138
|
+
),
|
|
139
|
+
],
|
|
140
|
+
edges: [
|
|
141
|
+
Elkrb::Graph::Edge.new(
|
|
142
|
+
id: "curved_loop",
|
|
143
|
+
sources: ["node"],
|
|
144
|
+
targets: ["node"],
|
|
145
|
+
),
|
|
146
|
+
],
|
|
147
|
+
layout_options: Elkrb::Graph::LayoutOptions.new(
|
|
148
|
+
algorithm: "layered",
|
|
149
|
+
edge_routing: "SPLINES",
|
|
150
|
+
),
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
result4 = Elkrb.layout(graph4)
|
|
154
|
+
section4 = result4.edges[0].sections[0]
|
|
155
|
+
puts "\nSpline self-loop:"
|
|
156
|
+
puts " Control points: #{section4.bend_points.length}"
|
|
157
|
+
puts " Control point 1: (#{section4.bend_points[0].x.round(1)}, " \
|
|
158
|
+
"#{section4.bend_points[0].y.round(1)})"
|
|
159
|
+
puts " Control point 2: (#{section4.bend_points[1].x.round(1)}, " \
|
|
160
|
+
"#{section4.bend_points[1].y.round(1)})"
|
|
161
|
+
|
|
162
|
+
# Example 5: State Machine with Self-loops
|
|
163
|
+
puts "\n#{'=' * 70}"
|
|
164
|
+
puts "Example 5: State Machine with Self-loops and Transitions"
|
|
165
|
+
puts "=" * 70
|
|
166
|
+
|
|
167
|
+
graph5 = Elkrb::Graph::Graph.new(
|
|
168
|
+
id: "state_machine",
|
|
169
|
+
children: [
|
|
170
|
+
Elkrb::Graph::Node.new(
|
|
171
|
+
id: "idle",
|
|
172
|
+
width: 80.0,
|
|
173
|
+
height: 50.0,
|
|
174
|
+
),
|
|
175
|
+
Elkrb::Graph::Node.new(
|
|
176
|
+
id: "running",
|
|
177
|
+
width: 80.0,
|
|
178
|
+
height: 50.0,
|
|
179
|
+
),
|
|
180
|
+
Elkrb::Graph::Node.new(
|
|
181
|
+
id: "error",
|
|
182
|
+
width: 80.0,
|
|
183
|
+
height: 50.0,
|
|
184
|
+
),
|
|
185
|
+
],
|
|
186
|
+
edges: [
|
|
187
|
+
# Self-loops (states that can transition to themselves)
|
|
188
|
+
Elkrb::Graph::Edge.new(
|
|
189
|
+
id: "idle_wait",
|
|
190
|
+
sources: ["idle"],
|
|
191
|
+
targets: ["idle"],
|
|
192
|
+
),
|
|
193
|
+
Elkrb::Graph::Edge.new(
|
|
194
|
+
id: "running_continue",
|
|
195
|
+
sources: ["running"],
|
|
196
|
+
targets: ["running"],
|
|
197
|
+
),
|
|
198
|
+
# Regular transitions
|
|
199
|
+
Elkrb::Graph::Edge.new(
|
|
200
|
+
id: "start",
|
|
201
|
+
sources: ["idle"],
|
|
202
|
+
targets: ["running"],
|
|
203
|
+
),
|
|
204
|
+
Elkrb::Graph::Edge.new(
|
|
205
|
+
id: "fail",
|
|
206
|
+
sources: ["running"],
|
|
207
|
+
targets: ["error"],
|
|
208
|
+
),
|
|
209
|
+
Elkrb::Graph::Edge.new(
|
|
210
|
+
id: "reset",
|
|
211
|
+
sources: ["error"],
|
|
212
|
+
targets: ["idle"],
|
|
213
|
+
),
|
|
214
|
+
],
|
|
215
|
+
layout_options: Elkrb::Graph::LayoutOptions.new(
|
|
216
|
+
algorithm: "layered",
|
|
217
|
+
direction: "RIGHT",
|
|
218
|
+
),
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
result5 = Elkrb.layout(graph5)
|
|
222
|
+
puts "\nState machine layout:"
|
|
223
|
+
result5.children.each do |node|
|
|
224
|
+
puts " #{node.id}: (#{node.x.round(1)}, #{node.y.round(1)})"
|
|
225
|
+
end
|
|
226
|
+
puts "\nEdges:"
|
|
227
|
+
result5.edges.each do |edge|
|
|
228
|
+
is_self_loop = edge.sources[0] == edge.targets[0]
|
|
229
|
+
puts " #{edge.id}: #{is_self_loop ? 'self-loop' : 'transition'}"
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Example 6: Self-loop with Ports
|
|
233
|
+
puts "\n#{'=' * 70}"
|
|
234
|
+
puts "Example 6: Self-loop Between Ports"
|
|
235
|
+
puts "=" * 70
|
|
236
|
+
|
|
237
|
+
graph6 = Elkrb::Graph::Graph.new(
|
|
238
|
+
id: "port_self_loop",
|
|
239
|
+
children: [
|
|
240
|
+
Elkrb::Graph::Node.new(
|
|
241
|
+
id: "component",
|
|
242
|
+
width: 120.0,
|
|
243
|
+
height: 80.0,
|
|
244
|
+
ports: [
|
|
245
|
+
Elkrb::Graph::Port.new(
|
|
246
|
+
id: "out1",
|
|
247
|
+
x: 120.0,
|
|
248
|
+
y: 20.0,
|
|
249
|
+
side: "EAST",
|
|
250
|
+
),
|
|
251
|
+
Elkrb::Graph::Port.new(
|
|
252
|
+
id: "in1",
|
|
253
|
+
x: 120.0,
|
|
254
|
+
y: 60.0,
|
|
255
|
+
side: "EAST",
|
|
256
|
+
),
|
|
257
|
+
],
|
|
258
|
+
),
|
|
259
|
+
],
|
|
260
|
+
edges: [
|
|
261
|
+
Elkrb::Graph::Edge.new(
|
|
262
|
+
id: "feedback",
|
|
263
|
+
sources: ["out1"],
|
|
264
|
+
targets: ["in1"],
|
|
265
|
+
),
|
|
266
|
+
],
|
|
267
|
+
layout_options: Elkrb::Graph::LayoutOptions.new(
|
|
268
|
+
algorithm: "layered",
|
|
269
|
+
),
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
result6 = Elkrb.layout(graph6)
|
|
273
|
+
section6 = result6.edges[0].sections[0]
|
|
274
|
+
puts "\nPort-based self-loop:"
|
|
275
|
+
puts " Start (out1): (#{section6.start_point.x.round(1)}, " \
|
|
276
|
+
"#{section6.start_point.y.round(1)})"
|
|
277
|
+
puts " End (in1): (#{section6.end_point.x.round(1)}, " \
|
|
278
|
+
"#{section6.end_point.y.round(1)})"
|
|
279
|
+
puts " Bend points: #{section6.bend_points.length}"
|
|
280
|
+
|
|
281
|
+
# Example 7: Finite Automaton
|
|
282
|
+
puts "\n#{'=' * 70}"
|
|
283
|
+
puts "Example 7: Finite Automaton with Self-loops"
|
|
284
|
+
puts "=" * 70
|
|
285
|
+
|
|
286
|
+
graph7 = Elkrb::Graph::Graph.new(
|
|
287
|
+
id: "finite_automaton",
|
|
288
|
+
children: [
|
|
289
|
+
Elkrb::Graph::Node.new(
|
|
290
|
+
id: "q0",
|
|
291
|
+
width: 60.0,
|
|
292
|
+
height: 60.0,
|
|
293
|
+
),
|
|
294
|
+
Elkrb::Graph::Node.new(
|
|
295
|
+
id: "q1",
|
|
296
|
+
width: 60.0,
|
|
297
|
+
height: 60.0,
|
|
298
|
+
),
|
|
299
|
+
Elkrb::Graph::Node.new(
|
|
300
|
+
id: "q2",
|
|
301
|
+
width: 60.0,
|
|
302
|
+
height: 60.0,
|
|
303
|
+
),
|
|
304
|
+
],
|
|
305
|
+
edges: [
|
|
306
|
+
# Self-loops for states reading same symbol
|
|
307
|
+
Elkrb::Graph::Edge.new(
|
|
308
|
+
id: "q0_0",
|
|
309
|
+
sources: ["q0"],
|
|
310
|
+
targets: ["q0"],
|
|
311
|
+
layout_options: Elkrb::Graph::LayoutOptions.new(
|
|
312
|
+
"elk.selfLoopSide" => "NORTH",
|
|
313
|
+
),
|
|
314
|
+
),
|
|
315
|
+
Elkrb::Graph::Edge.new(
|
|
316
|
+
id: "q1_1",
|
|
317
|
+
sources: ["q1"],
|
|
318
|
+
targets: ["q1"],
|
|
319
|
+
layout_options: Elkrb::Graph::LayoutOptions.new(
|
|
320
|
+
"elk.selfLoopSide" => "NORTH",
|
|
321
|
+
),
|
|
322
|
+
),
|
|
323
|
+
# State transitions
|
|
324
|
+
Elkrb::Graph::Edge.new(
|
|
325
|
+
id: "q0_to_q1",
|
|
326
|
+
sources: ["q0"],
|
|
327
|
+
targets: ["q1"],
|
|
328
|
+
),
|
|
329
|
+
Elkrb::Graph::Edge.new(
|
|
330
|
+
id: "q1_to_q2",
|
|
331
|
+
sources: ["q1"],
|
|
332
|
+
targets: ["q2"],
|
|
333
|
+
),
|
|
334
|
+
],
|
|
335
|
+
layout_options: Elkrb::Graph::LayoutOptions.new(
|
|
336
|
+
algorithm: "layered",
|
|
337
|
+
direction: "RIGHT",
|
|
338
|
+
),
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
result7 = Elkrb.layout(graph7)
|
|
342
|
+
puts "\nFinite automaton states:"
|
|
343
|
+
result7.children.each do |node|
|
|
344
|
+
puts " #{node.id}: (#{node.x.round(1)}, #{node.y.round(1)})"
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
self_loops = result7.edges.select { |e| e.sources[0] == e.targets[0] }
|
|
348
|
+
puts "\nSelf-loops (same symbol transitions):"
|
|
349
|
+
self_loops.each do |edge|
|
|
350
|
+
section = edge.sections[0]
|
|
351
|
+
puts " #{edge.id}: #{section.bend_points.length} bend points"
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# Example 8: Mixed Routing Styles
|
|
355
|
+
puts "\n#{'=' * 70}"
|
|
356
|
+
puts "Example 8: Comparing Routing Styles"
|
|
357
|
+
puts "=" * 70
|
|
358
|
+
|
|
359
|
+
%w[ORTHOGONAL SPLINES POLYLINE].each do |style|
|
|
360
|
+
graph = Elkrb::Graph::Graph.new(
|
|
361
|
+
id: "style_#{style.downcase}",
|
|
362
|
+
children: [
|
|
363
|
+
Elkrb::Graph::Node.new(
|
|
364
|
+
id: "node",
|
|
365
|
+
width: 80.0,
|
|
366
|
+
height: 60.0,
|
|
367
|
+
),
|
|
368
|
+
],
|
|
369
|
+
edges: [
|
|
370
|
+
Elkrb::Graph::Edge.new(
|
|
371
|
+
id: "loop",
|
|
372
|
+
sources: ["node"],
|
|
373
|
+
targets: ["node"],
|
|
374
|
+
),
|
|
375
|
+
],
|
|
376
|
+
layout_options: Elkrb::Graph::LayoutOptions.new(
|
|
377
|
+
algorithm: "layered",
|
|
378
|
+
edge_routing: style,
|
|
379
|
+
),
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
result = Elkrb.layout(graph)
|
|
383
|
+
section = result.edges[0].sections[0]
|
|
384
|
+
puts "\n#{style} routing:"
|
|
385
|
+
puts " Bend points: #{section.bend_points.length}"
|
|
386
|
+
puts " Path length: #{section.length.round(2)}"
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
puts "\n#{'=' * 70}"
|
|
390
|
+
puts "All examples completed successfully!"
|
|
391
|
+
puts "=" * 70
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "bundler/setup"
|
|
5
|
+
require "elkrb"
|
|
6
|
+
|
|
7
|
+
# Simple graph layout example
|
|
8
|
+
# This demonstrates the basic usage of ElkRb with a simple node graph
|
|
9
|
+
|
|
10
|
+
# Create a graph with 4 nodes connected in sequence
|
|
11
|
+
graph = Elkrb::Graph.new(
|
|
12
|
+
id: "root",
|
|
13
|
+
children: [
|
|
14
|
+
{ id: "n1", width: 100, height: 50 },
|
|
15
|
+
{ id: "n2", width: 100, height: 50 },
|
|
16
|
+
{ id: "n3", width: 100, height: 50 },
|
|
17
|
+
{ id: "n4", width: 100, height: 50 },
|
|
18
|
+
],
|
|
19
|
+
edges: [
|
|
20
|
+
{ id: "e1", sources: ["n1"], targets: ["n2"] },
|
|
21
|
+
{ id: "e2", sources: ["n2"], targets: ["n3"] },
|
|
22
|
+
{ id: "e3", sources: ["n3"], targets: ["n4"] },
|
|
23
|
+
],
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Configure layout options
|
|
27
|
+
graph.layout_options = {
|
|
28
|
+
"algorithm" => "layered",
|
|
29
|
+
"elk.direction" => "DOWN",
|
|
30
|
+
"spacing.nodeNode" => 50,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# Perform layout
|
|
34
|
+
engine = Elkrb::Layout::LayoutEngine.new
|
|
35
|
+
result = engine.layout(graph)
|
|
36
|
+
|
|
37
|
+
# Display results
|
|
38
|
+
puts "Graph layout completed!"
|
|
39
|
+
puts "=" * 60
|
|
40
|
+
puts "Root dimensions: #{result.width}x#{result.height}"
|
|
41
|
+
puts "\nNode positions:"
|
|
42
|
+
result.children.each do |node|
|
|
43
|
+
puts " #{node.id}: (#{node.x}, #{node.y})"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
puts "\nEdge routing:"
|
|
47
|
+
result.edges.each do |edge|
|
|
48
|
+
puts " #{edge.id}: #{edge.sections&.first&.start_point} -> " \
|
|
49
|
+
"#{edge.sections&.first&.end_point}"
|
|
50
|
+
end
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Spline Edge Routing Demo
|
|
5
|
+
#
|
|
6
|
+
# This example demonstrates the use of smooth Bezier curve routing for edges
|
|
7
|
+
# in ElkRb, comparing different routing styles.
|
|
8
|
+
|
|
9
|
+
require_relative "../lib/elkrb"
|
|
10
|
+
|
|
11
|
+
def create_sample_graph(routing_style, curvature = 0.5)
|
|
12
|
+
Elkrb::Graph::Graph.new(
|
|
13
|
+
id: "root",
|
|
14
|
+
layout_options: Elkrb::Graph::LayoutOptions.new(
|
|
15
|
+
algorithm: "layered",
|
|
16
|
+
direction: "RIGHT",
|
|
17
|
+
edge_routing: routing_style,
|
|
18
|
+
spline_curvature: curvature,
|
|
19
|
+
spacing_node_node: 80.0,
|
|
20
|
+
),
|
|
21
|
+
children: [
|
|
22
|
+
Elkrb::Graph::Node.new(
|
|
23
|
+
id: "n1",
|
|
24
|
+
width: 80,
|
|
25
|
+
height: 40,
|
|
26
|
+
labels: [
|
|
27
|
+
Elkrb::Graph::Label.new(text: "Start", width: 40, height: 15),
|
|
28
|
+
],
|
|
29
|
+
),
|
|
30
|
+
Elkrb::Graph::Node.new(
|
|
31
|
+
id: "n2",
|
|
32
|
+
width: 80,
|
|
33
|
+
height: 40,
|
|
34
|
+
labels: [
|
|
35
|
+
Elkrb::Graph::Label.new(text: "Process", width: 50, height: 15),
|
|
36
|
+
],
|
|
37
|
+
),
|
|
38
|
+
Elkrb::Graph::Node.new(
|
|
39
|
+
id: "n3",
|
|
40
|
+
width: 80,
|
|
41
|
+
height: 40,
|
|
42
|
+
labels: [
|
|
43
|
+
Elkrb::Graph::Label.new(text: "Decision", width: 55, height: 15),
|
|
44
|
+
],
|
|
45
|
+
),
|
|
46
|
+
Elkrb::Graph::Node.new(
|
|
47
|
+
id: "n4",
|
|
48
|
+
width: 80,
|
|
49
|
+
height: 40,
|
|
50
|
+
labels: [
|
|
51
|
+
Elkrb::Graph::Label.new(text: "End", width: 30, height: 15),
|
|
52
|
+
],
|
|
53
|
+
),
|
|
54
|
+
],
|
|
55
|
+
edges: [
|
|
56
|
+
Elkrb::Graph::Edge.new(
|
|
57
|
+
id: "e1",
|
|
58
|
+
sources: ["n1"],
|
|
59
|
+
targets: ["n2"],
|
|
60
|
+
),
|
|
61
|
+
Elkrb::Graph::Edge.new(
|
|
62
|
+
id: "e2",
|
|
63
|
+
sources: ["n2"],
|
|
64
|
+
targets: ["n3"],
|
|
65
|
+
),
|
|
66
|
+
Elkrb::Graph::Edge.new(
|
|
67
|
+
id: "e3",
|
|
68
|
+
sources: ["n3"],
|
|
69
|
+
targets: ["n4"],
|
|
70
|
+
),
|
|
71
|
+
Elkrb::Graph::Edge.new(
|
|
72
|
+
id: "e4",
|
|
73
|
+
sources: ["n3"],
|
|
74
|
+
targets: ["n2"],
|
|
75
|
+
labels: [
|
|
76
|
+
Elkrb::Graph::Label.new(text: "retry", width: 30, height: 15),
|
|
77
|
+
],
|
|
78
|
+
),
|
|
79
|
+
],
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def print_edge_info(edge, style_name)
|
|
84
|
+
section = edge.sections&.first
|
|
85
|
+
return unless section
|
|
86
|
+
|
|
87
|
+
puts "\n Edge #{edge.id} (#{style_name}):"
|
|
88
|
+
puts " Start: (#{section.start_point.x.round(1)}, " \
|
|
89
|
+
"#{section.start_point.y.round(1)})"
|
|
90
|
+
|
|
91
|
+
if section.bend_points&.any?
|
|
92
|
+
section.bend_points.each_with_index do |point, i|
|
|
93
|
+
puts " Control #{i + 1}: (#{point.x.round(1)}, #{point.y.round(1)})"
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
puts " End: (#{section.end_point.x.round(1)}, " \
|
|
98
|
+
"#{section.end_point.y.round(1)})"
|
|
99
|
+
puts " Length: #{section.length.round(2)}"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
puts "=" * 70
|
|
103
|
+
puts "ElkRb Spline Edge Routing Demo"
|
|
104
|
+
puts "=" * 70
|
|
105
|
+
|
|
106
|
+
# Demo 1: Orthogonal Routing (Default)
|
|
107
|
+
puts "\n1. ORTHOGONAL ROUTING (90-degree bends)"
|
|
108
|
+
puts "-" * 70
|
|
109
|
+
|
|
110
|
+
graph1 = create_sample_graph("ORTHOGONAL")
|
|
111
|
+
result1 = Elkrb::Layout::LayoutEngine.layout(graph1)
|
|
112
|
+
|
|
113
|
+
puts "\nGraph dimensions: #{result1.width.round(1)} x #{result1.height.round(1)}"
|
|
114
|
+
result1.edges.each { |edge| print_edge_info(edge, "ORTHOGONAL") }
|
|
115
|
+
|
|
116
|
+
# Demo 2: Polyline Routing
|
|
117
|
+
puts "\n\n2. POLYLINE ROUTING (Straight lines)"
|
|
118
|
+
puts "-" * 70
|
|
119
|
+
|
|
120
|
+
graph2 = create_sample_graph("POLYLINE")
|
|
121
|
+
result2 = Elkrb::Layout::LayoutEngine.layout(graph2)
|
|
122
|
+
|
|
123
|
+
puts "\nGraph dimensions: #{result2.width.round(1)} x #{result2.height.round(1)}"
|
|
124
|
+
result2.edges.each { |edge| print_edge_info(edge, "POLYLINE") }
|
|
125
|
+
|
|
126
|
+
# Demo 3: Spline Routing (Smooth curves - Default curvature)
|
|
127
|
+
puts "\n\n3. SPLINE ROUTING (Smooth curves - curvature: 0.5)"
|
|
128
|
+
puts "-" * 70
|
|
129
|
+
|
|
130
|
+
graph3 = create_sample_graph("SPLINES", 0.5)
|
|
131
|
+
result3 = Elkrb::Layout::LayoutEngine.layout(graph3)
|
|
132
|
+
|
|
133
|
+
puts "\nGraph dimensions: #{result3.width.round(1)} x #{result3.height.round(1)}"
|
|
134
|
+
result3.edges.each { |edge| print_edge_info(edge, "SPLINES") }
|
|
135
|
+
|
|
136
|
+
# Demo 4: Spline Routing with Low Curvature
|
|
137
|
+
puts "\n\n4. SPLINE ROUTING (Gentle curves - curvature: 0.2)"
|
|
138
|
+
puts "-" * 70
|
|
139
|
+
|
|
140
|
+
graph4 = create_sample_graph("SPLINES", 0.2)
|
|
141
|
+
result4 = Elkrb::Layout::LayoutEngine.layout(graph4)
|
|
142
|
+
|
|
143
|
+
puts "\nGraph dimensions: #{result4.width.round(1)} x #{result4.height.round(1)}"
|
|
144
|
+
result4.edges.each { |edge| print_edge_info(edge, "SPLINES 0.2") }
|
|
145
|
+
|
|
146
|
+
# Demo 5: Spline Routing with High Curvature
|
|
147
|
+
puts "\n\n5. SPLINE ROUTING (Strong curves - curvature: 0.9)"
|
|
148
|
+
puts "-" * 70
|
|
149
|
+
|
|
150
|
+
graph5 = create_sample_graph("SPLINES", 0.9)
|
|
151
|
+
result5 = Elkrb::Layout::LayoutEngine.layout(graph5)
|
|
152
|
+
|
|
153
|
+
puts "\nGraph dimensions: #{result5.width.round(1)} x #{result5.height.round(1)}"
|
|
154
|
+
result5.edges.each { |edge| print_edge_info(edge, "SPLINES 0.9") }
|
|
155
|
+
|
|
156
|
+
# Demo 6: Port-based Spline Routing
|
|
157
|
+
puts "\n\n6. PORT-BASED SPLINE ROUTING"
|
|
158
|
+
puts "-" * 70
|
|
159
|
+
|
|
160
|
+
port_graph = Elkrb::Graph::Graph.new(
|
|
161
|
+
id: "port_demo",
|
|
162
|
+
layout_options: Elkrb::Graph::LayoutOptions.new(
|
|
163
|
+
algorithm: "layered",
|
|
164
|
+
direction: "RIGHT",
|
|
165
|
+
edge_routing: "SPLINES",
|
|
166
|
+
spline_curvature: 0.6,
|
|
167
|
+
),
|
|
168
|
+
children: [
|
|
169
|
+
Elkrb::Graph::Node.new(
|
|
170
|
+
id: "source",
|
|
171
|
+
width: 100,
|
|
172
|
+
height: 60,
|
|
173
|
+
ports: [
|
|
174
|
+
Elkrb::Graph::Port.new(id: "out1", x: 100, y: 15),
|
|
175
|
+
Elkrb::Graph::Port.new(id: "out2", x: 100, y: 45),
|
|
176
|
+
],
|
|
177
|
+
labels: [
|
|
178
|
+
Elkrb::Graph::Label.new(text: "Source", width: 45, height: 15),
|
|
179
|
+
],
|
|
180
|
+
),
|
|
181
|
+
Elkrb::Graph::Node.new(
|
|
182
|
+
id: "target",
|
|
183
|
+
width: 100,
|
|
184
|
+
height: 60,
|
|
185
|
+
ports: [
|
|
186
|
+
Elkrb::Graph::Port.new(id: "in1", x: 0, y: 15),
|
|
187
|
+
Elkrb::Graph::Port.new(id: "in2", x: 0, y: 45),
|
|
188
|
+
],
|
|
189
|
+
labels: [
|
|
190
|
+
Elkrb::Graph::Label.new(text: "Target", width: 45, height: 15),
|
|
191
|
+
],
|
|
192
|
+
),
|
|
193
|
+
],
|
|
194
|
+
edges: [
|
|
195
|
+
Elkrb::Graph::Edge.new(
|
|
196
|
+
id: "pe1",
|
|
197
|
+
sources: ["out1"],
|
|
198
|
+
targets: ["in1"],
|
|
199
|
+
),
|
|
200
|
+
Elkrb::Graph::Edge.new(
|
|
201
|
+
id: "pe2",
|
|
202
|
+
sources: ["out2"],
|
|
203
|
+
targets: ["in2"],
|
|
204
|
+
),
|
|
205
|
+
],
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
port_result = Elkrb::Layout::LayoutEngine.layout(port_graph)
|
|
209
|
+
puts "\nGraph dimensions: #{port_result.width.round(1)} x " \
|
|
210
|
+
"#{port_result.height.round(1)}"
|
|
211
|
+
port_result.edges.each { |edge| print_edge_info(edge, "PORT SPLINES") }
|
|
212
|
+
|
|
213
|
+
# Comparison Summary
|
|
214
|
+
puts "\n\n#{'=' * 70}"
|
|
215
|
+
puts "COMPARISON SUMMARY"
|
|
216
|
+
puts "=" * 70
|
|
217
|
+
|
|
218
|
+
puts "\nRouting Style Characteristics:"
|
|
219
|
+
puts " • ORTHOGONAL: Right-angle bends, clear paths, traditional look"
|
|
220
|
+
puts " • POLYLINE: Direct straight lines, minimal space, technical look"
|
|
221
|
+
puts " • SPLINES: Smooth curves, aesthetic appeal, natural flow"
|
|
222
|
+
|
|
223
|
+
puts "\nSpline Curvature Guide:"
|
|
224
|
+
puts " • 0.0-0.3: Gentle curves, closer to straight lines"
|
|
225
|
+
puts " • 0.4-0.6: Moderate curves, balanced appearance (recommended)"
|
|
226
|
+
puts " • 0.7-1.0: Strong curves, very pronounced arcs"
|
|
227
|
+
|
|
228
|
+
puts "\nWhen to Use Each Style:"
|
|
229
|
+
puts " • ORTHOGONAL: Technical diagrams, flowcharts, circuit diagrams"
|
|
230
|
+
puts " • POLYLINE: Simple connections, space-constrained layouts"
|
|
231
|
+
puts " • SPLINES: Presentation diagrams, organic layouts, visual appeal"
|
|
232
|
+
|
|
233
|
+
puts "\n#{'=' * 70}"
|
|
234
|
+
puts "Demo complete! See SPLINE_ROUTING_GUIDE.md for more information."
|
|
235
|
+
puts "=" * 70
|