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
@@ -0,0 +1,285 @@
1
+ {
2
+ "timestamp": "2025-10-28T13:39:49+09:00",
3
+ "ruby_version": "3.3.2",
4
+ "elkrb_version": "0.4.3",
5
+ "results": {
6
+ "small_simple": {
7
+ "layered": {
8
+ "avg": 7.433500001206994,
9
+ "min": 6.854000006569549,
10
+ "max": 8.520000003045425,
11
+ "memory": 2368
12
+ },
13
+ "force": {
14
+ "avg": 127.22110000177054,
15
+ "min": 49.12799999874551,
16
+ "max": 782.6429999986431,
17
+ "memory": 1856
18
+ },
19
+ "stress": {
20
+ "avg": 18.048900002031587,
21
+ "min": 15.010000002803281,
22
+ "max": 23.14100001240149,
23
+ "memory": 288
24
+ },
25
+ "box": {
26
+ "avg": 6.4161000031162985,
27
+ "min": 5.8969999954570085,
28
+ "max": 6.9420000072568655,
29
+ "memory": 448
30
+ },
31
+ "random": {
32
+ "avg": 38.642400002572685,
33
+ "min": 6.706000000122003,
34
+ "max": 132.673000000068,
35
+ "memory": 128
36
+ },
37
+ "fixed": {
38
+ "avg": 15.259099996183068,
39
+ "min": 6.213999993633479,
40
+ "max": 80.09200000378769,
41
+ "memory": 16
42
+ },
43
+ "mrtree": {
44
+ "error": "Stack overflow (graph has cycles)"
45
+ },
46
+ "radial": {
47
+ "avg": 6.609599998046178,
48
+ "min": 5.982999995467253,
49
+ "max": 7.303000005776994,
50
+ "memory": 176
51
+ },
52
+ "rectpacking": {
53
+ "avg": 75.61830000486225,
54
+ "min": 6.420000005164184,
55
+ "max": 323.69800000742543,
56
+ "memory": 32
57
+ },
58
+ "disco": {
59
+ "avg": 11.099700002523605,
60
+ "min": 7.312999994610436,
61
+ "max": 19.619000013335608,
62
+ "memory": 64
63
+ },
64
+ "sporeOverlap": {
65
+ "error": "Unknown layout algorithm: sporeOverlap"
66
+ },
67
+ "sporeCompaction": {
68
+ "error": "Unknown layout algorithm: sporeCompaction"
69
+ },
70
+ "topdownpacking": {
71
+ "avg": 6.6470000019762665,
72
+ "min": 6.047000002581626,
73
+ "max": 7.645000005140901,
74
+ "memory": 192
75
+ },
76
+ "libavoid": {
77
+ "avg": 71.8007999996189,
78
+ "min": 29.284000003826804,
79
+ "max": 183.2050000084564,
80
+ "memory": 16
81
+ },
82
+ "vertiflex": {
83
+ "avg": 6.495000002905726,
84
+ "min": 6.106000000727363,
85
+ "max": 7.274999996297993,
86
+ "memory": 0
87
+ }
88
+ },
89
+ "medium_hierarchical": {
90
+ "layered": {
91
+ "error": "nil can't be coerced into Float"
92
+ },
93
+ "force": {
94
+ "error": "undefined method `+' for nil"
95
+ },
96
+ "stress": {
97
+ "error": "nil can't be coerced into Float"
98
+ },
99
+ "box": {
100
+ "error": "undefined method `+' for nil"
101
+ },
102
+ "random": {
103
+ "error": "undefined method `+' for nil"
104
+ },
105
+ "fixed": {
106
+ "error": "nil can't be coerced into Float"
107
+ },
108
+ "mrtree": {
109
+ "error": "undefined method `+' for nil"
110
+ },
111
+ "radial": {
112
+ "error": "nil can't be coerced into Integer"
113
+ },
114
+ "rectpacking": {
115
+ "error": "undefined method `-@' for nil"
116
+ },
117
+ "disco": {
118
+ "error": "nil can't be coerced into Float"
119
+ },
120
+ "sporeOverlap": {
121
+ "error": "Unknown layout algorithm: sporeOverlap"
122
+ },
123
+ "sporeCompaction": {
124
+ "error": "Unknown layout algorithm: sporeCompaction"
125
+ },
126
+ "topdownpacking": {
127
+ "error": "nil can't be coerced into Integer"
128
+ },
129
+ "libavoid": {
130
+ "error": "undefined method `+' for nil"
131
+ },
132
+ "vertiflex": {
133
+ "error": "undefined method `+' for nil"
134
+ }
135
+ },
136
+ "large_complex": {
137
+ "layered": {
138
+ "avg": 229.20739999826765,
139
+ "min": 216.13099999376573,
140
+ "max": 276.89000000827946,
141
+ "memory": 2448
142
+ },
143
+ "force": {
144
+ "error": "Timeout"
145
+ },
146
+ "stress": {
147
+ "error": "Timeout"
148
+ },
149
+ "box": {
150
+ "avg": 330.15480000176467,
151
+ "min": 149.9130000011064,
152
+ "max": 925.889999998617,
153
+ "memory": 256
154
+ },
155
+ "random": {
156
+ "avg": 211.05680000182474,
157
+ "min": 148.6330000043381,
158
+ "max": 580.1270000083605,
159
+ "memory": 352
160
+ },
161
+ "fixed": {
162
+ "avg": 215.3812000004109,
163
+ "min": 146.1669999989681,
164
+ "max": 722.7609999972628,
165
+ "memory": 1152
166
+ },
167
+ "mrtree": {
168
+ "error": "Stack overflow (graph has cycles)"
169
+ },
170
+ "radial": {
171
+ "avg": 245.07840000005672,
172
+ "min": 145.85999998962507,
173
+ "max": 760.4060000012396,
174
+ "memory": 288
175
+ },
176
+ "rectpacking": {
177
+ "avg": 426.3377000010223,
178
+ "min": 147.25500000349712,
179
+ "max": 1149.898999996367,
180
+ "memory": 160
181
+ },
182
+ "disco": {
183
+ "avg": 347.8637999985949,
184
+ "min": 271.66199999919627,
185
+ "max": 955.4760000028182,
186
+ "memory": 144
187
+ },
188
+ "sporeOverlap": {
189
+ "error": "Unknown layout algorithm: sporeOverlap"
190
+ },
191
+ "sporeCompaction": {
192
+ "error": "Unknown layout algorithm: sporeCompaction"
193
+ },
194
+ "topdownpacking": {
195
+ "avg": 166.12010000244481,
196
+ "min": 149.74500000244007,
197
+ "max": 237.6079999958165,
198
+ "memory": 48
199
+ },
200
+ "libavoid": {
201
+ "error": "Timeout"
202
+ },
203
+ "vertiflex": {
204
+ "avg": 288.4548999980325,
205
+ "min": 162.87099999317434,
206
+ "max": 799.9410000047646,
207
+ "memory": 176
208
+ }
209
+ },
210
+ "dense_network": {
211
+ "layered": {
212
+ "avg": 426.88989999878686,
213
+ "min": 188.48299999081064,
214
+ "max": 798.5009999974864,
215
+ "memory": 16
216
+ },
217
+ "force": {
218
+ "error": "Timeout"
219
+ },
220
+ "stress": {
221
+ "error": "Timeout"
222
+ },
223
+ "box": {
224
+ "avg": 368.0181999996421,
225
+ "min": 143.31399998627603,
226
+ "max": 869.9789999955101,
227
+ "memory": 144
228
+ },
229
+ "random": {
230
+ "avg": 259.617500002787,
231
+ "min": 150.44500000658445,
232
+ "max": 697.4970000010217,
233
+ "memory": 16
234
+ },
235
+ "fixed": {
236
+ "avg": 345.68620000063675,
237
+ "min": 148.51800000178628,
238
+ "max": 777.6560000056634,
239
+ "memory": 0
240
+ },
241
+ "mrtree": {
242
+ "error": "Stack overflow (graph has cycles)"
243
+ },
244
+ "radial": {
245
+ "avg": 323.07310000032885,
246
+ "min": 154.51900000334717,
247
+ "max": 1149.6579999948153,
248
+ "memory": 48
249
+ },
250
+ "rectpacking": {
251
+ "avg": 238.30970000271918,
252
+ "min": 154.08700000261888,
253
+ "max": 729.6930000011344,
254
+ "memory": -2864
255
+ },
256
+ "disco": {
257
+ "avg": 294.9923999971361,
258
+ "min": 224.77599998819642,
259
+ "max": 809.6569999906933,
260
+ "memory": 0
261
+ },
262
+ "sporeOverlap": {
263
+ "error": "Unknown layout algorithm: sporeOverlap"
264
+ },
265
+ "sporeCompaction": {
266
+ "error": "Unknown layout algorithm: sporeCompaction"
267
+ },
268
+ "topdownpacking": {
269
+ "avg": 202.70830000081332,
270
+ "min": 149.26500000001397,
271
+ "max": 604.8569999984466,
272
+ "memory": 0
273
+ },
274
+ "libavoid": {
275
+ "error": "Timeout"
276
+ },
277
+ "vertiflex": {
278
+ "avg": 197.8210000001127,
279
+ "min": 148.06800000951625,
280
+ "max": 396.78599999751896,
281
+ "memory": -192
282
+ }
283
+ }
284
+ }
285
+ }
data/elkrb.gemspec ADDED
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/elkrb/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "elkrb"
7
+ spec.version = Elkrb::VERSION
8
+ spec.authors = ["Ribose Inc."]
9
+ spec.email = ["open.source@ribose.com"]
10
+
11
+ spec.summary = "ElkRb: Ruby implementation of Eclipse Layout Kernel (ELK)"
12
+ spec.description = <<~HEREDOC
13
+ Pure Ruby implementation of the Eclipse Layout Kernel (ELK) providing automatic
14
+ layout of node-link diagrams. Supports all ELK algorithms.
15
+ HEREDOC
16
+
17
+ spec.homepage = "https://github.com/claricle/elkrb"
18
+ spec.license = "BSD-2-Clause"
19
+ spec.required_ruby_version = ">= 3.0.0"
20
+
21
+ spec.metadata["homepage_uri"] = spec.homepage
22
+ spec.metadata["source_code_uri"] = "https://github.com/claricle/elkrb"
23
+ spec.metadata["changelog_uri"] = "https://github.com/claricle/elkrb"
24
+ spec.metadata["rubygems_mfa_required"] = "true"
25
+
26
+ # Specify which files should be added to the gem when it is released.
27
+ spec.files = Dir.chdir(__dir__) do
28
+ `git ls-files -z`.split("\x0").reject do |f|
29
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
30
+ end
31
+ end
32
+ spec.bindir = "exe"
33
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
34
+ spec.require_paths = ["lib"]
35
+
36
+ spec.add_dependency "lutaml-model", "~> 0.7"
37
+ spec.add_dependency "rbs", "~> 3.0"
38
+ spec.add_dependency "thor", "~> 1.4"
39
+ end
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/elkrb"
5
+
6
+ # Example 1: Simple graph with layout and DOT export
7
+ puts "Example 1: Simple Graph"
8
+ puts "=" * 50
9
+
10
+ # Build graph using model objects
11
+ simple_graph = Elkrb::Graph::Graph.new(id: "root")
12
+
13
+ node1 = Elkrb::Graph::Node.new(id: "n1", width: 100, height: 60)
14
+ node1.labels = [Elkrb::Graph::Label.new(text: "Node 1")]
15
+
16
+ node2 = Elkrb::Graph::Node.new(id: "n2", width: 100, height: 60)
17
+ node2.labels = [Elkrb::Graph::Label.new(text: "Node 2")]
18
+
19
+ node3 = Elkrb::Graph::Node.new(id: "n3", width: 100, height: 60)
20
+ node3.labels = [Elkrb::Graph::Label.new(text: "Node 3")]
21
+
22
+ simple_graph.children = [node1, node2, node3]
23
+ simple_graph.edges = [
24
+ Elkrb::Graph::Edge.new(id: "e1", sources: ["n1"], targets: ["n2"]),
25
+ Elkrb::Graph::Edge.new(id: "e2", sources: ["n2"], targets: ["n3"]),
26
+ Elkrb::Graph::Edge.new(id: "e3", sources: ["n1"], targets: ["n3"]),
27
+ ]
28
+
29
+ # Layout the graph
30
+ result = Elkrb.layout(simple_graph, algorithm: "layered")
31
+
32
+ # Export to DOT format
33
+ dot_output = Elkrb.export_dot(result, rankdir: "TB")
34
+ puts dot_output
35
+ puts "\n"
36
+
37
+ # Example 2: Hierarchical graph
38
+ puts "Example 2: Hierarchical Graph"
39
+ puts "=" * 50
40
+
41
+ # Build hierarchical graph using model objects
42
+ hierarchical_graph = Elkrb::Graph::Graph.new(id: "root")
43
+
44
+ # Cluster 1
45
+ cluster1 = Elkrb::Graph::Node.new(id: "cluster1")
46
+ cluster1.labels = [Elkrb::Graph::Label.new(text: "Group 1")]
47
+ n1 = Elkrb::Graph::Node.new(id: "n1", width: 50, height: 30)
48
+ n1.labels = [Elkrb::Graph::Label.new(text: "A")]
49
+ n2 = Elkrb::Graph::Node.new(id: "n2", width: 50, height: 30)
50
+ n2.labels = [Elkrb::Graph::Label.new(text: "B")]
51
+ cluster1.children = [n1, n2]
52
+ cluster1.edges = [Elkrb::Graph::Edge.new(id: "e1", sources: ["n1"],
53
+ targets: ["n2"])]
54
+
55
+ # Cluster 2
56
+ cluster2 = Elkrb::Graph::Node.new(id: "cluster2")
57
+ cluster2.labels = [Elkrb::Graph::Label.new(text: "Group 2")]
58
+ n3 = Elkrb::Graph::Node.new(id: "n3", width: 50, height: 30)
59
+ n3.labels = [Elkrb::Graph::Label.new(text: "C")]
60
+ n4 = Elkrb::Graph::Node.new(id: "n4", width: 50, height: 30)
61
+ n4.labels = [Elkrb::Graph::Label.new(text: "D")]
62
+ cluster2.children = [n3, n4]
63
+ cluster2.edges = [Elkrb::Graph::Edge.new(id: "e2", sources: ["n3"],
64
+ targets: ["n4"])]
65
+
66
+ hierarchical_graph.children = [cluster1, cluster2]
67
+ hierarchical_graph.edges = [Elkrb::Graph::Edge.new(id: "e3", sources: ["n2"],
68
+ targets: ["n3"])]
69
+
70
+ # Layout and export
71
+ hierarchical_result = Elkrb.layout(hierarchical_graph, algorithm: "layered")
72
+ dot_hierarchical = Elkrb.export_dot(hierarchical_result)
73
+ puts dot_hierarchical
74
+ puts "\n"
75
+
76
+ # Example 3: Force-directed layout with custom DOT attributes
77
+ puts "Example 3: Force-Directed Layout with Custom Attributes"
78
+ puts "=" * 50
79
+
80
+ # Build force-directed graph
81
+ force_graph = Elkrb::Graph::Graph.new(id: "root")
82
+
83
+ center = Elkrb::Graph::Node.new(id: "center", width: 80, height: 80)
84
+ center.labels = [Elkrb::Graph::Label.new(text: "Center")]
85
+
86
+ na = Elkrb::Graph::Node.new(id: "a", width: 60, height: 60)
87
+ na.labels = [Elkrb::Graph::Label.new(text: "A")]
88
+
89
+ nb = Elkrb::Graph::Node.new(id: "b", width: 60, height: 60)
90
+ nb.labels = [Elkrb::Graph::Label.new(text: "B")]
91
+
92
+ nc = Elkrb::Graph::Node.new(id: "c", width: 60, height: 60)
93
+ nc.labels = [Elkrb::Graph::Label.new(text: "C")]
94
+
95
+ nd = Elkrb::Graph::Node.new(id: "d", width: 60, height: 60)
96
+ nd.labels = [Elkrb::Graph::Label.new(text: "D")]
97
+
98
+ force_graph.children = [center, na, nb, nc, nd]
99
+ force_graph.edges = [
100
+ Elkrb::Graph::Edge.new(id: "e1", sources: ["center"], targets: ["a"]),
101
+ Elkrb::Graph::Edge.new(id: "e2", sources: ["center"], targets: ["b"]),
102
+ Elkrb::Graph::Edge.new(id: "e3", sources: ["center"], targets: ["c"]),
103
+ Elkrb::Graph::Edge.new(id: "e4", sources: ["center"], targets: ["d"]),
104
+ Elkrb::Graph::Edge.new(id: "e5", sources: ["a"], targets: ["b"]),
105
+ Elkrb::Graph::Edge.new(id: "e6", sources: ["c"], targets: ["d"]),
106
+ ]
107
+
108
+ force_result = Elkrb.layout(force_graph, algorithm: "force")
109
+ dot_force = Elkrb.export_dot(
110
+ force_result,
111
+ graph_name: "ForceDirected",
112
+ graph_attrs: { bgcolor: "white", splines: "true" },
113
+ node_attrs: { shape: "ellipse", style: "filled", fillcolor: "lightblue" },
114
+ edge_attrs: { color: "gray", arrowsize: 0.8 },
115
+ )
116
+ puts dot_force
117
+ puts "\n"
118
+
119
+ # Example 4: Write to file
120
+ puts "Example 4: Saving to File"
121
+ puts "=" * 50
122
+
123
+ output_file = "output_graph.dot"
124
+ File.write(output_file, dot_output)
125
+ puts "DOT file saved to: #{output_file}"
126
+ puts "You can render it with Graphviz:"
127
+ puts " dot -Tpng #{output_file} -o output_graph.png"
128
+ puts " dot -Tsvg #{output_file} -o output_graph.svg"
129
+ puts "\n"
130
+
131
+ puts "Demo complete!"
132
+ puts "All graphs have been laid out and exported to DOT format."
133
+ puts "Note: This is a pure Ruby implementation with no runtime Graphviz dependency."
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "elkrb"
6
+
7
+ # Hierarchical graph layout example
8
+ # This demonstrates nested graphs with parent-child relationships
9
+
10
+ # Create a hierarchical graph with nested nodes
11
+ graph = Elkrb::Graph.new(
12
+ id: "root",
13
+ children: [
14
+ {
15
+ id: "parent1",
16
+ width: 400,
17
+ height: 300,
18
+ children: [
19
+ { id: "p1_child1", width: 80, height: 40 },