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/lib/elkrb.rb ADDED
@@ -0,0 +1,509 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "elkrb/version"
4
+ require_relative "elkrb/errors"
5
+
6
+ # Geometry
7
+ require_relative "elkrb/geometry/point"
8
+ require_relative "elkrb/geometry/dimension"
9
+ require_relative "elkrb/geometry/rectangle"
10
+ require_relative "elkrb/geometry/vector"
11
+
12
+ # Graph models
13
+ require_relative "elkrb/graph/layout_options"
14
+ require_relative "elkrb/graph/label"
15
+ require_relative "elkrb/graph/port"
16
+ require_relative "elkrb/graph/node_constraints"
17
+ require_relative "elkrb/graph/edge"
18
+ require_relative "elkrb/graph/node"
19
+ require_relative "elkrb/graph/graph"
20
+
21
+ # Serializers
22
+ require_relative "elkrb/serializers/dot_serializer"
23
+
24
+ # Options parsers
25
+ require_relative "elkrb/options/elk_padding"
26
+ require_relative "elkrb/options/k_vector"
27
+ require_relative "elkrb/options/k_vector_chain"
28
+
29
+ # Layout constraints
30
+ require_relative "elkrb/layout/constraints/base_constraint"
31
+ require_relative "elkrb/layout/constraints/fixed_position_constraint"
32
+ require_relative "elkrb/layout/constraints/alignment_constraint"
33
+ require_relative "elkrb/layout/constraints/layer_constraint"
34
+ require_relative "elkrb/layout/constraints/relative_position_constraint"
35
+ require_relative "elkrb/layout/constraints/constraint_processor"
36
+
37
+ # Layout engine
38
+ require_relative "elkrb/layout/algorithm_registry"
39
+ require_relative "elkrb/layout/layout_engine"
40
+ require_relative "elkrb/layout/algorithms/base_algorithm"
41
+ require_relative "elkrb/layout/algorithms/random"
42
+ require_relative "elkrb/layout/algorithms/fixed"
43
+ require_relative "elkrb/layout/algorithms/box"
44
+ require_relative "elkrb/layout/algorithms/layered"
45
+ require_relative "elkrb/layout/algorithms/force"
46
+ require_relative "elkrb/layout/algorithms/stress"
47
+ require_relative "elkrb/layout/algorithms/mrtree"
48
+ require_relative "elkrb/layout/algorithms/radial"
49
+ require_relative "elkrb/layout/algorithms/rectpacking"
50
+ require_relative "elkrb/layout/algorithms/topdown_packing"
51
+ require_relative "elkrb/layout/algorithms/disco"
52
+ require_relative "elkrb/layout/algorithms/spore_overlap"
53
+ require_relative "elkrb/layout/algorithms/spore_compaction"
54
+ require_relative "elkrb/layout/algorithms/libavoid"
55
+ require_relative "elkrb/layout/algorithms/vertiflex"
56
+
57
+ # ElkRb - Pure Ruby implementation of the Eclipse Layout Kernel
58
+ #
59
+ # ElkRb provides automatic graph layout algorithms for node-link diagrams.
60
+ # It implements 12 layout algorithms from the Eclipse Layout Kernel (ELK),
61
+ # supporting hierarchical graphs, port-based connections, and automatic
62
+ # label placement.
63
+ #
64
+ # @example Basic usage
65
+ # require 'elkrb'
66
+ #
67
+ # graph = {
68
+ # id: "root",
69
+ # layoutOptions: { "elk.algorithm" => "layered" },
70
+ # children: [
71
+ # { id: "n1", width: 100, height: 60 },
72
+ # { id: "n2", width: 100, height: 60 }
73
+ # ],
74
+ # edges: [
75
+ # { id: "e1", sources: ["n1"], targets: ["n2"] }
76
+ # ]
77
+ # }
78
+ #
79
+ # result = Elkrb.layout(graph)
80
+ # puts result[:children][0][:x] # Node positions computed
81
+ #
82
+ # @example Using model classes
83
+ # graph = Elkrb::Graph::Graph.new(id: "root")
84
+ # node = Elkrb::Graph::Node.new(id: "n1", width: 100, height: 60)
85
+ # graph.children = [node]
86
+ #
87
+ # result = Elkrb.layout(graph, algorithm: "force")
88
+ #
89
+ # @example Querying available algorithms
90
+ # Elkrb.known_layout_algorithms.each do |alg|
91
+ # puts "#{alg[:id]}: #{alg[:description]}"
92
+ # end
93
+ #
94
+ # @see https://www.eclipse.org/elk/ Eclipse Layout Kernel
95
+ # @see https://github.com/kieler/elkjs elkjs - JavaScript port
96
+ module Elkrb
97
+ # Register basic layout algorithms
98
+ Layout::AlgorithmRegistry.register(
99
+ "random",
100
+ Layout::Algorithms::Random,
101
+ {
102
+ name: "Random",
103
+ description: "Places nodes at random positions",
104
+ },
105
+ )
106
+
107
+ Layout::AlgorithmRegistry.register(
108
+ "fixed",
109
+ Layout::Algorithms::Fixed,
110
+ {
111
+ name: "Fixed",
112
+ description: "Keeps nodes at their current positions",
113
+ },
114
+ )
115
+
116
+ Layout::AlgorithmRegistry.register(
117
+ "box",
118
+ Layout::Algorithms::Box,
119
+ {
120
+ name: "Box",
121
+ description: "Arranges nodes in a grid pattern",
122
+ },
123
+ )
124
+
125
+ Layout::AlgorithmRegistry.register(
126
+ "layered",
127
+ Layout::Algorithms::LayeredAlgorithm,
128
+ {
129
+ name: "Layered (Sugiyama)",
130
+ description: "Hierarchical layout using the Sugiyama framework",
131
+ category: "hierarchical",
132
+ supports_hierarchy: true,
133
+ },
134
+ )
135
+
136
+ Layout::AlgorithmRegistry.register(
137
+ "force",
138
+ Layout::Algorithms::Force,
139
+ {
140
+ name: "Force-Directed",
141
+ description: "Physics-based layout using attractive and repulsive forces",
142
+ category: "force",
143
+ supports_hierarchy: false,
144
+ },
145
+ )
146
+
147
+ Layout::AlgorithmRegistry.register(
148
+ "stress",
149
+ Layout::Algorithms::Stress,
150
+ {
151
+ name: "Stress Minimization",
152
+ description: "High-quality layout using stress majorization",
153
+ category: "force",
154
+ supports_hierarchy: false,
155
+ },
156
+ )
157
+
158
+ Layout::AlgorithmRegistry.register(
159
+ "mrtree",
160
+ Layout::Algorithms::MRTree,
161
+ {
162
+ name: "Multi-Rooted Tree",
163
+ description: "Tree layout supporting multiple root nodes",
164
+ category: "hierarchical",
165
+ supports_hierarchy: true,
166
+ },
167
+ )
168
+
169
+ Layout::AlgorithmRegistry.register(
170
+ "radial",
171
+ Layout::Algorithms::Radial,
172
+ {
173
+ name: "Radial",
174
+ description: "Circular/radial node arrangement",
175
+ category: "general",
176
+ supports_hierarchy: false,
177
+ },
178
+ )
179
+
180
+ Layout::AlgorithmRegistry.register(
181
+ "rectpacking",
182
+ Layout::Algorithms::RectPacking,
183
+ {
184
+ name: "Rectangle Packing",
185
+ description: "Efficient rectangle bin packing layout",
186
+ category: "packing",
187
+ supports_hierarchy: false,
188
+ },
189
+ )
190
+
191
+ Layout::AlgorithmRegistry.register(
192
+ "topdownpacking",
193
+ Layout::Algorithms::TopdownPacking,
194
+ {
195
+ name: "TopdownPacking",
196
+ description: "Top-down rectangle packing with hierarchical space partitioning",
197
+ category: "packing",
198
+ supports_hierarchy: false,
199
+ },
200
+ )
201
+
202
+ Layout::AlgorithmRegistry.register(
203
+ "disco",
204
+ Layout::Algorithms::Disco,
205
+ {
206
+ name: "DISCO",
207
+ description: "Layout for disconnected graph components",
208
+ category: "general",
209
+ supports_hierarchy: false,
210
+ },
211
+ )
212
+
213
+ Layout::AlgorithmRegistry.register(
214
+ "spore_overlap",
215
+ Layout::Algorithms::SporeOverlap,
216
+ {
217
+ name: "SPOrE Overlap Removal",
218
+ description: "Removes node overlaps while preserving structure",
219
+ category: "optimization",
220
+ supports_hierarchy: false,
221
+ },
222
+ )
223
+
224
+ Layout::AlgorithmRegistry.register(
225
+ "spore_compaction",
226
+ Layout::Algorithms::SporeCompaction,
227
+ {
228
+ name: "SPOrE Compaction",
229
+ description: "Compacts layout by removing whitespace",
230
+ category: "optimization",
231
+ supports_hierarchy: false,
232
+ },
233
+ )
234
+
235
+ Layout::AlgorithmRegistry.register(
236
+ "libavoid",
237
+ Layout::Algorithms::Libavoid,
238
+ {
239
+ name: "Libavoid",
240
+ description: "Orthogonal connector routing with obstacle avoidance",
241
+ category: "routing",
242
+ supports_hierarchy: false,
243
+ },
244
+ )
245
+
246
+ Layout::AlgorithmRegistry.register(
247
+ "vertiflex",
248
+ Layout::Algorithms::VertiFlex,
249
+ {
250
+ name: "VertiFlex",
251
+ description: "Vertical flexible layout with column-based arrangement",
252
+ category: "layered",
253
+ supports_hierarchy: false,
254
+ experimental: true,
255
+ },
256
+ )
257
+
258
+ # Performs layout on a graph using the specified algorithm.
259
+ #
260
+ # This is the main entry point for ElkRb. It accepts either a Hash or
261
+ # a Graph model object and applies the chosen layout algorithm to compute
262
+ # node positions and edge routes.
263
+ #
264
+ # @param graph [Hash, Graph::Graph] The graph to layout
265
+ # @param options [Hash] Layout options including:
266
+ # - :algorithm (String) - Algorithm name (default: "layered")
267
+ # - Algorithm-specific options (e.g., "elk.spacing.nodeNode")
268
+ # @return [Graph::Graph] The input graph with computed positions
269
+ #
270
+ # @example With hash input
271
+ # result = Elkrb.layout({
272
+ # id: "root",
273
+ # children: [{ id: "n1", width: 100, height: 60 }]
274
+ # })
275
+ #
276
+ # @example With specific algorithm
277
+ # result = Elkrb.layout(graph, algorithm: "force")
278
+ #
279
+ # @example With algorithm options
280
+ # result = Elkrb.layout(graph,
281
+ # algorithm: "layered",
282
+ # "elk.direction" => "DOWN",
283
+ # "elk.spacing.nodeNode" => 50
284
+ # )
285
+ def self.layout(graph, options = {})
286
+ Layout::LayoutEngine.layout(graph, options)
287
+ end
288
+
289
+ # Exports a graph to Graphviz DOT format.
290
+ #
291
+ # This is a convenience method that delegates to the LayoutEngine.
292
+ # It serializes an ELK graph structure to DOT format that can be
293
+ # rendered by Graphviz or other DOT-compatible tools.
294
+ #
295
+ # @param graph [Hash, Graph::Graph] The graph to export
296
+ # @param options [Hash] Serialization options:
297
+ # - :directed (Boolean) - Whether graph is directed (default: true)
298
+ # - :rankdir (String) - Layout direction (TB, LR, BT, RL)
299
+ # - :graph_name (String) - Name for the graph (default: "G")
300
+ # - :graph_attrs (Hash) - Additional graph attributes
301
+ # - :node_attrs (Hash) - Default node attributes
302
+ # - :edge_attrs (Hash) - Default edge attributes
303
+ # @return [String] DOT format string
304
+ #
305
+ # @example Basic export
306
+ # dot = Elkrb.export_dot(graph)
307
+ # File.write("output.dot", dot)
308
+ #
309
+ # @example Layout then export
310
+ # graph = Elkrb.layout(graph, algorithm: "layered")
311
+ # dot = Elkrb.export_dot(graph, rankdir: "LR")
312
+ # File.write("output.dot", dot)
313
+ #
314
+ # @example With custom attributes
315
+ # dot = Elkrb.export_dot(graph,
316
+ # graph_name: "MyGraph",
317
+ # graph_attrs: { bgcolor: "lightgray" },
318
+ # node_attrs: { shape: "box", color: "blue" }
319
+ # )
320
+ def self.export_dot(graph, options = {})
321
+ Layout::LayoutEngine.export_dot(graph, options)
322
+ end
323
+
324
+ # Registers a custom layout algorithm.
325
+ #
326
+ # This allows you to extend ElkRb with your own layout algorithms.
327
+ # The algorithm class must implement the layout interface (inherit from
328
+ # BaseAlgorithm or implement a compatible interface).
329
+ #
330
+ # @param name [String] Unique algorithm identifier
331
+ # @param algorithm_class [Class] Algorithm implementation class
332
+ # @param metadata [Hash] Optional metadata:
333
+ # - :name (String) - Display name
334
+ # - :description (String) - Brief description
335
+ # - :category (String) - Algorithm category
336
+ # - :supports_hierarchy (Boolean) - Hierarchical support
337
+ #
338
+ # @example Register custom algorithm
339
+ # class MyAlgorithm < Elkrb::Layout::Algorithms::BaseAlgorithm
340
+ # def layout_flat(graph, options = {})
341
+ # # Custom layout logic
342
+ # graph
343
+ # end
344
+ # end
345
+ #
346
+ # Elkrb.register_algorithm("my_algo", MyAlgorithm, {
347
+ # name: "My Algorithm",
348
+ # description: "Custom layout algorithm",
349
+ # category: "general"
350
+ # })
351
+ #
352
+ # # Use it
353
+ # Elkrb.layout(graph, algorithm: "my_algo")
354
+ def self.register_algorithm(name, algorithm_class, metadata = {})
355
+ Layout::AlgorithmRegistry.register(name, algorithm_class, metadata)
356
+ end
357
+
358
+ # Returns metadata for all available layout algorithms.
359
+ #
360
+ # This includes the 12 built-in algorithms plus any custom algorithms
361
+ # registered via {register_algorithm}.
362
+ #
363
+ # @return [Array<Hash>] Array of algorithm metadata with keys:
364
+ # - :id (String) - Algorithm identifier
365
+ # - :name (String) - Display name
366
+ # - :description (String) - Brief description
367
+ # - :category (String) - Algorithm category
368
+ # - :supports_hierarchy (Boolean) - Whether it supports nested graphs
369
+ #
370
+ # @example List all algorithms
371
+ # Elkrb.known_layout_algorithms.each do |alg|
372
+ # puts "#{alg[:id]}: #{alg[:description]}"
373
+ # end
374
+ #
375
+ # @example Filter by category
376
+ # force_algorithms = Elkrb.known_layout_algorithms
377
+ # .select { |alg| alg[:category] == "force" }
378
+ def self.known_layout_algorithms
379
+ Layout::AlgorithmRegistry.all.map do |name, data|
380
+ {
381
+ id: name,
382
+ name: data[:metadata][:name] || name.capitalize,
383
+ description: data[:metadata][:description] || "",
384
+ category: data[:metadata][:category] || "general",
385
+ supports_hierarchy: data[:metadata][:supports_hierarchy] || false,
386
+ }
387
+ end
388
+ end
389
+
390
+ # Returns metadata for all supported layout options.
391
+ #
392
+ # This includes common options like algorithm selection, spacing,
393
+ # direction, and padding, as well as algorithm-specific options.
394
+ #
395
+ # @return [Hash{String => Hash}] Hash mapping option names to metadata.
396
+ # Each metadata hash contains:
397
+ # - :type (String) - Option value type
398
+ # - :description (String) - Brief description
399
+ # - :default - Default value
400
+ # - :values (Array, nil) - Allowed values for enum types
401
+ # - :parser (String, nil) - Parser class for complex types
402
+ #
403
+ # @example Query options
404
+ # options = Elkrb.known_layout_options
405
+ # puts options["elk.direction"][:description]
406
+ # # => "Overall direction of layout"
407
+ #
408
+ # @example Get allowed values
409
+ # directions = Elkrb.known_layout_options["elk.direction"][:values]
410
+ # # => ["UP", "DOWN", "LEFT", "RIGHT"]
411
+ def self.known_layout_options
412
+ {
413
+ "algorithm" => {
414
+ type: "string",
415
+ description: "The layout algorithm to use",
416
+ default: "layered",
417
+ values: Layout::AlgorithmRegistry.all.keys,
418
+ },
419
+ "elk.direction" => {
420
+ type: "string",
421
+ description: "Overall direction of layout",
422
+ default: "RIGHT",
423
+ values: %w[UP DOWN LEFT RIGHT],
424
+ },
425
+ "elk.spacing.nodeNode" => {
426
+ type: "float",
427
+ description: "Spacing between nodes",
428
+ default: 20.0,
429
+ },
430
+ "elk.padding" => {
431
+ type: "ElkPadding",
432
+ description: "Padding around the graph",
433
+ default: "[left=12, top=12, right=12, bottom=12]",
434
+ parser: "Elkrb::Options::ElkPadding",
435
+ },
436
+ "position" => {
437
+ type: "KVector",
438
+ description: "Fixed position for nodes (when using fixed algorithm)",
439
+ default: nil,
440
+ parser: "Elkrb::Options::KVector",
441
+ },
442
+ "bendPoints" => {
443
+ type: "KVectorChain",
444
+ description: "Bend points for edges",
445
+ default: nil,
446
+ parser: "Elkrb::Options::KVectorChain",
447
+ },
448
+ }
449
+ end
450
+
451
+ # Returns metadata for layout option categories.
452
+ #
453
+ # Categories help organize the many layout options into logical groups.
454
+ # There are 9 categories covering different aspects of layout.
455
+ #
456
+ # @return [Hash{String => Hash}] Hash mapping category names to metadata.
457
+ # Each metadata hash contains:
458
+ # - :name (String) - Display name
459
+ # - :description (String) - Brief description
460
+ #
461
+ # @example List categories
462
+ # Elkrb.known_layout_categories.each do |id, info|
463
+ # puts "#{id}: #{info[:description]}"
464
+ # end
465
+ #
466
+ # @example Get specific category
467
+ # force_category = Elkrb.known_layout_categories["force"]
468
+ # puts force_category[:name] # => "Force-Directed"
469
+ def self.known_layout_categories
470
+ {
471
+ "general" => {
472
+ name: "General",
473
+ description: "General layout options",
474
+ },
475
+ "spacing" => {
476
+ name: "Spacing",
477
+ description: "Options for controlling spacing between elements",
478
+ },
479
+ "alignment" => {
480
+ name: "Alignment",
481
+ description: "Options for controlling element alignment",
482
+ },
483
+ "direction" => {
484
+ name: "Direction",
485
+ description: "Options for controlling layout direction",
486
+ },
487
+ "ports" => {
488
+ name: "Ports",
489
+ description: "Options for port handling",
490
+ },
491
+ "edges" => {
492
+ name: "Edges",
493
+ description: "Options for edge routing",
494
+ },
495
+ "hierarchical" => {
496
+ name: "Hierarchical",
497
+ description: "Options for hierarchical layouts",
498
+ },
499
+ "force" => {
500
+ name: "Force-Directed",
501
+ description: "Options for force-directed layouts",
502
+ },
503
+ "optimization" => {
504
+ name: "Optimization",
505
+ description: "Options for layout optimization algorithms",
506
+ },
507
+ }
508
+ end
509
+ end
@@ -0,0 +1,114 @@
1
+ # Type signatures for ElkRb layout constraints
2
+
3
+ module Elkrb
4
+ module Graph
5
+ # Relative offset for positioning
6
+ class RelativeOffset < Lutaml::Model::Serializable
7
+ attr_accessor x: Float
8
+ attr_accessor y: Float
9
+
10
+ def initialize: (?x: Float, ?y: Float) -> void
11
+ end
12
+
13
+ # Node positioning constraints
14
+ class NodeConstraints < Lutaml::Model::Serializable
15
+ attr_accessor fixed_position: bool
16
+ attr_accessor layer: Integer?
17
+ attr_accessor align_group: String?
18
+ attr_accessor align_direction: String?
19
+ attr_accessor relative_to: String?
20
+ attr_accessor relative_offset: RelativeOffset?
21
+ attr_accessor position_priority: Integer
22
+
23
+ HORIZONTAL: String
24
+ VERTICAL: String
25
+ ALIGN_DIRECTIONS: Array[String]
26
+
27
+ def initialize: (
28
+ ?fixed_position: bool,
29
+ ?layer: Integer?,
30
+ ?align_group: String?,
31
+ ?align_direction: String?,
32
+ ?relative_to: String?,
33
+ ?relative_offset: RelativeOffset?,
34
+ ?position_priority: Integer
35
+ ) -> void
36
+
37
+ def align_direction=: (String? value) -> String?
38
+ end
39
+ end
40
+
41
+ module Layout
42
+ module Constraints
43
+ # Base constraint class
44
+ class BaseConstraint
45
+ def apply: (Graph::Graph graph) -> Graph::Graph
46
+ def validate: (Graph::Graph graph) -> Array[String]
47
+ def applies_to?: (Graph::Node node) -> bool
48
+
49
+ private
50
+
51
+ def find_node: (Graph::Graph graph, String node_id) -> Graph::Node?
52
+ def all_nodes: (Graph::Graph graph) -> Array[Graph::Node]
53
+ end
54
+
55
+ # Fixed position constraint
56
+ class FixedPositionConstraint < BaseConstraint
57
+ def apply: (Graph::Graph graph) -> Graph::Graph
58
+ def validate: (Graph::Graph graph) -> Array[String]
59
+ def restore_fixed_positions: (Graph::Graph graph) -> Graph::Graph
60
+ end
61
+
62
+ # Alignment constraint
63
+ class AlignmentConstraint < BaseConstraint
64
+ def apply: (Graph::Graph graph) -> Graph::Graph
65
+ def validate: (Graph::Graph graph) -> Array[String]
66
+
67
+ private
68
+
69
+ def group_by_alignment: (Array[Graph::Node] nodes) -> Hash[[String, String], Array[Graph::Node]]
70
+ def align_nodes: (Array[Graph::Node] nodes, String direction) -> void
71
+ def align_horizontally: (Array[Graph::Node] nodes) -> void
72
+ def align_vertically: (Array[Graph::Node] nodes) -> void
73
+ def validate_group_alignment: (Array[Graph::Node] nodes, String group_name, String direction) -> Array[String]
74
+ end
75
+
76
+ # Layer constraint
77
+ class LayerConstraint < BaseConstraint
78
+ def apply: (Graph::Graph graph) -> Graph::Graph
79
+ def validate: (Graph::Graph graph) -> Array[String]
80
+ end
81
+
82
+ # Relative position constraint
83
+ class RelativePositionConstraint < BaseConstraint
84
+ def apply: (Graph::Graph graph) -> Graph::Graph
85
+ def validate: (Graph::Graph graph) -> Array[String]
86
+
87
+ private
88
+
89
+ def apply_relative_position: (Graph::Node node, Graph::Graph graph) -> void
90
+ end
91
+
92
+ # Constraint processor
93
+ class ConstraintProcessor
94
+ PRE_LAYOUT_CONSTRAINTS: Array[singleton(BaseConstraint)]
95
+ POST_LAYOUT_CONSTRAINTS: Array[singleton(BaseConstraint)]
96
+
97
+ @pre_constraints: Array[BaseConstraint]
98
+ @post_constraints: Array[BaseConstraint]
99
+ @all_constraints: Array[BaseConstraint]
100
+
101
+ def initialize: () -> void
102
+ def apply_pre_layout: (Graph::Graph graph) -> Graph::Graph
103
+ def enforce_post_layout: (Graph::Graph graph) -> Graph::Graph
104
+ def apply_all: (Graph::Graph graph) -> Graph::Graph
105
+ def validate_all: (Graph::Graph graph) -> Array[String]
106
+ def has_constraints?: (Graph::Graph graph) -> bool
107
+
108
+ private
109
+
110
+ def has_constraints_recursive?: (Graph::Node node) -> bool
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,61 @@
1
+ module Elkrb
2
+ module Geometry
3
+ class Point
4
+ attr_reader x: Float
5
+ attr_reader y: Float
6
+
7
+ def initialize: (Float x, Float y) -> void
8
+ def +: (Point other) -> Point
9
+ def -: (Point other) -> Point
10
+ def *: (Float scalar) -> Point
11
+ def /: (Float scalar) -> Point
12
+ def distance_to: (Point other) -> Float
13
+ def ==: (untyped other) -> bool
14
+ def to_h: () -> Hash[Symbol, Float]
15
+ end
16
+
17
+ class Dimension
18
+ attr_reader width: Float
19
+ attr_reader height: Float
20
+
21
+ def initialize: (Float width, Float height) -> void
22
+ def area: () -> Float
23
+ def ==: (untyped other) -> bool
24
+ def to_h: () -> Hash[Symbol, Float]
25
+ end
26
+
27
+ class Rectangle
28
+ attr_reader x: Float
29
+ attr_reader y: Float
30
+ attr_reader width: Float
31
+ attr_reader height: Float
32
+
33
+ def initialize: (Float x, Float y, Float width, Float height) -> void
34
+ def left: () -> Float
35
+ def right: () -> Float
36
+ def top: () -> Float
37
+ def bottom: () -> Float
38
+ def center: () -> Point
39
+ def contains?: (Point | Rectangle point_or_rect) -> bool
40
+ def intersects?: (Rectangle other) -> bool
41
+ def ==: (untyped other) -> bool
42
+ def to_h: () -> Hash[Symbol, Float]
43
+ end
44
+
45
+ class Vector
46
+ attr_reader x: Float
47
+ attr_reader y: Float
48
+
49
+ def initialize: (Float x, Float y) -> void
50
+ def +: (Vector other) -> Vector
51
+ def -: (Vector other) -> Vector
52
+ def *: (Float scalar) -> Vector
53
+ def /: (Float scalar) -> Vector
54
+ def magnitude: () -> Float
55
+ def normalize: () -> Vector
56
+ def dot: (Vector other) -> Float
57
+ def ==: (untyped other) -> bool
58
+ def to_h: () -> Hash[Symbol, Float]
59
+ end
60
+ end
61
+ end