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.
Files changed (89) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +11 -0
  4. data/Gemfile +13 -0
  5. data/README.adoc +1028 -0
  6. data/Rakefile +64 -0
  7. data/benchmarks/README.md +172 -0
  8. data/benchmarks/elkjs_benchmark.js +140 -0
  9. data/benchmarks/elkrb_benchmark.rb +145 -0
  10. data/benchmarks/fixtures/graphs.json +10777 -0
  11. data/benchmarks/generate_report.rb +241 -0
  12. data/benchmarks/generate_test_graphs.rb +154 -0
  13. data/benchmarks/results/elkrb_results.json +280 -0
  14. data/benchmarks/results/elkrb_summary.json +285 -0
  15. data/elkrb.gemspec +39 -0
  16. data/examples/dot_export_demo.rb +133 -0
  17. data/examples/hierarchical_graph.rb +19 -0
  18. data/examples/layout_constraints_demo.rb +272 -0
  19. data/examples/port_constraints_demo.rb +291 -0
  20. data/examples/self_loop_demo.rb +391 -0
  21. data/examples/simple_graph.rb +50 -0
  22. data/examples/spline_routing_demo.rb +235 -0
  23. data/exe/elkrb +8 -0
  24. data/lib/elkrb/cli.rb +224 -0
  25. data/lib/elkrb/commands/batch_command.rb +66 -0
  26. data/lib/elkrb/commands/convert_command.rb +130 -0
  27. data/lib/elkrb/commands/diagram_command.rb +208 -0
  28. data/lib/elkrb/commands/render_command.rb +52 -0
  29. data/lib/elkrb/commands/validate_command.rb +241 -0
  30. data/lib/elkrb/errors.rb +30 -0
  31. data/lib/elkrb/geometry/bezier.rb +163 -0
  32. data/lib/elkrb/geometry/dimension.rb +32 -0
  33. data/lib/elkrb/geometry/point.rb +68 -0
  34. data/lib/elkrb/geometry/rectangle.rb +86 -0
  35. data/lib/elkrb/geometry/vector.rb +67 -0
  36. data/lib/elkrb/graph/edge.rb +95 -0
  37. data/lib/elkrb/graph/graph.rb +90 -0
  38. data/lib/elkrb/graph/label.rb +45 -0
  39. data/lib/elkrb/graph/layout_options.rb +247 -0
  40. data/lib/elkrb/graph/node.rb +79 -0
  41. data/lib/elkrb/graph/node_constraints.rb +107 -0
  42. data/lib/elkrb/graph/port.rb +104 -0
  43. data/lib/elkrb/graphviz_wrapper.rb +133 -0
  44. data/lib/elkrb/layout/algorithm_registry.rb +57 -0
  45. data/lib/elkrb/layout/algorithms/base_algorithm.rb +208 -0
  46. data/lib/elkrb/layout/algorithms/box.rb +47 -0
  47. data/lib/elkrb/layout/algorithms/disco.rb +206 -0
  48. data/lib/elkrb/layout/algorithms/fixed.rb +32 -0
  49. data/lib/elkrb/layout/algorithms/force.rb +165 -0
  50. data/lib/elkrb/layout/algorithms/layered/cycle_breaker.rb +86 -0
  51. data/lib/elkrb/layout/algorithms/layered/layer_assigner.rb +96 -0
  52. data/lib/elkrb/layout/algorithms/layered/node_placer.rb +77 -0
  53. data/lib/elkrb/layout/algorithms/layered.rb +49 -0
  54. data/lib/elkrb/layout/algorithms/libavoid.rb +389 -0
  55. data/lib/elkrb/layout/algorithms/mrtree.rb +144 -0
  56. data/lib/elkrb/layout/algorithms/radial.rb +64 -0
  57. data/lib/elkrb/layout/algorithms/random.rb +43 -0
  58. data/lib/elkrb/layout/algorithms/rectpacking.rb +93 -0
  59. data/lib/elkrb/layout/algorithms/spore_compaction.rb +139 -0
  60. data/lib/elkrb/layout/algorithms/spore_overlap.rb +117 -0
  61. data/lib/elkrb/layout/algorithms/stress.rb +176 -0
  62. data/lib/elkrb/layout/algorithms/topdown_packing.rb +183 -0
  63. data/lib/elkrb/layout/algorithms/vertiflex.rb +174 -0
  64. data/lib/elkrb/layout/constraints/alignment_constraint.rb +150 -0
  65. data/lib/elkrb/layout/constraints/base_constraint.rb +72 -0
  66. data/lib/elkrb/layout/constraints/constraint_processor.rb +134 -0
  67. data/lib/elkrb/layout/constraints/fixed_position_constraint.rb +87 -0
  68. data/lib/elkrb/layout/constraints/layer_constraint.rb +71 -0
  69. data/lib/elkrb/layout/constraints/relative_position_constraint.rb +110 -0
  70. data/lib/elkrb/layout/edge_router.rb +935 -0
  71. data/lib/elkrb/layout/hierarchical_processor.rb +299 -0
  72. data/lib/elkrb/layout/label_placer.rb +338 -0
  73. data/lib/elkrb/layout/layout_engine.rb +170 -0
  74. data/lib/elkrb/layout/port_constraint_processor.rb +173 -0
  75. data/lib/elkrb/options/elk_padding.rb +94 -0
  76. data/lib/elkrb/options/k_vector.rb +100 -0
  77. data/lib/elkrb/options/k_vector_chain.rb +135 -0
  78. data/lib/elkrb/parsers/elkt_parser.rb +248 -0
  79. data/lib/elkrb/serializers/dot_serializer.rb +339 -0
  80. data/lib/elkrb/serializers/elkt_serializer.rb +236 -0
  81. data/lib/elkrb/version.rb +5 -0
  82. data/lib/elkrb.rb +509 -0
  83. data/sig/elkrb/constraints.rbs +114 -0
  84. data/sig/elkrb/geometry.rbs +61 -0
  85. data/sig/elkrb/graph.rbs +112 -0
  86. data/sig/elkrb/layout.rbs +107 -0
  87. data/sig/elkrb/options.rbs +81 -0
  88. data/sig/elkrb.rbs +32 -0
  89. metadata +179 -0
