elkrb 1.0.0 → 2.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 +4 -4
- data/CHANGELOG.adoc +364 -0
- data/LICENSE +25 -0
- data/README.adoc +695 -557
- data/exe/elkrb +36 -2
- data/lib/elkrb/atomic_destination.rb +53 -0
- data/lib/elkrb/best_effort_write.rb +49 -0
- data/lib/elkrb/cli/layout_flag_options.rb +54 -0
- data/lib/elkrb/cli.rb +155 -82
- data/lib/elkrb/command_resolver.rb +186 -0
- data/lib/elkrb/commands/batch_command.rb +28 -5
- data/lib/elkrb/commands/convert_command.rb +22 -52
- data/lib/elkrb/commands/diagram_command.rb +234 -102
- data/lib/elkrb/commands/options_command.rb +87 -0
- data/lib/elkrb/commands/render_command.rb +6 -1
- data/lib/elkrb/commands/validate_command.rb +146 -91
- data/lib/elkrb/errors.rb +20 -1
- data/lib/elkrb/format_sniffer.rb +362 -0
- data/lib/elkrb/geometry/bezier.rb +14 -4
- data/lib/elkrb/geometry/point.rb +5 -6
- data/lib/elkrb/geometry/rectangle.rb +2 -2
- data/lib/elkrb/graph/deep_stringify_keys.rb +30 -0
- data/lib/elkrb/graph/edge.rb +89 -3
- data/lib/elkrb/graph/graph.rb +43 -15
- data/lib/elkrb/graph/label.rb +16 -2
- data/lib/elkrb/graph/node.rb +13 -2
- data/lib/elkrb/graph/node_constraints.rb +139 -8
- data/lib/elkrb/graph/normalize_option_keys.rb +39 -0
- data/lib/elkrb/graph/port.rb +29 -21
- data/lib/elkrb/graph/read_only_mapping.rb +21 -0
- data/lib/elkrb/graphviz_wrapper.rb +152 -44
- data/lib/elkrb/layout/algorithm_registry.rb +65 -8
- data/lib/elkrb/layout/algorithms/base_algorithm.rb +74 -74
- data/lib/elkrb/layout/algorithms/box.rb +47 -20
- data/lib/elkrb/layout/algorithms/disco.rb +150 -61
- data/lib/elkrb/layout/algorithms/fixed.rb +70 -10
- data/lib/elkrb/layout/algorithms/force.rb +145 -86
- data/lib/elkrb/layout/algorithms/layered/bk/aligner.rb +104 -0
- data/lib/elkrb/layout/algorithms/layered/bk/compactor.rb +87 -0
- data/lib/elkrb/layout/algorithms/layered/bk/view.rb +103 -0
- data/lib/elkrb/layout/algorithms/layered/bk_node_placer.rb +170 -0
- data/lib/elkrb/layout/algorithms/layered/components.rb +170 -0
- data/lib/elkrb/layout/algorithms/layered/crossing_minimizer.rb +306 -0
- data/lib/elkrb/layout/algorithms/layered/cycle_breaker.rb +118 -49
- data/lib/elkrb/layout/algorithms/layered/discovery_order.rb +75 -0
- data/lib/elkrb/layout/algorithms/layered/dummy_slot.rb +31 -0
- data/lib/elkrb/layout/algorithms/layered/layer_assigner.rb +188 -48
- data/lib/elkrb/layout/algorithms/layered/layer_constraints.rb +82 -0
- data/lib/elkrb/layout/algorithms/layered/layer_sweep.rb +195 -0
- data/lib/elkrb/layout/algorithms/layered/node_placer.rb +214 -36
- data/lib/elkrb/layout/algorithms/layered/orthogonal/gap_solver.rb +219 -0
- data/lib/elkrb/layout/algorithms/layered/orthogonal/hyper_edge_segment.rb +152 -0
- data/lib/elkrb/layout/algorithms/layered/orthogonal/junction_points.rb +58 -0
- data/lib/elkrb/layout/algorithms/layered/orthogonal/segment_order.rb +134 -0
- data/lib/elkrb/layout/algorithms/layered/orthogonal_router.rb +253 -0
- data/lib/elkrb/layout/algorithms/layered/port_order.rb +188 -0
- data/lib/elkrb/layout/algorithms/layered/port_spread.rb +42 -0
- data/lib/elkrb/layout/algorithms/layered.rb +548 -13
- data/lib/elkrb/layout/algorithms/libavoid.rb +951 -187
- data/lib/elkrb/layout/algorithms/mrtree.rb +585 -65
- data/lib/elkrb/layout/algorithms/radial.rb +188 -29
- data/lib/elkrb/layout/algorithms/random.rb +131 -17
- data/lib/elkrb/layout/algorithms/rectpacking.rb +28 -40
- data/lib/elkrb/layout/algorithms/spore_compaction.rb +57 -100
- data/lib/elkrb/layout/algorithms/spore_overlap.rb +78 -84
- data/lib/elkrb/layout/algorithms/stress.rb +391 -84
- data/lib/elkrb/layout/algorithms/topdown_packing.rb +95 -31
- data/lib/elkrb/layout/algorithms/vertiflex.rb +12 -47
- data/lib/elkrb/layout/constraints/alignment_constraint.rb +49 -12
- data/lib/elkrb/layout/constraints/base_constraint.rb +3 -15
- data/lib/elkrb/layout/constraints/constraint_processor.rb +9 -23
- data/lib/elkrb/layout/constraints/fixed_position_constraint.rb +22 -13
- data/lib/elkrb/layout/constraints/layer_constraint.rb +10 -33
- data/lib/elkrb/layout/constraints/relative_position_constraint.rb +94 -50
- data/lib/elkrb/layout/edge_router.rb +533 -337
- data/lib/elkrb/layout/hierarchical_processor.rb +19 -276
- data/lib/elkrb/layout/java_random.rb +59 -0
- data/lib/elkrb/layout/label_placer.rb +165 -157
- data/lib/elkrb/layout/layout_engine.rb +46 -23
- data/lib/elkrb/layout/node_index.rb +149 -0
- data/lib/elkrb/layout/polyomino/component_compactor.rb +138 -0
- data/lib/elkrb/layout/polyomino/footprint.rb +94 -0
- data/lib/elkrb/layout/polyomino/grid.rb +106 -0
- data/lib/elkrb/layout/polyomino/packer.rb +95 -0
- data/lib/elkrb/layout/polyomino/piece.rb +52 -0
- data/lib/elkrb/layout/port_constraint_processor.rb +131 -44
- data/lib/elkrb/layout/spore/body.rb +65 -0
- data/lib/elkrb/layout/spore/depth_first_compaction.rb +127 -0
- data/lib/elkrb/layout/spore/edge_cost.rb +69 -0
- data/lib/elkrb/layout/spore/gap.rb +76 -0
- data/lib/elkrb/layout/spore/geometry.rb +104 -0
- data/lib/elkrb/layout/spore/overlap_sweep.rb +90 -0
- data/lib/elkrb/layout/spore/placement.rb +57 -0
- data/lib/elkrb/layout/spore/rays.rb +119 -0
- data/lib/elkrb/layout/spore/spanning_tree.rb +53 -0
- data/lib/elkrb/layout/spore/triangulation.rb +159 -0
- data/lib/elkrb/layout_flags.rb +69 -0
- data/lib/elkrb/options/decimal.rb +50 -0
- data/lib/elkrb/options/element_options.rb +43 -0
- data/lib/elkrb/options/elk_padding.rb +20 -14
- data/lib/elkrb/options/k_vector.rb +6 -7
- data/lib/elkrb/options/k_vector_chain.rb +12 -20
- data/lib/elkrb/options/option_map.rb +85 -0
- data/lib/elkrb/options/registry.rb +335 -0
- data/lib/elkrb/options/resolver.rb +151 -0
- data/lib/elkrb/options/spellings.rb +62 -0
- data/lib/elkrb/options/unhonoured_report.rb +194 -0
- data/lib/elkrb/parsers/elkt/lexer.rb +309 -0
- data/lib/elkrb/parsers/elkt/parser.rb +424 -0
- data/lib/elkrb/parsers/elkt/resolver.rb +142 -0
- data/lib/elkrb/parsers/elkt/token.rb +40 -0
- data/lib/elkrb/parsers/elkt_parser.rb +32 -230
- data/lib/elkrb/serializers/dot_serializer.rb +342 -54
- data/lib/elkrb/serializers/elkt_serializer.rb +386 -155
- data/lib/elkrb/version.rb +1 -1
- data/lib/elkrb.rb +60 -61
- metadata +71 -49
- data/.rspec +0 -3
- data/.rubocop.yml +0 -11
- data/Gemfile +0 -13
- data/Rakefile +0 -64
- data/benchmarks/README.md +0 -172
- data/benchmarks/elkjs_benchmark.js +0 -140
- data/benchmarks/elkrb_benchmark.rb +0 -145
- data/benchmarks/fixtures/graphs.json +0 -10777
- data/benchmarks/generate_report.rb +0 -241
- data/benchmarks/generate_test_graphs.rb +0 -154
- data/benchmarks/results/elkrb_results.json +0 -280
- data/benchmarks/results/elkrb_summary.json +0 -285
- data/elkrb.gemspec +0 -39
- data/examples/dot_export_demo.rb +0 -133
- data/examples/hierarchical_graph.rb +0 -19
- data/examples/layout_constraints_demo.rb +0 -272
- data/examples/port_constraints_demo.rb +0 -291
- data/examples/self_loop_demo.rb +0 -391
- data/examples/simple_graph.rb +0 -50
- data/examples/spline_routing_demo.rb +0 -235
- data/lib/elkrb/graph/layout_options.rb +0 -247
- data/sig/elkrb/constraints.rbs +0 -114
- data/sig/elkrb/geometry.rbs +0 -61
- data/sig/elkrb/graph.rbs +0 -112
- data/sig/elkrb/layout.rbs +0 -107
- data/sig/elkrb/options.rbs +0 -81
- data/sig/elkrb.rbs +0 -32
data/README.adoc
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
= ElkRb
|
|
2
2
|
|
|
3
3
|
image:https://img.shields.io/gem/v/elkrb.svg[RubyGems Version]
|
|
4
|
-
image:https://img.shields.io/github/license/
|
|
5
|
-
image:https://github.com/
|
|
4
|
+
image:https://img.shields.io/github/license/claricle/elkrb.svg[License]
|
|
5
|
+
image:https://github.com/claricle/elkrb/actions/workflows/rake.yml/badge.svg["Build", link="https://github.com/claricle/elkrb/actions/workflows/rake.yml"]
|
|
6
6
|
|
|
7
7
|
== Purpose
|
|
8
8
|
|
|
@@ -22,90 +22,50 @@ The library is designed for box-based diagrams including:
|
|
|
22
22
|
ElkRb is built on top of the `lutaml-model` serialization framework, providing
|
|
23
23
|
flexible YAML and JSON serialization with ELK JSON format compatibility.
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
[[compatibility-with-elk]]
|
|
26
|
+
== Compatibility with ELK
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
compatible in specs)
|
|
28
|
+
ElkRb is format-compatible with elkjs 0.11.0: it reads and writes the ELK JSON
|
|
29
|
+
graph format.
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
|
31
|
+
Coordinate parity with elkjs is exactly what the tiers in
|
|
32
|
+
`spec/elkrb/golden_spec.rb` say it is. Cases in the `exact` tier compare
|
|
33
|
+
coordinates; cases in the looser `structural` tier do not, and a case that
|
|
34
|
+
carries a `pending:` reason in `spec/support/golden_cases.rb` is known to
|
|
35
|
+
differ.
|
|
67
36
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
* Configurable routing styles
|
|
37
|
+
Layered places nodes with Brandes-Köpf only; every `nodePlacement.strategy`
|
|
38
|
+
value falls back to it.
|
|
71
39
|
|
|
40
|
+
Not every option ELK defines changes the result here. Each option elkrb knows
|
|
41
|
+
carries a status (`honoured`, `partial`, `accepted` or `unsupported`); see
|
|
42
|
+
<<option-status>>.
|
|
72
43
|
|
|
73
|
-
|
|
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
|
-
|
|
44
|
+
== Features
|
|
87
45
|
|
|
88
|
-
|
|
46
|
+
Layout algorithms:: every algorithm in the registry (see
|
|
47
|
+
<<layout-algorithms>>), including layered, force, stress, box, fixed, random,
|
|
48
|
+
MRTree, radial, RectPacking, TopdownPacking, DISCO, SPOrE Overlap, SPOrE
|
|
49
|
+
Compaction, Libavoid and VertiFlex.
|
|
89
50
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
* Port side detection for labels
|
|
51
|
+
Edge routing:: ORTHOGONAL, POLYLINE and SPLINES, with bend points on the
|
|
52
|
+
computed edge sections.
|
|
93
53
|
|
|
94
|
-
|
|
54
|
+
Ports:: ports with an explicit side and index, placed on the node border, and
|
|
55
|
+
port-to-port edges.
|
|
95
56
|
|
|
96
|
-
|
|
97
|
-
|
|
57
|
+
Self-loops:: edges whose source and target are the same node, or ports of the
|
|
58
|
+
same node, routed outside the node border.
|
|
98
59
|
|
|
99
|
-
|
|
60
|
+
Hierarchy:: nested graphs laid out recursively.
|
|
100
61
|
|
|
101
|
-
|
|
102
|
-
* Algorithm-specific options
|
|
103
|
-
* Layout categories and metadata
|
|
62
|
+
Labels:: automatic placement of node, edge and port labels.
|
|
104
63
|
|
|
105
|
-
|
|
64
|
+
Export formats:: ELK JSON, YAML (Ruby-specific), Graphviz DOT and ELKT.
|
|
106
65
|
|
|
107
|
-
|
|
108
|
-
|
|
66
|
+
Command-line interface:: `layout`, `algorithms`, `options`, `diagram`,
|
|
67
|
+
`convert`, `render`, `validate`, `batch` and `version`; see
|
|
68
|
+
<<command-line-interface>>.
|
|
109
69
|
|
|
110
70
|
== Architecture
|
|
111
71
|
|
|
@@ -141,7 +101,7 @@ Input Graph (YAML/JSON/Hash)
|
|
|
141
101
|
└────┬────┘
|
|
142
102
|
│
|
|
143
103
|
▼
|
|
144
|
-
Output (
|
|
104
|
+
Output (Graph; serialize with to_json / to_yaml)
|
|
145
105
|
----
|
|
146
106
|
|
|
147
107
|
.Graph model structure
|
|
@@ -189,651 +149,681 @@ $ gem install elkrb
|
|
|
189
149
|
|
|
190
150
|
=== Basic graph layout
|
|
191
151
|
|
|
192
|
-
|
|
193
|
-
|
|
152
|
+
`Elkrb.layout` takes a graph as a Ruby hash (or an `Elkrb::Graph::Graph`) and
|
|
153
|
+
returns an `Elkrb::Graph::Graph` with positions filled in. Read the result
|
|
154
|
+
through attribute readers.
|
|
155
|
+
|
|
156
|
+
Put the layout options in the graph's own `layoutOptions`; that is the primary
|
|
157
|
+
way to configure a layout.
|
|
194
158
|
|
|
195
159
|
[source,ruby]
|
|
196
160
|
----
|
|
197
161
|
require "elkrb"
|
|
198
162
|
|
|
199
|
-
# Define a simple graph
|
|
200
163
|
graph = {
|
|
201
164
|
id: "root",
|
|
202
165
|
layoutOptions: {
|
|
203
|
-
"elk.algorithm" => "layered"
|
|
166
|
+
"elk.algorithm" => "layered",
|
|
167
|
+
"elk.direction" => "DOWN",
|
|
204
168
|
},
|
|
205
169
|
children: [
|
|
206
170
|
{ id: "n1", width: 100, height: 60 },
|
|
207
171
|
{ id: "n2", width: 100, height: 60 },
|
|
208
|
-
{ id: "n3", width: 100, height: 60 }
|
|
172
|
+
{ id: "n3", width: 100, height: 60 },
|
|
209
173
|
],
|
|
210
174
|
edges: [
|
|
211
175
|
{ id: "e1", sources: ["n1"], targets: ["n2"] },
|
|
212
|
-
{ id: "e2", sources: ["n1"], targets: ["n3"] }
|
|
213
|
-
]
|
|
176
|
+
{ id: "e2", sources: ["n1"], targets: ["n3"] },
|
|
177
|
+
],
|
|
214
178
|
}
|
|
215
179
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
result
|
|
180
|
+
result = Elkrb.layout(graph)
|
|
181
|
+
|
|
182
|
+
result.children.each do |node|
|
|
183
|
+
puts "#{node.id}: x=#{node.x} y=#{node.y}"
|
|
184
|
+
end
|
|
185
|
+
puts "root: #{result.width} x #{result.height}"
|
|
186
|
+
----
|
|
219
187
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
188
|
+
Prints:
|
|
189
|
+
|
|
190
|
+
[source]
|
|
191
|
+
----
|
|
192
|
+
n1: x=28.666666666666664 y=12.0
|
|
193
|
+
n2: x=12.0 y=92.0
|
|
194
|
+
n3: x=132.0 y=92.0
|
|
195
|
+
root: 244.0 x 164.0
|
|
224
196
|
----
|
|
225
197
|
|
|
226
|
-
A
|
|
198
|
+
A Hash argument is converted to a fresh graph and left untouched. A
|
|
199
|
+
`Graph` argument is laid out in place and returned. The model classes build the
|
|
200
|
+
same graph:
|
|
227
201
|
|
|
228
202
|
[source,ruby]
|
|
229
203
|
----
|
|
230
204
|
require "elkrb"
|
|
231
205
|
|
|
232
|
-
# Create graph using model classes
|
|
233
206
|
graph = Elkrb::Graph::Graph.new(id: "root")
|
|
207
|
+
graph.children = [
|
|
208
|
+
Elkrb::Graph::Node.new(id: "n1", width: 100, height: 60),
|
|
209
|
+
Elkrb::Graph::Node.new(id: "n2", width: 100, height: 60),
|
|
210
|
+
]
|
|
211
|
+
graph.edges = [
|
|
212
|
+
Elkrb::Graph::Edge.new(id: "e1", sources: ["n1"], targets: ["n2"]),
|
|
213
|
+
]
|
|
214
|
+
graph.layout_options = { "elk.algorithm" => "layered", "elk.direction" => "DOWN" }
|
|
215
|
+
|
|
216
|
+
result = Elkrb.layout(graph)
|
|
234
217
|
|
|
235
|
-
|
|
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
|
|
218
|
+
puts result.equal?(graph)
|
|
268
219
|
puts result.to_yaml
|
|
269
|
-
puts result.to_json
|
|
270
220
|
----
|
|
271
221
|
|
|
272
|
-
|
|
222
|
+
Prints:
|
|
223
|
+
|
|
224
|
+
[source]
|
|
225
|
+
----
|
|
226
|
+
true
|
|
227
|
+
---
|
|
228
|
+
id: root
|
|
229
|
+
x: 0.0
|
|
230
|
+
y: 0.0
|
|
231
|
+
width: 124.0
|
|
232
|
+
height: 164.0
|
|
233
|
+
children:
|
|
234
|
+
- id: n1
|
|
235
|
+
x: 12.0
|
|
236
|
+
y: 12.0
|
|
237
|
+
width: 100.0
|
|
238
|
+
height: 60.0
|
|
239
|
+
- id: n2
|
|
240
|
+
x: 12.0
|
|
241
|
+
y: 92.0
|
|
242
|
+
width: 100.0
|
|
243
|
+
height: 60.0
|
|
244
|
+
edges:
|
|
245
|
+
- id: e1
|
|
246
|
+
sources:
|
|
247
|
+
- n1
|
|
248
|
+
targets:
|
|
249
|
+
- n2
|
|
250
|
+
sections:
|
|
251
|
+
- id: e1_s0
|
|
252
|
+
start_point:
|
|
253
|
+
x: 62.0
|
|
254
|
+
y: 72.0
|
|
255
|
+
end_point:
|
|
256
|
+
x: 62.0
|
|
257
|
+
y: 92.0
|
|
258
|
+
incoming_shape: n1
|
|
259
|
+
outgoing_shape: n2
|
|
260
|
+
container: root
|
|
261
|
+
layout_options:
|
|
262
|
+
elk.algorithm: layered
|
|
263
|
+
elk.direction: DOWN
|
|
264
|
+
----
|
|
265
|
+
|
|
266
|
+
`result.to_json` writes the same graph in ELK JSON.
|
|
267
|
+
|
|
268
|
+
=== Call-level options
|
|
269
|
+
|
|
270
|
+
Options can also be passed to `Elkrb.layout` itself, as globals for that one
|
|
271
|
+
call.
|
|
273
272
|
|
|
274
273
|
[source,ruby]
|
|
275
274
|
----
|
|
276
275
|
require "elkrb"
|
|
277
276
|
|
|
278
|
-
# UML class diagram with ports for connections
|
|
279
277
|
graph = {
|
|
280
|
-
id: "
|
|
281
|
-
layoutOptions: {
|
|
282
|
-
"elk.algorithm" => "layered",
|
|
283
|
-
"elk.direction" => "DOWN",
|
|
284
|
-
"elk.spacing.nodeNode" => 50
|
|
285
|
-
},
|
|
278
|
+
id: "root",
|
|
286
279
|
children: [
|
|
287
|
-
{
|
|
288
|
-
|
|
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
|
-
}
|
|
280
|
+
{ id: "n1", width: 100, height: 60 },
|
|
281
|
+
{ id: "n2", width: 100, height: 60 },
|
|
321
282
|
],
|
|
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
|
-
]
|
|
283
|
+
edges: [{ id: "e1", sources: ["n1"], targets: ["n2"] }],
|
|
340
284
|
}
|
|
341
285
|
|
|
342
|
-
|
|
343
|
-
|
|
286
|
+
down = Elkrb.layout(graph, "elk.direction" => "DOWN")
|
|
287
|
+
puts "DOWN: n2 at (#{down.children[1].x}, #{down.children[1].y})"
|
|
288
|
+
|
|
289
|
+
right = Elkrb.layout(graph, "elk.direction" => "RIGHT")
|
|
290
|
+
puts "RIGHT: n2 at (#{right.children[1].x}, #{right.children[1].y})"
|
|
291
|
+
----
|
|
292
|
+
|
|
293
|
+
Prints:
|
|
294
|
+
|
|
295
|
+
[source]
|
|
296
|
+
----
|
|
297
|
+
DOWN: n2 at (12.0, 92.0)
|
|
298
|
+
RIGHT: n2 at (132.0, 12.0)
|
|
344
299
|
----
|
|
345
300
|
|
|
346
|
-
|
|
301
|
+
`algorithm:` is the call-level spelling of `"elk.algorithm"`.
|
|
302
|
+
|
|
303
|
+
Options are read in one order: the graph's own `layoutOptions`, then its
|
|
304
|
+
`properties`, then the call-level options, then the registry default. A graph
|
|
305
|
+
that pins an algorithm keeps it, whatever the call passes:
|
|
347
306
|
|
|
348
307
|
[source,ruby]
|
|
349
308
|
----
|
|
350
309
|
require "elkrb"
|
|
351
310
|
|
|
352
|
-
# EXPRESS-G entity-relationship diagram
|
|
353
311
|
graph = {
|
|
354
|
-
id: "
|
|
355
|
-
layoutOptions: {
|
|
356
|
-
"elk.algorithm" => "layered",
|
|
357
|
-
"elk.direction" => "RIGHT",
|
|
358
|
-
"elk.edgeRouting" => "ORTHOGONAL"
|
|
359
|
-
},
|
|
312
|
+
id: "root",
|
|
313
|
+
layoutOptions: { "elk.algorithm" => "box" },
|
|
360
314
|
children: [
|
|
361
|
-
{
|
|
362
|
-
|
|
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
|
-
}
|
|
315
|
+
{ id: "n1", width: 100, height: 60 },
|
|
316
|
+
{ id: "n2", width: 100, height: 60 },
|
|
379
317
|
],
|
|
380
|
-
edges: [
|
|
381
|
-
{ id: "e1", sources: ["Entity1"], targets: ["Attribute1"] },
|
|
382
|
-
{ id: "e2", sources: ["Entity1"], targets: ["Attribute2"] }
|
|
383
|
-
]
|
|
318
|
+
edges: [{ id: "e1", sources: ["n1"], targets: ["n2"] }],
|
|
384
319
|
}
|
|
385
320
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
----
|
|
389
|
-
|
|
390
|
-
=== Layout from YAML File
|
|
321
|
+
pinned = Elkrb.layout(graph, algorithm: "force")
|
|
322
|
+
unpinned = Elkrb.layout(graph.except(:layoutOptions), algorithm: "box")
|
|
391
323
|
|
|
392
|
-
[
|
|
324
|
+
positions = ->(g) { g.children.map { |n| [n.x, n.y] } }
|
|
325
|
+
puts positions.call(pinned) == positions.call(unpinned)
|
|
393
326
|
----
|
|
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
327
|
|
|
400
|
-
|
|
401
|
-
engine = Elkrb::LayoutEngine.new
|
|
402
|
-
result = engine.layout(graph)
|
|
328
|
+
Prints:
|
|
403
329
|
|
|
404
|
-
|
|
405
|
-
|
|
330
|
+
[source]
|
|
331
|
+
----
|
|
332
|
+
true
|
|
406
333
|
----
|
|
407
334
|
|
|
408
|
-
|
|
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
|
|
335
|
+
=== UML class diagram layout
|
|
424
336
|
|
|
425
337
|
[source,ruby]
|
|
426
338
|
----
|
|
427
|
-
|
|
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
|
|
339
|
+
require "elkrb"
|
|
438
340
|
|
|
439
|
-
|
|
341
|
+
graph = {
|
|
342
|
+
id: "uml_diagram",
|
|
343
|
+
layoutOptions: {
|
|
344
|
+
"elk.algorithm" => "layered",
|
|
345
|
+
"elk.direction" => "DOWN",
|
|
346
|
+
"elk.spacing.nodeNode" => 50,
|
|
347
|
+
},
|
|
348
|
+
children: [
|
|
349
|
+
{ id: "Person", width: 120, height: 80,
|
|
350
|
+
labels: [{ text: "Person", width: 100, height: 20 }] },
|
|
351
|
+
{ id: "Employee", width: 120, height: 80,
|
|
352
|
+
labels: [{ text: "Employee", width: 100, height: 20 }] },
|
|
353
|
+
{ id: "Customer", width: 120, height: 80,
|
|
354
|
+
labels: [{ text: "Customer", width: 100, height: 20 }] },
|
|
355
|
+
],
|
|
356
|
+
edges: [
|
|
357
|
+
{ id: "inheritance1", sources: ["Employee"], targets: ["Person"] },
|
|
358
|
+
{ id: "inheritance2", sources: ["Customer"], targets: ["Person"] },
|
|
359
|
+
],
|
|
360
|
+
}
|
|
440
361
|
|
|
441
|
-
|
|
362
|
+
result = Elkrb.layout(graph)
|
|
442
363
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
364
|
+
result.children.each do |node|
|
|
365
|
+
puts "#{node.id}: (#{node.x}, #{node.y})"
|
|
366
|
+
end
|
|
367
|
+
----
|
|
447
368
|
|
|
448
|
-
|
|
369
|
+
Prints:
|
|
449
370
|
|
|
450
|
-
[source
|
|
371
|
+
[source]
|
|
451
372
|
----
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
"elk.force.temperature" => 0.001
|
|
456
|
-
}
|
|
373
|
+
Person: (32.0, 112.0)
|
|
374
|
+
Employee: (182.0, 12.0)
|
|
375
|
+
Customer: (12.0, 12.0)
|
|
457
376
|
----
|
|
458
377
|
|
|
459
|
-
===
|
|
378
|
+
=== Layout from YAML
|
|
460
379
|
|
|
461
|
-
|
|
380
|
+
[source,ruby]
|
|
381
|
+
----
|
|
382
|
+
require "elkrb"
|
|
462
383
|
|
|
463
|
-
|
|
384
|
+
yaml = <<~YAML
|
|
385
|
+
id: root
|
|
386
|
+
layout_options:
|
|
387
|
+
elk.algorithm: layered
|
|
388
|
+
elk.direction: RIGHT
|
|
389
|
+
children:
|
|
390
|
+
- id: a
|
|
391
|
+
width: 80
|
|
392
|
+
height: 40
|
|
393
|
+
- id: b
|
|
394
|
+
width: 80
|
|
395
|
+
height: 40
|
|
396
|
+
edges:
|
|
397
|
+
- id: e1
|
|
398
|
+
sources: [a]
|
|
399
|
+
targets: [b]
|
|
400
|
+
YAML
|
|
401
|
+
|
|
402
|
+
graph = Elkrb::Graph::Graph.from_yaml(yaml)
|
|
403
|
+
result = Elkrb.layout(graph)
|
|
464
404
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
405
|
+
puts result.children.map { |n| "#{n.id}: (#{n.x}, #{n.y})" }
|
|
406
|
+
File.write("diagram_laid_out.yml", result.to_yaml)
|
|
407
|
+
puts File.size?("diagram_laid_out.yml").positive?
|
|
408
|
+
----
|
|
468
409
|
|
|
469
|
-
|
|
410
|
+
Prints:
|
|
470
411
|
|
|
471
|
-
[source
|
|
412
|
+
[source]
|
|
472
413
|
----
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
414
|
+
a: (12.0, 12.0)
|
|
415
|
+
b: (112.0, 12.0)
|
|
416
|
+
true
|
|
476
417
|
----
|
|
477
418
|
|
|
478
|
-
|
|
419
|
+
YAML uses snake_case keys (`layout_options`); JSON uses the ELK camelCase keys
|
|
420
|
+
(`layoutOptions`).
|
|
479
421
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
Use cases:
|
|
483
|
-
|
|
484
|
-
* Simple container layouts
|
|
485
|
-
* Grid-like arrangements
|
|
486
|
-
* Dashboards
|
|
422
|
+
[[layout-algorithms]]
|
|
423
|
+
== Layout algorithms
|
|
487
424
|
|
|
488
|
-
|
|
425
|
+
Select an algorithm with `"elk.algorithm"`. The registry is the list of what
|
|
426
|
+
is available:
|
|
489
427
|
|
|
490
428
|
[source,ruby]
|
|
491
429
|
----
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
430
|
+
require "elkrb"
|
|
431
|
+
|
|
432
|
+
puts Elkrb.known_layout_algorithms.size
|
|
433
|
+
Elkrb.known_layout_algorithms.each do |algorithm|
|
|
434
|
+
puts "#{algorithm[:id]}: #{algorithm[:name]}"
|
|
435
|
+
end
|
|
495
436
|
----
|
|
496
437
|
|
|
497
|
-
|
|
438
|
+
Prints:
|
|
498
439
|
|
|
499
|
-
|
|
440
|
+
[source]
|
|
441
|
+
----
|
|
442
|
+
15
|
|
443
|
+
box: Box
|
|
444
|
+
disco: DISCO
|
|
445
|
+
fixed: Fixed
|
|
446
|
+
force: Force-Directed
|
|
447
|
+
layered: Layered (Sugiyama)
|
|
448
|
+
libavoid: Libavoid
|
|
449
|
+
mrtree: Multi-Rooted Tree
|
|
450
|
+
radial: Radial
|
|
451
|
+
random: Random
|
|
452
|
+
rectpacking: Rectangle Packing
|
|
453
|
+
spore_compaction: SPOrE Compaction
|
|
454
|
+
spore_overlap: SPOrE Overlap Removal
|
|
455
|
+
stress: Stress Minimization
|
|
456
|
+
topdownpacking: TopdownPacking
|
|
457
|
+
vertiflex: VertiFlex
|
|
458
|
+
----
|
|
500
459
|
|
|
501
|
-
|
|
460
|
+
Each entry also carries `:description`, `:category`, `:supports_hierarchy` and
|
|
461
|
+
`:supported_options`, the option ids that algorithm reads.
|
|
502
462
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
* Multi-root taxonomies
|
|
463
|
+
[cols="1,3",options="header"]
|
|
464
|
+
|===
|
|
465
|
+
| Algorithm | Use it for
|
|
507
466
|
|
|
508
|
-
|
|
467
|
+
| `layered`
|
|
468
|
+
| Directed, hierarchical diagrams with a flow direction: UML class diagrams,
|
|
469
|
+
flowcharts, data flow diagrams, organization charts, dependency graphs.
|
|
470
|
+
Supports nested graphs.
|
|
509
471
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
layoutOptions: {
|
|
513
|
-
"elk.algorithm" => "mrtree",
|
|
514
|
-
"elk.direction" => "DOWN",
|
|
515
|
-
"elk.spacing.nodeNode" => 30
|
|
516
|
-
}
|
|
517
|
-
----
|
|
472
|
+
| `force`, `stress`
|
|
473
|
+
| Undirected network diagrams and social graphs.
|
|
518
474
|
|
|
519
|
-
|
|
475
|
+
| `mrtree`
|
|
476
|
+
| Forests and trees with several roots. Supports nested graphs.
|
|
520
477
|
|
|
521
|
-
|
|
478
|
+
| `radial`
|
|
479
|
+
| Hub-and-spoke arrangements around a centre.
|
|
522
480
|
|
|
523
|
-
|
|
481
|
+
| `box`, `rectpacking`, `topdownpacking`, `vertiflex`
|
|
482
|
+
| Grid-like and packed arrangements; dashboards and tile layouts.
|
|
524
483
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
* Hub-and-spoke diagrams
|
|
528
|
-
* Cyclic structures
|
|
484
|
+
| `disco`
|
|
485
|
+
| Graphs made of several disconnected components.
|
|
529
486
|
|
|
530
|
-
|
|
487
|
+
| `spore_overlap`, `spore_compaction`
|
|
488
|
+
| Post-processing: remove overlaps between nodes, or compact whitespace.
|
|
531
489
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
layoutOptions: {
|
|
535
|
-
"elk.algorithm" => "radial",
|
|
536
|
-
"elk.spacing.nodeNode" => 50
|
|
537
|
-
}
|
|
538
|
-
----
|
|
490
|
+
| `libavoid`
|
|
491
|
+
| Connector routing around obstacles.
|
|
539
492
|
|
|
540
|
-
|
|
493
|
+
| `fixed`, `random`
|
|
494
|
+
| Keep the positions the graph already carries, or place nodes at random.
|
|
495
|
+
|===
|
|
541
496
|
|
|
542
|
-
|
|
497
|
+
[[option-status]]
|
|
498
|
+
=== Option status
|
|
543
499
|
|
|
544
|
-
|
|
500
|
+
Options are validated against `Elkrb::Options::Registry`. `Elkrb.known_layout_options`
|
|
501
|
+
returns every known option id with its type, default, allowed values and
|
|
502
|
+
status. The status says how much of the option elkrb acts on: `honoured`,
|
|
503
|
+
`partial`, `accepted` (parsed and validated, but it does not change the
|
|
504
|
+
result) or `unsupported`.
|
|
545
505
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
* Component grids
|
|
506
|
+
Run `elkrb options` to list every option with its status, or
|
|
507
|
+
`bundle exec rake docs:all` to write the full option and elkjs comparison
|
|
508
|
+
pages under `docs/`.
|
|
550
509
|
|
|
551
|
-
|
|
510
|
+
By default an option elkrb does not fully honour logs a warning through
|
|
511
|
+
`Elkrb.logger`. Pass `strict: true` to raise `Elkrb::Error` instead:
|
|
552
512
|
|
|
553
513
|
[source,ruby]
|
|
554
514
|
----
|
|
555
|
-
|
|
556
|
-
"elk.algorithm" => "rectpacking",
|
|
557
|
-
"elk.spacing.nodeNode" => 15
|
|
558
|
-
}
|
|
559
|
-
----
|
|
560
|
-
|
|
561
|
-
=== DISCO Layout
|
|
515
|
+
require "elkrb"
|
|
562
516
|
|
|
563
|
-
|
|
517
|
+
direction = Elkrb.known_layout_options["elk.direction"]
|
|
518
|
+
puts direction[:status]
|
|
519
|
+
puts direction[:values].inspect
|
|
564
520
|
|
|
565
|
-
|
|
521
|
+
graph = {
|
|
522
|
+
id: "root",
|
|
523
|
+
layoutOptions: { "elk.layered.nodePlacement.strategy" => "NETWORK_SIMPLEX" },
|
|
524
|
+
children: [{ id: "n1", width: 40, height: 40 }],
|
|
525
|
+
}
|
|
566
526
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
527
|
+
begin
|
|
528
|
+
Elkrb.layout(graph, strict: true)
|
|
529
|
+
rescue Elkrb::Error => e
|
|
530
|
+
puts e.message
|
|
531
|
+
end
|
|
532
|
+
----
|
|
571
533
|
|
|
572
|
-
|
|
534
|
+
Prints:
|
|
573
535
|
|
|
574
|
-
[source
|
|
536
|
+
[source]
|
|
575
537
|
----
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
"disco.componentCompaction.componentLayoutAlgorithm" => "layered",
|
|
580
|
-
"disco.spacing.componentComponent" => 30
|
|
581
|
-
}
|
|
538
|
+
partial
|
|
539
|
+
["UNDEFINED", "RIGHT", "LEFT", "DOWN", "UP"]
|
|
540
|
+
strict mode: unknown or unhonoured layout option(s): elk.layered.nodePlacement.strategy (accepted, not honoured)
|
|
582
541
|
----
|
|
583
542
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
Removes overlaps between nodes while preserving the overall structure.
|
|
543
|
+
A value of the wrong type, such as `"abc"` for `elk.spacing.nodeNode`, raises
|
|
544
|
+
`Elkrb::ValidationError`.
|
|
587
545
|
|
|
588
|
-
|
|
546
|
+
== Advanced features
|
|
589
547
|
|
|
590
|
-
|
|
591
|
-
* Cleaning up manually positioned diagrams
|
|
592
|
-
* Overlap resolution
|
|
593
|
-
* Layout refinement
|
|
548
|
+
=== Edge routing
|
|
594
549
|
|
|
595
|
-
|
|
550
|
+
Choose the routing style with `"elk.edgeRouting"` (`ORTHOGONAL`, `POLYLINE`
|
|
551
|
+
or `SPLINES`). The computed route is on the edge's sections:
|
|
596
552
|
|
|
597
553
|
[source,ruby]
|
|
598
554
|
----
|
|
599
|
-
|
|
600
|
-
"elk.algorithm" => "sporeOverlap",
|
|
601
|
-
"elk.spacing.nodeNode" => 10,
|
|
602
|
-
"sporeOverlap.maxIterations" => 100
|
|
603
|
-
}
|
|
604
|
-
----
|
|
605
|
-
|
|
606
|
-
=== SPOrE Compaction
|
|
555
|
+
require "elkrb"
|
|
607
556
|
|
|
608
|
-
|
|
557
|
+
graph = {
|
|
558
|
+
id: "root",
|
|
559
|
+
layoutOptions: {
|
|
560
|
+
"elk.algorithm" => "layered",
|
|
561
|
+
"elk.direction" => "DOWN",
|
|
562
|
+
"elk.edgeRouting" => "ORTHOGONAL",
|
|
563
|
+
},
|
|
564
|
+
children: [
|
|
565
|
+
{ id: "a", width: 60, height: 40 },
|
|
566
|
+
{ id: "b", width: 60, height: 40 },
|
|
567
|
+
{ id: "c", width: 60, height: 40 },
|
|
568
|
+
],
|
|
569
|
+
edges: [
|
|
570
|
+
{ id: "e1", sources: ["a"], targets: ["b"] },
|
|
571
|
+
{ id: "e2", sources: ["a"], targets: ["c"] },
|
|
572
|
+
],
|
|
573
|
+
}
|
|
609
574
|
|
|
610
|
-
|
|
575
|
+
result = Elkrb.layout(graph)
|
|
611
576
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
577
|
+
result.edges.each do |edge|
|
|
578
|
+
section = edge.sections.first
|
|
579
|
+
bends = section.bend_points.map { |p| [p.x.round(1), p.y.round(1)] }
|
|
580
|
+
puts "#{edge.id}: (#{section.start_point.x.round(1)}, " \
|
|
581
|
+
"#{section.start_point.y.round(1)}) -> " \
|
|
582
|
+
"(#{section.end_point.x.round(1)}, #{section.end_point.y.round(1)}) " \
|
|
583
|
+
"bends=#{bends}"
|
|
584
|
+
end
|
|
585
|
+
----
|
|
616
586
|
|
|
617
|
-
|
|
587
|
+
Prints:
|
|
618
588
|
|
|
619
|
-
[source
|
|
589
|
+
[source]
|
|
620
590
|
----
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
"sporeCompaction.compactionStrategy" => "BOTH", # HORIZONTAL, VERTICAL, BOTH
|
|
624
|
-
"elk.spacing.nodeNode" => 10,
|
|
625
|
-
"sporeCompaction.normalize" => true
|
|
626
|
-
}
|
|
591
|
+
e1: (42.0, 52.0) -> (42.0, 72.0) bends=[]
|
|
592
|
+
e2: (62.0, 52.0) -> (122.0, 72.0) bends=[[62.0, 62.0], [122.0, 62.0]]
|
|
627
593
|
----
|
|
628
594
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
=== Edge routing
|
|
632
|
-
|
|
633
|
-
ElkRb supports advanced edge routing with bend points and port awareness.
|
|
595
|
+
With `"elk.edgeRouting" => "SPLINES"` the section carries the control points of
|
|
596
|
+
the curve in `bend_points`:
|
|
634
597
|
|
|
635
598
|
[source,ruby]
|
|
636
599
|
----
|
|
637
|
-
|
|
600
|
+
require "elkrb"
|
|
601
|
+
|
|
638
602
|
graph = {
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
603
|
+
id: "root",
|
|
604
|
+
layoutOptions: { "elk.algorithm" => "layered", "elk.edgeRouting" => "SPLINES" },
|
|
605
|
+
children: [
|
|
606
|
+
{ id: "a", width: 60, height: 40 },
|
|
607
|
+
{ id: "b", width: 60, height: 40 },
|
|
608
|
+
],
|
|
609
|
+
edges: [{ id: "e1", sources: ["a"], targets: ["b"] }],
|
|
644
610
|
}
|
|
645
611
|
|
|
646
612
|
result = Elkrb.layout(graph)
|
|
613
|
+
section = result.edges.first.sections.first
|
|
614
|
+
puts section.bend_points.size
|
|
615
|
+
puts section.start_point.x
|
|
616
|
+
----
|
|
647
617
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
618
|
+
Prints:
|
|
619
|
+
|
|
620
|
+
[source]
|
|
621
|
+
----
|
|
622
|
+
2
|
|
623
|
+
72.0
|
|
653
624
|
----
|
|
654
625
|
|
|
655
626
|
=== Hierarchical graphs
|
|
656
627
|
|
|
657
|
-
|
|
628
|
+
A node with `children` is laid out recursively. The parent is sized to contain
|
|
629
|
+
its children, and child coordinates are relative to the parent.
|
|
658
630
|
|
|
659
631
|
[source,ruby]
|
|
660
632
|
----
|
|
633
|
+
require "elkrb"
|
|
634
|
+
|
|
661
635
|
graph = {
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
"hierarchical" => true
|
|
665
|
-
},
|
|
636
|
+
id: "root",
|
|
637
|
+
layoutOptions: { "elk.algorithm" => "layered" },
|
|
666
638
|
children: [
|
|
667
639
|
{
|
|
668
640
|
id: "parent",
|
|
669
|
-
width: 300,
|
|
670
|
-
height: 200,
|
|
671
641
|
children: [
|
|
672
642
|
{ id: "child1", width: 80, height: 60 },
|
|
673
|
-
{ id: "child2", width: 80, height: 60 }
|
|
643
|
+
{ id: "child2", width: 80, height: 60 },
|
|
674
644
|
],
|
|
675
|
-
edges: [
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
}
|
|
679
|
-
]
|
|
645
|
+
edges: [{ id: "inner", sources: ["child1"], targets: ["child2"] }],
|
|
646
|
+
},
|
|
647
|
+
],
|
|
680
648
|
}
|
|
681
649
|
|
|
682
650
|
result = Elkrb.layout(graph)
|
|
683
|
-
|
|
684
|
-
|
|
651
|
+
|
|
652
|
+
parent = result.children.first
|
|
653
|
+
puts "parent: #{parent.width} x #{parent.height}"
|
|
654
|
+
parent.children.each { |child| puts "#{child.id}: (#{child.x}, #{child.y})" }
|
|
655
|
+
----
|
|
656
|
+
|
|
657
|
+
Prints:
|
|
658
|
+
|
|
659
|
+
[source]
|
|
660
|
+
----
|
|
661
|
+
parent: 204.0 x 84.0
|
|
662
|
+
child1: (12.0, 12.0)
|
|
663
|
+
child2: (112.0, 12.0)
|
|
685
664
|
----
|
|
686
665
|
|
|
687
666
|
=== Label placement
|
|
688
667
|
|
|
689
|
-
|
|
668
|
+
Labels on nodes, edges and ports are positioned by the layout:
|
|
690
669
|
|
|
691
670
|
[source,ruby]
|
|
692
671
|
----
|
|
672
|
+
require "elkrb"
|
|
673
|
+
|
|
693
674
|
graph = {
|
|
675
|
+
id: "root",
|
|
694
676
|
children: [
|
|
695
|
-
{
|
|
696
|
-
|
|
697
|
-
|
|
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
|
-
}
|
|
677
|
+
{ id: "n1", width: 100, height: 60,
|
|
678
|
+
labels: [{ text: "Node Label", width: 80, height: 20 }] },
|
|
679
|
+
{ id: "n2", width: 100, height: 60 },
|
|
716
680
|
],
|
|
717
681
|
edges: [
|
|
718
|
-
{
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
labels: [
|
|
722
|
-
{ text: "Edge Label", width: 60, height: 15 }
|
|
723
|
-
]
|
|
724
|
-
}
|
|
725
|
-
]
|
|
682
|
+
{ id: "e1", sources: ["n1"], targets: ["n2"],
|
|
683
|
+
labels: [{ text: "Edge Label", width: 60, height: 15 }] },
|
|
684
|
+
],
|
|
726
685
|
}
|
|
727
686
|
|
|
728
687
|
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
688
|
|
|
734
|
-
|
|
689
|
+
node_label = result.children.first.labels.first
|
|
690
|
+
edge_label = result.edges.first.labels.first
|
|
691
|
+
puts "#{node_label.text}: (#{node_label.x}, #{node_label.y})"
|
|
692
|
+
puts "#{edge_label.text}: (#{edge_label.x}, #{edge_label.y})"
|
|
693
|
+
----
|
|
735
694
|
|
|
736
|
-
|
|
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
|
|
695
|
+
Prints:
|
|
741
696
|
|
|
742
|
-
|
|
697
|
+
[source]
|
|
698
|
+
----
|
|
699
|
+
Node Label: (0.0, 0.0)
|
|
700
|
+
Edge Label: (92.0, 34.5)
|
|
701
|
+
----
|
|
743
702
|
|
|
744
|
-
===
|
|
703
|
+
=== Ports
|
|
745
704
|
|
|
746
|
-
|
|
705
|
+
A port has a `side` (`NORTH`, `SOUTH`, `EAST` or `WEST`) and an `index` that
|
|
706
|
+
orders it along that side. Edges name ports as their sources and targets.
|
|
747
707
|
|
|
748
708
|
[source,ruby]
|
|
749
709
|
----
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
)
|
|
755
|
-
|
|
710
|
+
require "elkrb"
|
|
711
|
+
|
|
712
|
+
graph = Elkrb::Graph::Graph.new(id: "root")
|
|
713
|
+
a = Elkrb::Graph::Node.new(id: "a", width: 100, height: 60)
|
|
714
|
+
a.ports = [Elkrb::Graph::Port.new(id: "a_out", side: "EAST", index: 0)]
|
|
715
|
+
b = Elkrb::Graph::Node.new(id: "b", width: 100, height: 60)
|
|
716
|
+
b.ports = [Elkrb::Graph::Port.new(id: "b_in", side: "WEST", index: 0)]
|
|
717
|
+
graph.children = [a, b]
|
|
718
|
+
graph.edges = [
|
|
719
|
+
Elkrb::Graph::Edge.new(id: "e1", sources: ["a_out"], targets: ["b_in"]),
|
|
720
|
+
]
|
|
756
721
|
|
|
757
|
-
|
|
722
|
+
result = Elkrb.layout(graph)
|
|
758
723
|
|
|
759
|
-
|
|
724
|
+
result.children.each do |node|
|
|
725
|
+
node.ports.each do |port|
|
|
726
|
+
puts "#{port.id}: side=#{port.side} at (#{port.x}, #{port.y})"
|
|
727
|
+
end
|
|
728
|
+
end
|
|
729
|
+
----
|
|
760
730
|
|
|
761
|
-
|
|
731
|
+
Prints:
|
|
762
732
|
|
|
763
|
-
[source
|
|
733
|
+
[source]
|
|
764
734
|
----
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
side: "WEST",
|
|
768
|
-
index: 0
|
|
769
|
-
)
|
|
735
|
+
a_out: side=EAST at (100.0, 30.0)
|
|
736
|
+
b_in: side=WEST at (0.0, 30.0)
|
|
770
737
|
----
|
|
771
738
|
|
|
772
|
-
|
|
739
|
+
See `examples/port_constraints_demo.rb` for more.
|
|
773
740
|
|
|
774
|
-
=== Self-
|
|
741
|
+
=== Self-loops
|
|
775
742
|
|
|
776
|
-
|
|
743
|
+
An edge whose source and target are the same node, or ports of the same node,
|
|
744
|
+
is a self-loop. It starts and ends on the node (or port) border and its route
|
|
745
|
+
runs outside the node:
|
|
777
746
|
|
|
778
747
|
[source,ruby]
|
|
779
748
|
----
|
|
780
|
-
|
|
781
|
-
id: "loop",
|
|
782
|
-
sources: ["n1"],
|
|
783
|
-
targets: ["n1"]
|
|
784
|
-
)
|
|
785
|
-
----
|
|
786
|
-
|
|
787
|
-
// See link:SELF_LOOP_GUIDE.md[Self-loop Guide] for details.
|
|
749
|
+
require "elkrb"
|
|
788
750
|
|
|
751
|
+
graph = {
|
|
752
|
+
id: "root",
|
|
753
|
+
children: [{ id: "n1", width: 100, height: 60 }],
|
|
754
|
+
edges: [{ id: "loop", sources: ["n1"], targets: ["n1"] }],
|
|
755
|
+
}
|
|
789
756
|
|
|
790
|
-
|
|
757
|
+
result = Elkrb.layout(graph)
|
|
791
758
|
|
|
792
|
-
|
|
793
|
-
|
|
759
|
+
section = result.edges.first.sections.first
|
|
760
|
+
puts "sections: #{result.edges.first.sections.size}"
|
|
761
|
+
puts "start: (#{section.start_point.x}, #{section.start_point.y})"
|
|
762
|
+
puts "bends: #{section.bend_points.map { |p| [p.x, p.y] }.inspect}"
|
|
763
|
+
puts "end: (#{section.end_point.x}, #{section.end_point.y})"
|
|
764
|
+
----
|
|
794
765
|
|
|
795
|
-
|
|
766
|
+
Prints:
|
|
796
767
|
|
|
797
|
-
[source
|
|
768
|
+
[source]
|
|
798
769
|
----
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
770
|
+
sections: 1
|
|
771
|
+
start: (112.0, 37.0)
|
|
772
|
+
bends: [[172.0, 37.0], [172.0, 47.0]]
|
|
773
|
+
end: (112.0, 47.0)
|
|
802
774
|
----
|
|
803
775
|
|
|
804
|
-
|
|
776
|
+
See `examples/self_loop_demo.rb` for more.
|
|
805
777
|
|
|
778
|
+
=== Graphviz DOT export
|
|
806
779
|
|
|
807
|
-
|
|
780
|
+
`Elkrb.export_dot` serializes a graph to DOT text. It does not need Graphviz;
|
|
781
|
+
only rendering the DOT to an image does (`elkrb render`).
|
|
808
782
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
comparisons with elkjs and Java ELK.
|
|
813
|
-
|
|
814
|
-
Quick overview of algorithm performance:
|
|
783
|
+
[source,ruby]
|
|
784
|
+
----
|
|
785
|
+
require "elkrb"
|
|
815
786
|
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
787
|
+
graph = {
|
|
788
|
+
id: "root",
|
|
789
|
+
children: [
|
|
790
|
+
{ id: "a", width: 60, height: 40 },
|
|
791
|
+
{ id: "b", width: 60, height: 40 },
|
|
792
|
+
],
|
|
793
|
+
edges: [{ id: "e1", sources: ["a"], targets: ["b"] }],
|
|
794
|
+
}
|
|
819
795
|
|
|
820
|
-
|
|
796
|
+
result = Elkrb.layout(graph)
|
|
797
|
+
puts Elkrb.export_dot(result, rankdir: "LR")
|
|
798
|
+
----
|
|
821
799
|
|
|
822
|
-
|
|
823
|
-
* Batch processing of large graphs
|
|
824
|
-
* Interactive diagram editors
|
|
800
|
+
Prints:
|
|
825
801
|
|
|
802
|
+
[source]
|
|
803
|
+
----
|
|
804
|
+
digraph G
|
|
805
|
+
{
|
|
806
|
+
rankdir=LR
|
|
807
|
+
size="2.2777777777777777,0.8888888888888888"
|
|
808
|
+
a [label="a", width=0.83, height=0.56, fixedsize=true, shape=box]
|
|
809
|
+
b [label="b", width=0.83, height=0.56, fixedsize=true, shape=box]
|
|
810
|
+
a -> b
|
|
811
|
+
}
|
|
812
|
+
----
|
|
826
813
|
|
|
827
814
|
== Migration from elkjs
|
|
828
815
|
|
|
829
|
-
|
|
830
|
-
link:docs/MIGRATION_FROM_ELKJS.adoc[Migration Guide] for a smooth transition.
|
|
816
|
+
The elkjs call `elk.layout(graph).then(...)` becomes `Elkrb.layout(graph)`:
|
|
831
817
|
|
|
832
|
-
|
|
818
|
+
* It returns an `Elkrb::Graph::Graph` synchronously, not a Promise of a plain
|
|
819
|
+
object. Read it with attribute readers (`result.children[0].x`), or call
|
|
820
|
+
`to_json` for the ELK JSON.
|
|
821
|
+
* The graph may be a Hash with ELK's camelCase keys (`layoutOptions`), the
|
|
822
|
+
same shape elkjs takes.
|
|
823
|
+
* Call-level options are the second argument.
|
|
824
|
+
* YAML round-tripping is available through `to_yaml` and `from_yaml`.
|
|
833
825
|
|
|
834
|
-
|
|
835
|
-
* Additional algorithms (TopdownPacking, Libavoid, VertiFlex)
|
|
836
|
-
* YAML serialization support
|
|
826
|
+
See <<compatibility-with-elk>> for where results can differ.
|
|
837
827
|
|
|
838
828
|
== Data Model
|
|
839
829
|
|
|
@@ -846,7 +836,9 @@ The root container for nodes and edges.
|
|
|
846
836
|
`width, height`:: Dimensions
|
|
847
837
|
`children`:: Array of Node objects
|
|
848
838
|
`edges`:: Array of Edge objects
|
|
849
|
-
`
|
|
839
|
+
`labels`:: Array of Label objects
|
|
840
|
+
`ports`:: Array of Port objects
|
|
841
|
+
`layout_options`:: Hash of ELK option ids to values
|
|
850
842
|
`properties`:: Custom properties hash
|
|
851
843
|
|
|
852
844
|
=== Node
|
|
@@ -855,12 +847,13 @@ Represents a graph node (box, shape, entity).
|
|
|
855
847
|
|
|
856
848
|
`id`:: Unique identifier
|
|
857
849
|
`x, y`:: Position coordinates
|
|
858
|
-
`width, height`::
|
|
850
|
+
`width, height`:: Dimensions
|
|
859
851
|
`labels`:: Array of Label objects
|
|
860
852
|
`ports`:: Array of Port objects
|
|
861
853
|
`children`:: Nested nodes (for hierarchical graphs)
|
|
862
854
|
`edges`:: Local edges
|
|
863
855
|
`layout_options`:: Node-specific layout options
|
|
856
|
+
`constraints`:: Placement constraints
|
|
864
857
|
`properties`:: Custom properties
|
|
865
858
|
|
|
866
859
|
=== Edge
|
|
@@ -874,6 +867,8 @@ Represents connections between nodes.
|
|
|
874
867
|
`sections`:: Computed routing (EdgeSection objects)
|
|
875
868
|
`layout_options`:: Edge-specific layout options
|
|
876
869
|
`properties`:: Custom properties
|
|
870
|
+
`junction_points`:: Junction points of the routed edge
|
|
871
|
+
`container`:: Id of the container the edge belongs to
|
|
877
872
|
|
|
878
873
|
=== Port
|
|
879
874
|
|
|
@@ -882,8 +877,12 @@ Explicit attachment points on node borders.
|
|
|
882
877
|
`id`:: Unique identifier
|
|
883
878
|
`x, y`:: Position relative to node
|
|
884
879
|
`width, height`:: Port dimensions
|
|
880
|
+
`side`:: `NORTH`, `SOUTH`, `EAST` or `WEST` (`nil` until set)
|
|
881
|
+
`index`:: Order along the side (`nil` until set or assigned by layout)
|
|
882
|
+
`offset`:: Port offset (`nil` until set)
|
|
885
883
|
`labels`:: Array of Label objects
|
|
886
884
|
`layout_options`:: Port-specific layout options
|
|
885
|
+
`properties`:: Custom properties
|
|
887
886
|
|
|
888
887
|
=== Label
|
|
889
888
|
|
|
@@ -893,37 +892,84 @@ Text labels for nodes, edges, or ports.
|
|
|
893
892
|
`text`:: Label text
|
|
894
893
|
`x, y`:: Position coordinates
|
|
895
894
|
`width, height`:: Label dimensions
|
|
895
|
+
`layout_options`:: Label-specific layout options
|
|
896
|
+
`properties`:: Custom properties
|
|
897
|
+
|
|
898
|
+
=== Layout options
|
|
899
|
+
|
|
900
|
+
`layout_options` is a Hash on every model (graph, node, edge, port, label).
|
|
901
|
+
Keys are ELK option ids; values are whatever that option takes. Symbol keys
|
|
902
|
+
are stringified on assignment, so `:"elk.direction"` and `"elk.direction"` are
|
|
903
|
+
equivalent.
|
|
904
|
+
|
|
905
|
+
[source,ruby]
|
|
906
|
+
----
|
|
907
|
+
require "elkrb"
|
|
896
908
|
|
|
897
|
-
|
|
909
|
+
graph = Elkrb::Graph::Graph.new(id: "root")
|
|
910
|
+
graph.layout_options = { "elk.direction": "DOWN", "elk.algorithm" => "layered" }
|
|
911
|
+
p graph.layout_options.keys
|
|
912
|
+
|
|
913
|
+
begin
|
|
914
|
+
graph.layout_options = { "text" => "a" }
|
|
915
|
+
rescue Elkrb::ValidationError => e
|
|
916
|
+
puts e.message
|
|
917
|
+
end
|
|
918
|
+
----
|
|
898
919
|
|
|
899
|
-
|
|
920
|
+
Prints:
|
|
900
921
|
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
`
|
|
905
|
-
|
|
906
|
-
`properties`:: Additional algorithm-specific options
|
|
922
|
+
[source]
|
|
923
|
+
----
|
|
924
|
+
["elk.direction", "elk.algorithm"]
|
|
925
|
+
layoutOptions cannot use `text` or `elements` as a key; both are reserved by lutaml-model
|
|
926
|
+
----
|
|
907
927
|
|
|
928
|
+
[[command-line-interface]]
|
|
908
929
|
== Command-Line Interface
|
|
909
930
|
|
|
910
|
-
The `elkrb` command
|
|
931
|
+
The `elkrb` command lays out, converts, validates and renders graph files.
|
|
932
|
+
`elkrb help` lists the commands; `elkrb help COMMAND` lists a command's flags.
|
|
933
|
+
Every command takes `--verbose`.
|
|
934
|
+
|
|
935
|
+
Input files are JSON, YAML or ELKT. The format is taken from the file
|
|
936
|
+
extension, and sniffed from the content when the extension is not one of
|
|
937
|
+
those.
|
|
938
|
+
|
|
939
|
+
=== layout
|
|
911
940
|
|
|
912
|
-
|
|
941
|
+
Lays out a graph and writes the result.
|
|
913
942
|
|
|
914
943
|
[source,sh]
|
|
915
944
|
----
|
|
916
|
-
|
|
917
|
-
$ elkrb layout input.
|
|
945
|
+
$ elkrb layout input.json # JSON to stdout
|
|
946
|
+
$ elkrb layout input.json --output result.json
|
|
947
|
+
$ elkrb layout input.json --format yaml --output result.yml
|
|
948
|
+
$ elkrb layout input.json --algorithm layered --direction DOWN --spacing 50
|
|
949
|
+
----
|
|
918
950
|
|
|
919
|
-
|
|
920
|
-
|
|
951
|
+
The output format is `--format` when supplied. Otherwise, a `.yml` or `.yaml`
|
|
952
|
+
`--output` extension selects YAML (case-insensitively); stdout and every other
|
|
953
|
+
extension use JSON. Flags that set a layout option write it onto the root graph,
|
|
954
|
+
where it outranks the same option in the file.
|
|
921
955
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
956
|
+
[cols="1,3",options="header"]
|
|
957
|
+
|===
|
|
958
|
+
| Flag | Meaning
|
|
959
|
+
|
|
960
|
+
| `--algorithm NAME` | Layout algorithm. Without it, the file's own `elk.algorithm`, else `layered`.
|
|
961
|
+
| `-o, --output FILE` | Output file (default: stdout).
|
|
962
|
+
| `--format json\|yaml` | Explicit output format; otherwise `.yml`/`.yaml` selects YAML and every other destination selects JSON.
|
|
963
|
+
| `--spacing N` | Node spacing (`elk.spacing.nodeNode`).
|
|
964
|
+
| `--layer-spacing N` | Layer spacing, for the layered algorithm.
|
|
965
|
+
| `--direction DIR` | `DOWN`, `RIGHT`, ...; applied by the layered and mrtree algorithms.
|
|
966
|
+
| `--edge-routing STYLE` | Edge routing strategy.
|
|
967
|
+
| `--padding-top N`, `--padding-bottom N`, `--padding-left N`, `--padding-right N` | Padding.
|
|
968
|
+
|===
|
|
925
969
|
|
|
926
|
-
===
|
|
970
|
+
=== algorithms
|
|
971
|
+
|
|
972
|
+
Lists the registered algorithms.
|
|
927
973
|
|
|
928
974
|
[source,sh]
|
|
929
975
|
----
|
|
@@ -935,43 +981,124 @@ Available Layout Algorithms:
|
|
|
935
981
|
Name: Box
|
|
936
982
|
Description: Arranges nodes in a grid pattern
|
|
937
983
|
|
|
984
|
+
disco
|
|
985
|
+
Name: DISCO
|
|
986
|
+
Description: Layout for disconnected graph components
|
|
987
|
+
|
|
938
988
|
fixed
|
|
939
989
|
Name: Fixed
|
|
940
990
|
Description: Keeps nodes at their current positions
|
|
941
991
|
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
992
|
+
...
|
|
993
|
+
----
|
|
994
|
+
|
|
995
|
+
`...` stands for the remaining algorithms. Categories other than `general`,
|
|
996
|
+
and hierarchy support, are listed per algorithm.
|
|
946
997
|
|
|
947
|
-
|
|
948
|
-
Name: Layered (Sugiyama)
|
|
949
|
-
Description: Hierarchical layout using the Sugiyama framework
|
|
950
|
-
Category: hierarchical
|
|
951
|
-
Supports Hierarchy: Yes
|
|
998
|
+
=== options
|
|
952
999
|
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
1000
|
+
Lists every known layout option and its implementation status. Pass an
|
|
1001
|
+
algorithm name to list only the options that algorithm supports; `--json`
|
|
1002
|
+
writes the same information as JSON.
|
|
956
1003
|
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1004
|
+
[source,sh]
|
|
1005
|
+
----
|
|
1006
|
+
$ elkrb options
|
|
1007
|
+
$ elkrb options layered --json
|
|
961
1008
|
----
|
|
962
1009
|
|
|
1010
|
+
=== diagram
|
|
963
1011
|
|
|
964
|
-
|
|
1012
|
+
Lays out a graph and writes a diagram in one step. The output format comes
|
|
1013
|
+
from the `--output` extension: `.json`, `.yml`/`.yaml`, `.dot`/`.gv`, `.elkt`,
|
|
1014
|
+
or the image formats `.png`, `.svg`, `.pdf`, `.ps` and `.eps`. Image formats
|
|
1015
|
+
need Graphviz.
|
|
1016
|
+
|
|
1017
|
+
[source,sh]
|
|
1018
|
+
----
|
|
1019
|
+
$ elkrb diagram input.json --output diagram.svg
|
|
1020
|
+
✓ Diagram created: diagram.svg
|
|
1021
|
+
----
|
|
1022
|
+
|
|
1023
|
+
Flags: `--output FILE` (required), `--algorithm`, `--direction`, `--spacing`,
|
|
1024
|
+
`--edge-routing` (as for `layout`), `--format` (used when the extension is not
|
|
1025
|
+
recognised) and `--preview` (open the result in the default viewer).
|
|
965
1026
|
|
|
966
|
-
|
|
1027
|
+
=== convert
|
|
967
1028
|
|
|
968
|
-
|
|
969
|
-
elkjs and Java ELK (TODO).
|
|
1029
|
+
Converts a graph file between JSON, YAML, DOT and ELKT, without laying it out.
|
|
970
1030
|
|
|
1031
|
+
[source,sh]
|
|
1032
|
+
----
|
|
1033
|
+
$ elkrb convert input.json --output input.dot
|
|
1034
|
+
✓ Converted input.json → input.dot (dot)
|
|
1035
|
+
----
|
|
1036
|
+
|
|
1037
|
+
Flags: `--output FILE` (required) and `--format`. The output extension picks
|
|
1038
|
+
the format (`.json`, `.yml`/`.yaml`, `.dot`/`.gv`, `.elkt`).
|
|
1039
|
+
|
|
1040
|
+
=== render
|
|
1041
|
+
|
|
1042
|
+
Renders a DOT file to an image with Graphviz.
|
|
1043
|
+
|
|
1044
|
+
[source,sh]
|
|
1045
|
+
----
|
|
1046
|
+
$ elkrb render input.dot --output input.png
|
|
1047
|
+
✓ Rendered input.dot → input.png
|
|
1048
|
+
----
|
|
1049
|
+
|
|
1050
|
+
Flags: `--output FILE` (required), `--engine` (Graphviz engine such as `dot`,
|
|
1051
|
+
`neato`, `fdp`; default `dot`) and `--dpi` (default 96). It fails when
|
|
1052
|
+
Graphviz is not installed.
|
|
1053
|
+
|
|
1054
|
+
=== validate
|
|
1055
|
+
|
|
1056
|
+
Checks the structure of a graph file.
|
|
1057
|
+
|
|
1058
|
+
[source,sh]
|
|
1059
|
+
----
|
|
1060
|
+
$ elkrb validate input.json
|
|
1061
|
+
✅ input.json is valid
|
|
1062
|
+
----
|
|
1063
|
+
|
|
1064
|
+
`--strict` also requires every node to have a positive `width` and `height`,
|
|
1065
|
+
and `layoutOptions` to be a Hash.
|
|
1066
|
+
|
|
1067
|
+
=== batch
|
|
1068
|
+
|
|
1069
|
+
Lays out every `.json`, `.yml`, `.yaml` and `.elkt` file in a directory.
|
|
971
1070
|
|
|
972
1071
|
[source,sh]
|
|
973
1072
|
----
|
|
974
|
-
|
|
1073
|
+
$ elkrb batch graphs/ --output-dir out/
|
|
1074
|
+
✓ Diagram created: out/a.svg
|
|
1075
|
+
✓ Diagram created: out/b.svg
|
|
1076
|
+
|
|
1077
|
+
✓ Processed 2 file(s) → out/
|
|
1078
|
+
----
|
|
1079
|
+
|
|
1080
|
+
Flags: `--output-dir DIR` (required), `--format` (default `svg`),
|
|
1081
|
+
`--algorithm`, `--direction` and `--edge-routing`. The command fails when any
|
|
1082
|
+
file fails.
|
|
1083
|
+
|
|
1084
|
+
=== version
|
|
1085
|
+
|
|
1086
|
+
[source,sh]
|
|
1087
|
+
----
|
|
1088
|
+
$ elkrb version
|
|
1089
|
+
elkrb version 2.0.0
|
|
1090
|
+
----
|
|
1091
|
+
|
|
1092
|
+
== Benchmarking
|
|
1093
|
+
|
|
1094
|
+
`rake` tasks time each algorithm on four generated graphs (10 to 200 nodes) and
|
|
1095
|
+
write the results to `benchmarks/results/`. ElkRb does not publish performance
|
|
1096
|
+
figures in this README: the committed results are a snapshot from an earlier
|
|
1097
|
+
release, so measure on your own hardware.
|
|
1098
|
+
|
|
1099
|
+
[source,sh]
|
|
1100
|
+
----
|
|
1101
|
+
# Run all ElkRb benchmarks and generate the report
|
|
975
1102
|
$ rake benchmark:all
|
|
976
1103
|
|
|
977
1104
|
# Generate test graphs
|
|
@@ -983,10 +1110,11 @@ $ rake benchmark:elkrb
|
|
|
983
1110
|
# Run elkjs benchmarks (requires Node.js and elkjs)
|
|
984
1111
|
$ rake benchmark:elkjs
|
|
985
1112
|
|
|
986
|
-
# Generate
|
|
1113
|
+
# Generate the report (writes docs/PERFORMANCE.adoc)
|
|
987
1114
|
$ rake benchmark:report
|
|
988
1115
|
----
|
|
989
1116
|
|
|
1117
|
+
See `benchmarks/README.md` for the method.
|
|
990
1118
|
|
|
991
1119
|
== Development
|
|
992
1120
|
|
|
@@ -1005,6 +1133,16 @@ $ bundle exec rubocop
|
|
|
1005
1133
|
$ bundle exec rubocop -A # Auto-correct
|
|
1006
1134
|
----
|
|
1007
1135
|
|
|
1136
|
+
=== Examples
|
|
1137
|
+
|
|
1138
|
+
`examples/*.rb` are runnable scripts, and `spec/examples_spec.rb` runs every
|
|
1139
|
+
one of them.
|
|
1140
|
+
|
|
1141
|
+
[source,sh]
|
|
1142
|
+
----
|
|
1143
|
+
$ bundle exec ruby examples/simple_graph.rb
|
|
1144
|
+
----
|
|
1145
|
+
|
|
1008
1146
|
== References
|
|
1009
1147
|
|
|
1010
1148
|
Other libraries that implement ELK:
|