data/README.adoc ADDED
@@ -0,0 +1,1028 @@
1
+ = ElkRb
2
+
3
+ image:https://img.shields.io/gem/v/elkrb.svg[RubyGems Version]
4
+ image:https://img.shields.io/github/license/metanorma/elkrb.svg[License]
5
+ image:https://github.com/metanorma/elkrb/actions/workflows/test.yml/badge.svg["Build", link="https://github.com/metanorma/elkrb/actions/workflows/test.yml"]
6
+
7
+ == Purpose
8
+
9
+ ElkRb is a pure Ruby implementation of the Eclipse Layout Kernel (ELK) for
10
+ automatic layout of node-link diagrams. It implements ELK's layout algorithms
11
+ to compute positions and routes for graph elements.
12
+
13
+ The library is designed for box-based diagrams including:
14
+
15
+ * UML class diagrams
16
+ * EXPRESS-G data models
17
+ * Mermaid diagrams
18
+ * Flowcharts and data flow diagrams
19
+ * Organization charts
20
+ * Network diagrams
21
+
22
+ ElkRb is built on top of the `lutaml-model` serialization framework, providing
23
+ flexible YAML and JSON serialization with ELK JSON format compatibility.
24
+
25
+ ElkRb aims to be fully compatible with:
26
+
27
+ * Java-based ELK (https://www.eclipse.org/elk/)
28
+ * JavaScript-based elkjs (https://github.com/kieler/elkjs) (verifiable
29
+ compatible in specs)
30
+
31
+ == Features
32
+
33
+ Supports all 15 ELK layout algorithms from Java implementation:
34
+
35
+ Layered:: (Sugiyama) hierarchical diagrams with directed flow
36
+ Force:: organic, symmetric layouts using force simulation
37
+ Stress:: quality-focused layout with stress minimization
38
+ Box:: simple grid-based arrangement
39
+ Fixed:: preserve existing positions
40
+ Random:: random node placement
41
+ MRTree:: multi-rooted tree layout
42
+ Radial:: circular/radial node arrangement
43
+ RectPacking:: efficient rectangle packing
44
+ TopdownPacking:: grid-based treemap layout
45
+ Libavoid:: "A*" pathfinding connector routing
46
+ VertiFlex:: vertical flexible column layout
47
+ DISCO:: disconnected component layout
48
+ SPOrE Overlap:: overlap removal optimization
49
+ SPOrE Compaction:: whitespace compaction
50
+
51
+
52
+ Advanced edge routing
53
+
54
+ ** ORTHOGONAL: 90-degree bends
55
+ ** POLYLINE: Straight segments
56
+ ** SPLINES: Smooth Bezier curves
57
+
58
+
59
+ Advanced port constraints
60
+
61
+ * Port side specification (NORTH, SOUTH, EAST, WEST)
62
+ * Automatic side detection
63
+ * Port ordering within sides
64
+
65
+
66
+ Self-loop support
67
+
68
+ * Edges connecting nodes to themselves
69
+ * Multiple self-loops per node
70
+ * Configurable routing styles
71
+
72
+
73
+ Export formats
74
+
75
+ * ELK JSON format
76
+ * YAML format (Ruby-specific)
77
+ * Graphviz DOT format
78
+
79
+
80
+ Advanced features
81
+
82
+ * Edge routing with bend points and port awareness
83
+ * Hierarchical graph support with recursive layout
84
+ * Automatic label placement for nodes, edges, and ports
85
+ * EdgeSection model with orthogonal routing
86
+
87
+
88
+ Port-based node connections
89
+
90
+ * Explicit port definitions
91
+ * Port-to-port edge routing
92
+ * Port side detection for labels
93
+
94
+ ELK JSON format compatibility
95
+
96
+ * Full elkjs v0.11.0 compatibility
97
+ * YAML/JSON serialization via Lutaml::Model
98
+
99
+ Comprehensive layout options
100
+
101
+ * Option parsers (ElkPadding, KVector, KVectorChain)
102
+ * Algorithm-specific options
103
+ * Layout categories and metadata
104
+
105
+ Command-line interface
106
+
107
+ * Layout command with format options
108
+ * Algorithm listing and query
109
+
110
+ == Architecture
111
+
112
+ .ElkRb layout process
113
+ [source]
114
+ ----
115
+ Input Graph (YAML/JSON/Hash)
116
+
117
+
118
+ ┌─────────┐
119
+ │ Graph │ (Lutaml::Model)
120
+ │ Model │
121
+ └────┬────┘
122
+
123
+
124
+ ┌──────────────┐
125
+ │Layout Engine │
126
+ └──────┬───────┘
127
+
128
+
129
+ ┌──────────────────┐
130
+ │Algorithm Registry│
131
+ └──────┬───────────┘
132
+
133
+
134
+ Algorithm
135
+ (Layered, Force, etc.)
136
+
137
+
138
+ ┌─────────┐
139
+ │ Graph │ (with computed positions)
140
+ │ Model │
141
+ └────┬────┘
142
+
143
+
144
+ Output (YAML/JSON/Hash)
145
+ ----
146
+
147
+ .Graph model structure
148
+ [source]
149
+ ----
150
+ Graph
151
+
152
+ ┌────────┴────────┐
153
+ │ │
154
+ Nodes Edges
155
+ │ │
156
+ ┌───────┴───────┐ │
157
+ │ │ │
158
+ Ports Labels EdgeSections
159
+ │ │ │
160
+ ┌───┴───┐ ┌───┴───┐ │
161
+ │ │ │ │ │
162
+ Position Size Text Position BendPoints
163
+ ----
164
+
165
+ == Installation
166
+
167
+ Add this line to your application's Gemfile:
168
+
169
+ [source,ruby]
170
+ ----
171
+ gem "elkrb"
172
+ ----
173
+
174
+ And then execute:
175
+
176
+ [source,sh]
177
+ ----
178
+ $ bundle install
179
+ ----
180
+
181
+ Or install it yourself as:
182
+
183
+ [source,sh]
184
+ ----
185
+ $ gem install elkrb
186
+ ----
187
+
188
+ == Usage
189
+
190
+ === Basic graph layout
191
+
192
+ A layout can be performed by defining a graph structure as a Ruby hash and
193
+ passing it to the `Elkrb::LayoutEngine`.
194
+
195
+ [source,ruby]
196
+ ----
197
+ require "elkrb"
198
+
199
+ # Define a simple graph
200
+ graph = {
201
+ id: "root",
202
+ layoutOptions: {
203
+ "elk.algorithm" => "layered"
204
+ },
205
+ children: [
206
+ { id: "n1", width: 100, height: 60 },
207
+ { id: "n2", width: 100, height: 60 },
208
+ { id: "n3", width: 100, height: 60 }
209
+ ],
210
+ edges: [
211
+ { id: "e1", sources: ["n1"], targets: ["n2"] },
212
+ { id: "e2", sources: ["n1"], targets: ["n3"] }
213
+ ]
214
+ }
215
+
216
+ # Layout the graph
217
+ engine = Elkrb::LayoutEngine.new
218
+ result = engine.layout(graph)
219
+
220
+ # Access computed positions
221
+ puts result[:children][0][:x] # => 0.0
222
+ puts result[:children][0][:y] # => 0.0
223
+ puts result[:children][1][:x] # => 150.0
224
+ ----
225
+
226
+ A more structured approach uses the model classes to create the graph.
227
+
228
+ [source,ruby]
229
+ ----
230
+ require "elkrb"
231
+
232
+ # Create graph using model classes
233
+ graph = Elkrb::Graph::Graph.new(id: "root")
234
+
235
+ # Add nodes
236
+ node1 = Elkrb::Graph::Node.new(
237
+ id: "n1",
238
+ width: 100,
239
+ height: 60
240
+ )
241
+ node2 = Elkrb::Graph::Node.new(
242
+ id: "n2",
243
+ width: 100,
244
+ height: 60
245
+ )
246
+
247
+ graph.children = [node1, node2]
248
+
249
+ # Add edge
250
+ edge = Elkrb::Graph::Edge.new(
251
+ id: "e1",
252
+ sources: ["n1"],
253
+ targets: ["n2"]
254
+ )
255
+ graph.edges = [edge]
256
+
257
+ # Set layout options
258
+ graph.layout_options = Elkrb::Graph::LayoutOptions.new(
259
+ algorithm: "layered",
260
+ direction: "DOWN"
261
+ )
262
+
263
+ # Layout
264
+ engine = Elkrb::LayoutEngine.new
265
+ result = engine.layout(graph)
266
+
267
+ # Serialize result
268
+ puts result.to_yaml
269
+ puts result.to_json
270
+ ----
271
+
272
+ === UML Class diagram layout
273
+
274
+ [source,ruby]
275
+ ----
276
+ require "elkrb"
277
+
278
+ # UML class diagram with ports for connections
279
+ graph = {
280
+ id: "uml_diagram",
281
+ layoutOptions: {
282
+ "elk.algorithm" => "layered",
283
+ "elk.direction" => "DOWN",
284
+ "elk.spacing.nodeNode" => 50
285
+ },
286
+ children: [
287
+ {
288
+ id: "Person",
289
+ width: 120,
290
+ height: 80,
291
+ labels: [
292
+ { text: "Person", width: 100, height: 20 }
293
+ ],
294
+ ports: [
295
+ { id: "p1", x: 60, y: 0 }, # Top center
296
+ { id: "p2", x: 60, y: 80 } # Bottom center
297
+ ]
298
+ },
299
+ {
300
+ id: "Employee",
301
+ width: 120,
302
+ height: 80,
303
+ labels: [
304
+ { text: "Employee", width: 100, height: 20 }
305
+ ],
306
+ ports: [
307
+ { id: "p3", x: 60, y: 0 }
308
+ ]
309
+ },
310
+ {
311
+ id: "Customer",
312
+ width: 120,
313
+ height: 80,
314
+ labels: [
315
+ { text: "Customer", width: 100, height: 20 }
316
+ ],
317
+ ports: [
318
+ { id: "p4", x: 60, y: 0 }
319
+ ]
320
+ }
321
+ ],
322
+ edges: [
323
+ {
324
+ id: "inheritance1",
325
+ sources: ["Employee"],
326
+ targets: ["Person"],
327
+ layoutOptions: {
328
+ "elk.edgeRouting" => "ORTHOGONAL"
329
+ }
330
+ },
331
+ {
332
+ id: "inheritance2",
333
+ sources: ["Customer"],
334
+ targets: ["Person"],
335
+ layoutOptions: {
336
+ "elk.edgeRouting" => "ORTHOGONAL"
337
+ }
338
+ }
339
+ ]
340
+ }
341
+
342
+ engine = Elkrb::LayoutEngine.new
343
+ result = engine.layout(graph)
344
+ ----
345
+
346
+ === EXPRESS-G diagram layout
347
+
348
+ [source,ruby]
349
+ ----
350
+ require "elkrb"
351
+
352
+ # EXPRESS-G entity-relationship diagram
353
+ graph = {
354
+ id: "express_g",
355
+ layoutOptions: {
356
+ "elk.algorithm" => "layered",
357
+ "elk.direction" => "RIGHT",
358
+ "elk.edgeRouting" => "ORTHOGONAL"
359
+ },
360
+ children: [
361
+ {
362
+ id: "Entity1",
363
+ width: 100,
364
+ height: 60,
365
+ labels: [{ text: "Entity", width: 80, height: 15 }]
366
+ },
367
+ {
368
+ id: "Attribute1",
369
+ width: 80,
370
+ height: 40,
371
+ labels: [{ text: "name", width: 60, height: 15 }]
372
+ },
373
+ {
374
+ id: "Attribute2",
375
+ width: 80,
376
+ height: 40,
377
+ labels: [{ text: "value", width: 60, height: 15 }]
378
+ }
379
+ ],
380
+ edges: [
381
+ { id: "e1", sources: ["Entity1"], targets: ["Attribute1"] },
382
+ { id: "e2", sources: ["Entity1"], targets: ["Attribute2"] }
383
+ ]
384
+ }
385
+
386
+ engine = Elkrb::LayoutEngine.new
387
+ result = engine.layout(graph)
388
+ ----
389
+
390
+ === Layout from YAML File
391
+
392
+ [source,ruby]
393
+ ----
394
+ require "elkrb"
395
+
396
+ # Load graph from YAML
397
+ yaml_content = File.read("diagram.yml")
398
+ graph = Elkrb::Graph::Graph.from_yaml(yaml_content)
399
+
400
+ # Layout
401
+ engine = Elkrb::LayoutEngine.new
402
+ result = engine.layout(graph)
403
+
404
+ # Save result
405
+ File.write("diagram_laid_out.yml", result.to_yaml)
406
+ ----
407
+
408
+ == Layout algorithms
409
+
410
+ === Layered layout
411
+
412
+ The layered (Sugiyama) algorithm is ELK's flagship layout, ideal for
413
+ hierarchical diagrams with a natural flow direction.
414
+
415
+ ==== Use cases
416
+
417
+ * UML class diagrams (inheritance hierarchies)
418
+ * Flowcharts and process diagrams
419
+ * Data flow diagrams
420
+ * Organization charts
421
+ * Dependency graphs
422
+
423
+ ==== Key options
424
+
425
+ [source,ruby]
426
+ ----
427
+ layoutOptions: {
428
+ "elk.algorithm" => "layered",
429
+ "elk.direction" => "DOWN", # DOWN, UP, LEFT, RIGHT
430
+ "elk.spacing.nodeNode" => 50,
431
+ "elk.layered.crossingMinimization.strategy" => "LAYER_SWEEP",
432
+ "elk.layered.nodePlacement.strategy" => "NETWORK_SIMPLEX",
433
+ "elk.edgeRouting" => "ORTHOGONAL" # ORTHOGONAL, POLYLINE, SPLINES
434
+ }
435
+ ----
436
+
437
+ === Force-Directed Layout
438
+
439
+ Creates organic, symmetric layouts using force simulation.
440
+
441
+ Use cases:
442
+
443
+ * Network diagrams
444
+ * Social graphs
445
+ * Mind maps
446
+ * General undirected graphs
447
+
448
+ Options:
449
+
450
+ [source,ruby]
451
+ ----
452
+ layoutOptions: {
453
+ "elk.algorithm" => "force",
454
+ "elk.force.repulsion" => 5.0,
455
+ "elk.force.temperature" => 0.001
456
+ }
457
+ ----
458
+
459
+ === Stress Minimization
460
+
461
+ Quality-focused layout optimizing edge lengths and crossings.
462
+
463
+ Use cases:
464
+
465
+ * High-quality graph visualization
466
+ * Research diagrams
467
+ * Publication-ready layouts
468
+
469
+ Options:
470
+
471
+ [source,ruby]
472
+ ----
473
+ layoutOptions: {
474
+ "elk.algorithm" => "stress"
475
+ }
476
+ ----
477
+
478
+ === Box Layout
479
+
480
+ Simple rectangular packing of nodes.
481
+
482
+ Use cases:
483
+
484
+ * Simple container layouts
485
+ * Grid-like arrangements
486
+ * Dashboards
487
+
488
+ Options:
489
+
490
+ [source,ruby]
491
+ ----
492
+ layoutOptions: {
493
+ "elk.algorithm" => "box"
494
+ }
495
+ ----
496
+
497
+ === MRTree Layout
498
+
499
+ Multi-rooted tree layout for forest structures.
500
+
501
+ Use cases:
502
+
503
+ * Multiple inheritance hierarchies
504
+ * Forest data structures
505
+ * Parallel tree structures
506
+ * Multi-root taxonomies
507
+
508
+ Options:
509
+
510
+ [source,ruby]
511
+ ----
512
+ layoutOptions: {
513
+ "elk.algorithm" => "mrtree",
514
+ "elk.direction" => "DOWN",
515
+ "elk.spacing.nodeNode" => 30
516
+ }
517
+ ----
518
+
519
+ === Radial Layout
520
+
521
+ Arranges nodes in a circular pattern around the center.
522
+
523
+ Use cases:
524
+
525
+ * Network diagrams
526
+ * Circular visualizations
527
+ * Hub-and-spoke diagrams
528
+ * Cyclic structures
529
+
530
+ Options:
531
+
532
+ [source,ruby]
533
+ ----
534
+ layoutOptions: {
535
+ "elk.algorithm" => "radial",
536
+ "elk.spacing.nodeNode" => 50
537
+ }
538
+ ----
539
+
540
+ === RectPacking Layout
541
+
542
+ Efficient rectangle packing using shelf algorithms.
543
+
544
+ Use cases:
545
+
546
+ * Dashboards
547
+ * Tile layouts
548
+ * Space-efficient arrangements
549
+ * Component grids
550
+
551
+ Options:
552
+
553
+ [source,ruby]
554
+ ----
555
+ layoutOptions: {
556
+ "elk.algorithm" => "rectpacking",
557
+ "elk.spacing.nodeNode" => 15
558
+ }
559
+ ----
560
+
561
+ === DISCO Layout
562
+
563
+ Disconnected component layout - identifies and arranges separate graph components.
564
+
565
+ Use cases:
566
+
567
+ * Graphs with multiple disconnected parts
568
+ * Component-based visualizations
569
+ * Separate module layouts
570
+ * Multi-cluster diagrams
571
+
572
+ Options:
573
+
574
+ [source,ruby]
575
+ ----
576
+ layoutOptions: {
577
+ "elk.algorithm" => "disco",
578
+ "disco.componentCompaction.strategy" => "NONE", # NONE, ROW, COLUMN, GRID
579
+ "disco.componentCompaction.componentLayoutAlgorithm" => "layered",
580
+ "disco.spacing.componentComponent" => 30
581
+ }
582
+ ----
583
+
584
+ === SPOrE Overlap Removal
585
+
586
+ Removes overlaps between nodes while preserving the overall structure.
587
+
588
+ Use cases:
589
+
590
+ * Post-processing for force-directed layouts
591
+ * Cleaning up manually positioned diagrams
592
+ * Overlap resolution
593
+ * Layout refinement
594
+
595
+ Options:
596
+
597
+ [source,ruby]
598
+ ----
599
+ layoutOptions: {
600
+ "elk.algorithm" => "sporeOverlap",
601
+ "elk.spacing.nodeNode" => 10,
602
+ "sporeOverlap.maxIterations" => 100
603
+ }
604
+ ----
605
+
606
+ === SPOrE Compaction
607
+
608
+ Compacts layouts by removing whitespace while maintaining structure.
609
+
610
+ Use cases:
611
+
612
+ * Minimizing diagram size
613
+ * Reducing whitespace
614
+ * Creating compact layouts
615
+ * Space optimization
616
+
617
+ Options:
618
+
619
+ [source,ruby]
620
+ ----
621
+ layoutOptions: {
622
+ "elk.algorithm" => "sporeCompaction",
623
+ "sporeCompaction.compactionStrategy" => "BOTH", # HORIZONTAL, VERTICAL, BOTH
624
+ "elk.spacing.nodeNode" => 10,
625
+ "sporeCompaction.normalize" => true
626
+ }
627
+ ----
628
+
629
+ == Advanced features
630
+
631
+ === Edge routing
632
+
633
+ ElkRb supports advanced edge routing with bend points and port awareness.
634
+
635
+ [source,ruby]
636
+ ----
637
+ # Enable edge routing with orthogonal bend points
638
+ graph = {
639
+ layoutOptions: {
640
+ "elk.algorithm" => "layered",
641
+ "elk.edgeRouting" => "ORTHOGONAL"
642
+ },
643
+ # ... nodes and edges
644
+ }
645
+
646
+ result = Elkrb.layout(graph)
647
+
648
+ # Access edge sections with bend points
649
+ edge_section = result[:edges][0][:sections][0]
650
+ puts edge_section[:startPoint] # { x: 50.0, y: 60.0 }
651
+ puts edge_section[:endPoint] # { x: 150.0, y: 60.0 }
652
+ puts edge_section[:bendPoints] # [{ x: 100.0, y: 30.0 }, ...]
653
+ ----
654
+
655
+ === Hierarchical graphs
656
+
657
+ Nested node structures are automatically handled with recursive layout.
658
+
659
+ [source,ruby]
660
+ ----
661
+ graph = {
662
+ layoutOptions: {
663
+ "elk.algorithm" => "layered",
664
+ "hierarchical" => true
665
+ },
666
+ children: [
667
+ {
668
+ id: "parent",
669
+ width: 300,
670
+ height: 200,
671
+ children: [
672
+ { id: "child1", width: 80, height: 60 },
673
+ { id: "child2", width: 80, height: 60 }
674
+ ],
675
+ edges: [
676
+ { sources: ["child1"], targets: ["child2"] }
677
+ ]
678
+ }
679
+ ]
680
+ }
681
+
682
+ result = Elkrb.layout(graph)
683
+ # Parent bounds automatically calculated to contain children
684
+ # Child nodes recursively laid out within parent
685
+ ----
686
+
687
+ === Label placement
688
+
689
+ Automatic label positioning for nodes, edges, and ports.
690
+
691
+ [source,ruby]
692
+ ----
693
+ graph = {
694
+ children: [
695
+ {
696
+ id: "n1",
697
+ width: 100,
698
+ height: 60,
699
+ labels: [
700
+ { text: "Node Label", width: 80, height: 20 }
701
+ ],
702
+ layoutOptions: {
703
+ "node.label.placement" => "INSIDE CENTER" # or OUTSIDE TOP, etc.
704
+ },
705
+ ports: [
706
+ {
707
+ id: "p1",
708
+ x: 0,
709
+ y: 30,
710
+ labels: [
711
+ { text: "Port", width: 30, height: 15 }
712
+ ]
713
+ }
714
+ ]
715
+ }
716
+ ],
717
+ edges: [
718
+ {
719
+ sources: ["n1"],
720
+ targets: ["n2"],
721
+ labels: [
722
+ { text: "Edge Label", width: 60, height: 15 }
723
+ ]
724
+ }
725
+ ]
726
+ }
727
+
728
+ result = Elkrb.layout(graph)
729
+ # Labels automatically positioned based on placement options
730
+ # Port labels placed according to port side detection
731
+ # Edge labels placed at center of edge path
732
+ ----
733
+
734
+ Label placement options:
735
+
736
+ * `node.label.placement` - INSIDE/OUTSIDE + TOP/BOTTOM/LEFT/RIGHT/CENTER
737
+ * `port.label.placement` - Same format as node labels
738
+ * `label.padding` - Internal spacing within labels
739
+ * `label.margin` - External spacing around labels
740
+ * `label.placement.disabled` - Disable automatic placement
741
+
742
+ == Advanced features
743
+
744
+ === Spline Edge Routing
745
+
746
+ Use smooth Bezier curves for aesthetically pleasing edge routing:
747
+
748
+ [source,ruby]
749
+ ----
750
+ graph.layout_options = Elkrb::Graph::LayoutOptions.new(
751
+ algorithm: "layered",
752
+ edge_routing: "SPLINES",
753
+ spline_curvature: 0.5
754
+ )
755
+ ----
756
+
757
+ // See link:SPLINE_ROUTING_GUIDE.md[Spline Routing Guide] for details.
758
+
759
+ === Advanced Port Constraints
760
+
761
+ Control port placement and ordering on nodes:
762
+
763
+ [source,ruby]
764
+ ----
765
+ port = Elkrb::Graph::Port.new(
766
+ id: "p1",
767
+ side: "WEST",
768
+ index: 0
769
+ )
770
+ ----
771
+
772
+ // See link:PORT_CONSTRAINTS_GUIDE.md[Port Constraints Guide] for details.
773
+
774
+ === Self-loop Support
775
+
776
+ Create edges connecting nodes to themselves:
777
+
778
+ [source,ruby]
779
+ ----
780
+ edge = Elkrb::Graph::Edge.new(
781
+ id: "loop",
782
+ sources: ["n1"],
783
+ targets: ["n1"]
784
+ )
785
+ ----
786
+
787
+ // See link:SELF_LOOP_GUIDE.md[Self-loop Guide] for details.
788
+
789
+
790
+ === Graphviz DOT Export
791
+
792
+ NOTE: DOT export requires Graphviz installed on your system. ElkRb does not
793
+ include Graphviz itself, only the DOT serialization functionality.
794
+
795
+ Export layouts to DOT format:
796
+
797
+ [source,ruby]
798
+ ----
799
+ result = Elkrb.layout(graph)
800
+ dot_string = Elkrb.export_dot(result)
801
+ File.write("output.dot", dot_string)
802
+ ----
803
+
804
+ // See link:DOT_SERIALIZER_GUIDE.md[DOT Export Guide] for details.
805
+
806
+
807
+ == Performance
808
+
809
+ ElkRb provides production-ready performance for most use cases.
810
+
811
+ TODO: See link:docs/PERFORMANCE.adoc[Performance Benchmarks] for detailed
812
+ comparisons with elkjs and Java ELK.
813
+
814
+ Quick overview of algorithm performance:
815
+
816
+ * Fast algorithms (< 10ms): Box, Radial, VertiFlex
817
+ * Medium algorithms (10-50ms): Layered, Stress, MRTree
818
+ * Complex algorithms (50-500ms): Force, RectPacking, Libavoid
819
+
820
+ Suitable for:
821
+
822
+ * Real-time layout of small to medium graphs (< 100 nodes)
823
+ * Batch processing of large graphs
824
+ * Interactive diagram editors
825
+
826
+
827
+ == Migration from elkjs
828
+
829
+ If you're migrating from elkjs, see
830
+ link:docs/MIGRATION_FROM_ELKJS.adoc[Migration Guide] for a smooth transition.
831
+
832
+ Key differences:
833
+
834
+ * Ruby API instead of JavaScript Promise-based API
835
+ * Additional algorithms (TopdownPacking, Libavoid, VertiFlex)
836
+ * YAML serialization support
837
+
838
+ == Data Model
839
+
840
+ === Graph
841
+
842
+ The root container for nodes and edges.
843
+
844
+ `id`:: Unique identifier
845
+ `x, y`:: Position coordinates
846
+ `width, height`:: Dimensions
847
+ `children`:: Array of Node objects
848
+ `edges`:: Array of Edge objects
849
+ `layout_options`:: LayoutOptions object
850
+ `properties`:: Custom properties hash
851
+
852
+ === Node
853
+
854
+ Represents a graph node (box, shape, entity).
855
+
856
+ `id`:: Unique identifier
857
+ `x, y`:: Position coordinates
858
+ `width, height`:: Required dimensions
859
+ `labels`:: Array of Label objects
860
+ `ports`:: Array of Port objects
861
+ `children`:: Nested nodes (for hierarchical graphs)
862
+ `edges`:: Local edges
863
+ `layout_options`:: Node-specific layout options
864
+ `properties`:: Custom properties
865
+
866
+ === Edge
867
+
868
+ Represents connections between nodes.
869
+
870
+ `id`:: Unique identifier
871
+ `sources`:: Array of source node/port IDs
872
+ `targets`:: Array of target node/port IDs
873
+ `labels`:: Array of Label objects
874
+ `sections`:: Computed routing (EdgeSection objects)
875
+ `layout_options`:: Edge-specific layout options
876
+ `properties`:: Custom properties
877
+
878
+ === Port
879
+
880
+ Explicit attachment points on node borders.
881
+
882
+ `id`:: Unique identifier
883
+ `x, y`:: Position relative to node
884
+ `width, height`:: Port dimensions
885
+ `labels`:: Array of Label objects
886
+ `layout_options`:: Port-specific layout options
887
+
888
+ === Label
889
+
890
+ Text labels for nodes, edges, or ports.
891
+
892
+ `id`:: Unique identifier
893
+ `text`:: Label text
894
+ `x, y`:: Position coordinates
895
+ `width, height`:: Label dimensions
896
+
897
+ === LayoutOptions
898
+
899
+ Configuration for layout algorithms.
900
+
901
+ `algorithm`:: Algorithm name (layered, force, stress, etc.)
902
+ `direction`:: Layout direction (DOWN, UP, LEFT, RIGHT)
903
+ `spacing_node_node`:: Node-to-node spacing
904
+ `spacing_edge_node`:: Edge-to-node spacing
905
+ `edge_routing`:: Edge routing style (ORTHOGONAL, POLYLINE, SPLINES)
906
+ `properties`:: Additional algorithm-specific options
907
+
908
+ == Command-Line Interface
909
+
910
+ The `elkrb` command provides utilities for layout operations.
911
+
912
+ === Layout Command
913
+
914
+ [source,sh]
915
+ ----
916
+ # Layout a graph from file
917
+ $ elkrb layout input.yml --output result.yml
918
+
919
+ # Specify algorithm
920
+ $ elkrb layout input.yml --algorithm layered --output result.yml
921
+
922
+ # Output JSON
923
+ $ elkrb layout input.yml --format json --output result.json
924
+ ----
925
+
926
+ === Available Algorithms
927
+
928
+ [source,sh]
929
+ ----
930
+ $ elkrb algorithms
931
+
932
+ Available Layout Algorithms:
933
+
934
+ box
935
+ Name: Box
936
+ Description: Arranges nodes in a grid pattern
937
+
938
+ fixed
939
+ Name: Fixed
940
+ Description: Keeps nodes at their current positions
941
+
942
+ force
943
+ Name: Force-Directed
944
+ Description: Physics-based layout using attractive and repulsive forces
945
+ Category: force
946
+
947
+ layered
948
+ Name: Layered (Sugiyama)
949
+ Description: Hierarchical layout using the Sugiyama framework
950
+ Category: hierarchical
951
+ Supports Hierarchy: Yes
952
+
953
+ random
954
+ Name: Random
955
+ Description: Places nodes at random positions
956
+
957
+ stress
958
+ Name: Stress Minimization
959
+ Description: High-quality layout using stress majorization
960
+ Category: force
961
+ ----
962
+
963
+
964
+ == Benchmarking
965
+
966
+ ElkRb provides production-ready performance.
967
+
968
+ ElkRb can be run in a benchmark mode to compare its performance against
969
+ elkjs and Java ELK (TODO).
970
+
971
+
972
+ [source,sh]
973
+ ----
974
+ # Run all ElkRb benchmarks and generate report
975
+ $ rake benchmark:all
976
+
977
+ # Generate test graphs
978
+ $ rake benchmark:generate_graphs
979
+
980
+ # Run ElkRb benchmarks only
981
+ $ rake benchmark:elkrb
982
+
983
+ # Run elkjs benchmarks (requires Node.js and elkjs)
984
+ $ rake benchmark:elkjs
985
+
986
+ # Generate performance report
987
+ $ rake benchmark:report
988
+ ----
989
+
990
+
991
+ == Development
992
+
993
+ === Running tests
994
+
995
+ [source,sh]
996
+ ----
997
+ $ bundle exec rake spec
998
+ ----
999
+
1000
+ === Code Style
1001
+
1002
+ [source,sh]
1003
+ ----
1004
+ $ bundle exec rubocop
1005
+ $ bundle exec rubocop -A # Auto-correct
1006
+ ----
1007
+
1008
+ == References
1009
+
1010
+ Other libraries that implement ELK:
1011
+
1012
+ * https://www.eclipse.org/elk/[Eclipse Layout Kernel]
1013
+ * https://github.com/kieler/elkjs[elkjs - JavaScript port]
1014
+
1015
+ ELK Documentation:
1016
+
1017
+ * https://www.eclipse.org/elk/reference.html[ELK Layout Options Reference]
1018
+
1019
+
1020
+ == Copyright
1021
+
1022
+ Copyright https://www.ribose.com[Ribose Inc.]
1023
+
1024
+
1025
+ == License
1026
+
1027
+ The gem is available as open source under the terms of the
1028
+ https://opensource.org/licenses/BSD-2-Clause[2-Clause BSD License].