elkrb 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +11 -0
- data/Gemfile +13 -0
- data/README.adoc +1028 -0
- data/Rakefile +64 -0
- data/benchmarks/README.md +172 -0
- data/benchmarks/elkjs_benchmark.js +140 -0
- data/benchmarks/elkrb_benchmark.rb +145 -0
- data/benchmarks/fixtures/graphs.json +10777 -0
- data/benchmarks/generate_report.rb +241 -0
- data/benchmarks/generate_test_graphs.rb +154 -0
- data/benchmarks/results/elkrb_results.json +280 -0
- data/benchmarks/results/elkrb_summary.json +285 -0
- data/elkrb.gemspec +39 -0
- data/examples/dot_export_demo.rb +133 -0
- data/examples/hierarchical_graph.rb +19 -0
- data/examples/layout_constraints_demo.rb +272 -0
- data/examples/port_constraints_demo.rb +291 -0
- data/examples/self_loop_demo.rb +391 -0
- data/examples/simple_graph.rb +50 -0
- data/examples/spline_routing_demo.rb +235 -0
- data/exe/elkrb +8 -0
- data/lib/elkrb/cli.rb +224 -0
- data/lib/elkrb/commands/batch_command.rb +66 -0
- data/lib/elkrb/commands/convert_command.rb +130 -0
- data/lib/elkrb/commands/diagram_command.rb +208 -0
- data/lib/elkrb/commands/render_command.rb +52 -0
- data/lib/elkrb/commands/validate_command.rb +241 -0
- data/lib/elkrb/errors.rb +30 -0
- data/lib/elkrb/geometry/bezier.rb +163 -0
- data/lib/elkrb/geometry/dimension.rb +32 -0
- data/lib/elkrb/geometry/point.rb +68 -0
- data/lib/elkrb/geometry/rectangle.rb +86 -0
- data/lib/elkrb/geometry/vector.rb +67 -0
- data/lib/elkrb/graph/edge.rb +95 -0
- data/lib/elkrb/graph/graph.rb +90 -0
- data/lib/elkrb/graph/label.rb +45 -0
- data/lib/elkrb/graph/layout_options.rb +247 -0
- data/lib/elkrb/graph/node.rb +79 -0
- data/lib/elkrb/graph/node_constraints.rb +107 -0
- data/lib/elkrb/graph/port.rb +104 -0
- data/lib/elkrb/graphviz_wrapper.rb +133 -0
- data/lib/elkrb/layout/algorithm_registry.rb +57 -0
- data/lib/elkrb/layout/algorithms/base_algorithm.rb +208 -0
- data/lib/elkrb/layout/algorithms/box.rb +47 -0
- data/lib/elkrb/layout/algorithms/disco.rb +206 -0
- data/lib/elkrb/layout/algorithms/fixed.rb +32 -0
- data/lib/elkrb/layout/algorithms/force.rb +165 -0
- data/lib/elkrb/layout/algorithms/layered/cycle_breaker.rb +86 -0
- data/lib/elkrb/layout/algorithms/layered/layer_assigner.rb +96 -0
- data/lib/elkrb/layout/algorithms/layered/node_placer.rb +77 -0
- data/lib/elkrb/layout/algorithms/layered.rb +49 -0
- data/lib/elkrb/layout/algorithms/libavoid.rb +389 -0
- data/lib/elkrb/layout/algorithms/mrtree.rb +144 -0
- data/lib/elkrb/layout/algorithms/radial.rb +64 -0
- data/lib/elkrb/layout/algorithms/random.rb +43 -0
- data/lib/elkrb/layout/algorithms/rectpacking.rb +93 -0
- data/lib/elkrb/layout/algorithms/spore_compaction.rb +139 -0
- data/lib/elkrb/layout/algorithms/spore_overlap.rb +117 -0
- data/lib/elkrb/layout/algorithms/stress.rb +176 -0
- data/lib/elkrb/layout/algorithms/topdown_packing.rb +183 -0
- data/lib/elkrb/layout/algorithms/vertiflex.rb +174 -0
- data/lib/elkrb/layout/constraints/alignment_constraint.rb +150 -0
- data/lib/elkrb/layout/constraints/base_constraint.rb +72 -0
- data/lib/elkrb/layout/constraints/constraint_processor.rb +134 -0
- data/lib/elkrb/layout/constraints/fixed_position_constraint.rb +87 -0
- data/lib/elkrb/layout/constraints/layer_constraint.rb +71 -0
- data/lib/elkrb/layout/constraints/relative_position_constraint.rb +110 -0
- data/lib/elkrb/layout/edge_router.rb +935 -0
- data/lib/elkrb/layout/hierarchical_processor.rb +299 -0
- data/lib/elkrb/layout/label_placer.rb +338 -0
- data/lib/elkrb/layout/layout_engine.rb +170 -0
- data/lib/elkrb/layout/port_constraint_processor.rb +173 -0
- data/lib/elkrb/options/elk_padding.rb +94 -0
- data/lib/elkrb/options/k_vector.rb +100 -0
- data/lib/elkrb/options/k_vector_chain.rb +135 -0
- data/lib/elkrb/parsers/elkt_parser.rb +248 -0
- data/lib/elkrb/serializers/dot_serializer.rb +339 -0
- data/lib/elkrb/serializers/elkt_serializer.rb +236 -0
- data/lib/elkrb/version.rb +5 -0
- data/lib/elkrb.rb +509 -0
- data/sig/elkrb/constraints.rbs +114 -0
- data/sig/elkrb/geometry.rbs +61 -0
- data/sig/elkrb/graph.rbs +112 -0
- data/sig/elkrb/layout.rbs +107 -0
- data/sig/elkrb/options.rbs +81 -0
- data/sig/elkrb.rbs +32 -0
- metadata +179 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Elkrb
|
|
6
|
+
module Serializers
|
|
7
|
+
# Serializer for ELKT (ELK Text) format
|
|
8
|
+
# Converts ELK graph structures to textual ELKT representation
|
|
9
|
+
class ElktSerializer
|
|
10
|
+
def initialize(options = {})
|
|
11
|
+
@indent_size = options[:indent_size] || 2
|
|
12
|
+
@include_comments = options.fetch(:include_comments, true)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def serialize(graph, _options = {})
|
|
16
|
+
@indent_level = 0
|
|
17
|
+
@output = []
|
|
18
|
+
|
|
19
|
+
# Convert to hash using JSON round-trip for Lutaml models
|
|
20
|
+
@graph_hash = if graph.is_a?(Hash)
|
|
21
|
+
graph
|
|
22
|
+
elsif graph.respond_to?(:to_json)
|
|
23
|
+
JSON.parse(graph.to_json, symbolize_names: true)
|
|
24
|
+
else
|
|
25
|
+
graph
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
serialize_graph(@graph_hash)
|
|
29
|
+
|
|
30
|
+
"#{@output.join("\n")}\n"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def serialize_graph(graph)
|
|
36
|
+
# Serialize graph-level layout options
|
|
37
|
+
layout_opts = graph[:layoutOptions] || graph["layoutOptions"] || {}
|
|
38
|
+
serialize_layout_options(layout_opts)
|
|
39
|
+
|
|
40
|
+
# Add blank line after options if present
|
|
41
|
+
@output << "" if (graph[:layoutOptions] || {}).any?
|
|
42
|
+
|
|
43
|
+
# Serialize nodes
|
|
44
|
+
(graph[:children] || []).each do |node|
|
|
45
|
+
serialize_node(node)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Add blank line before edges if both nodes and edges exist
|
|
49
|
+
if (graph[:children] || []).any? && (graph[:edges] || []).any?
|
|
50
|
+
@output << ""
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Serialize edges
|
|
54
|
+
(graph[:edges] || []).each do |edge|
|
|
55
|
+
serialize_edge(edge)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def serialize_layout_options(options)
|
|
60
|
+
return if options.empty?
|
|
61
|
+
|
|
62
|
+
options.each do |key, value|
|
|
63
|
+
# Remove elk. prefix for cleaner output
|
|
64
|
+
display_key = key.to_s.start_with?("elk.") ? key.to_s[4..] : key.to_s
|
|
65
|
+
|
|
66
|
+
# Special handling for algorithm and direction
|
|
67
|
+
@output << if display_key == "algorithm"
|
|
68
|
+
"algorithm: #{value}"
|
|
69
|
+
elsif display_key == "direction"
|
|
70
|
+
"direction: #{value}"
|
|
71
|
+
else
|
|
72
|
+
"#{display_key}: #{format_value(value)}"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def serialize_node(node)
|
|
78
|
+
indent = " " * (@indent_level * @indent_size)
|
|
79
|
+
|
|
80
|
+
# Check if node has attributes to serialize in a block
|
|
81
|
+
has_block = node_has_block?(node)
|
|
82
|
+
|
|
83
|
+
if has_block
|
|
84
|
+
@output << "#{indent}node #{node[:id]} {"
|
|
85
|
+
@indent_level += 1
|
|
86
|
+
|
|
87
|
+
serialize_node_block(node)
|
|
88
|
+
|
|
89
|
+
@indent_level -= 1
|
|
90
|
+
@output << "#{indent}}"
|
|
91
|
+
else
|
|
92
|
+
@output << "#{indent}node #{node[:id]}"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def node_has_block?(node)
|
|
97
|
+
# Node needs a block if it has:
|
|
98
|
+
# - Layout attributes (size, position)
|
|
99
|
+
# - Labels
|
|
100
|
+
# - Ports
|
|
101
|
+
# - Children (nested nodes)
|
|
102
|
+
# - Non-default dimensions
|
|
103
|
+
|
|
104
|
+
has_layout = node[:width] && node[:height] &&
|
|
105
|
+
(node[:width] != 40 || node[:height] != 40)
|
|
106
|
+
has_position = node[:x] || node[:y]
|
|
107
|
+
has_labels = (node[:labels] || []).any?
|
|
108
|
+
has_ports = (node[:ports] || []).any?
|
|
109
|
+
has_children = (node[:children] || []).any?
|
|
110
|
+
has_edges = (node[:edges] || []).any?
|
|
111
|
+
|
|
112
|
+
has_layout || has_position || has_labels || has_ports ||
|
|
113
|
+
has_children || has_edges
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def serialize_node_block(node)
|
|
117
|
+
indent = " " * (@indent_level * @indent_size)
|
|
118
|
+
|
|
119
|
+
# Serialize layout attributes
|
|
120
|
+
if node[:width] && node[:height]
|
|
121
|
+
width = format_number(node[:width])
|
|
122
|
+
height = format_number(node[:height])
|
|
123
|
+
@output << "#{indent}layout [ size: #{width}, #{height} ]"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
if node[:x] && node[:y]
|
|
127
|
+
x = format_number(node[:x])
|
|
128
|
+
y = format_number(node[:y])
|
|
129
|
+
@output << "#{indent}layout [ position: #{x}, #{y} ]"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Serialize labels
|
|
133
|
+
(node[:labels] || []).each do |label|
|
|
134
|
+
@output << "#{indent}label \"#{label[:text]}\""
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Serialize ports
|
|
138
|
+
(node[:ports] || []).each do |port|
|
|
139
|
+
serialize_port(port)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Serialize nested nodes
|
|
143
|
+
(node[:children] || []).each do |child|
|
|
144
|
+
serialize_node(child)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Serialize nested edges
|
|
148
|
+
(node[:edges] || []).each do |edge|
|
|
149
|
+
serialize_edge(edge)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def serialize_port(port)
|
|
154
|
+
indent = " " * (@indent_level * @indent_size)
|
|
155
|
+
|
|
156
|
+
if port_has_block?(port)
|
|
157
|
+
@output << "#{indent}port #{port[:id]} {"
|
|
158
|
+
@indent_level += 1
|
|
159
|
+
|
|
160
|
+
serialize_port_block(port)
|
|
161
|
+
|
|
162
|
+
@indent_level -= 1
|
|
163
|
+
@output << "#{indent}}"
|
|
164
|
+
else
|
|
165
|
+
@output << "#{indent}port #{port[:id]}"
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def port_has_block?(port)
|
|
170
|
+
(port[:layoutOptions] || {}).any? ||
|
|
171
|
+
(port[:labels] || []).any?
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def serialize_port_block(port)
|
|
175
|
+
indent = " " * (@indent_level * @indent_size)
|
|
176
|
+
|
|
177
|
+
# Serialize port layout options
|
|
178
|
+
(port[:layoutOptions] || {}).each do |key, value|
|
|
179
|
+
display_key = key.to_s.start_with?("elk.") ? key.to_s[4..] : key.to_s
|
|
180
|
+
@output << "#{indent}#{display_key}: #{format_value(value)}"
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Serialize port labels
|
|
184
|
+
(port[:labels] || []).each do |label|
|
|
185
|
+
@output << "#{indent}label \"#{label[:text]}\""
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def serialize_edge(edge)
|
|
190
|
+
indent = " " * (@indent_level * @indent_size)
|
|
191
|
+
|
|
192
|
+
source = edge[:sources]&.first || edge[:source]
|
|
193
|
+
target = edge[:targets]&.first || edge[:target]
|
|
194
|
+
|
|
195
|
+
# Add port references if present
|
|
196
|
+
source_ref = if edge[:sourcePort]
|
|
197
|
+
"#{source}.#{edge[:sourcePort]}"
|
|
198
|
+
else
|
|
199
|
+
source
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
target_ref = if edge[:targetPort]
|
|
203
|
+
"#{target}.#{edge[:targetPort]}"
|
|
204
|
+
else
|
|
205
|
+
target
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Include edge ID if it's not auto-generated
|
|
209
|
+
@output << if edge[:id] && !edge[:id].to_s.match?(/^e\d+$/)
|
|
210
|
+
"#{indent}edge #{edge[:id]}: #{source_ref} -> #{target_ref}"
|
|
211
|
+
else
|
|
212
|
+
"#{indent}edge #{source_ref} -> #{target_ref}"
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def format_value(value)
|
|
217
|
+
case value
|
|
218
|
+
when Float
|
|
219
|
+
format_number(value)
|
|
220
|
+
when Integer
|
|
221
|
+
value
|
|
222
|
+
when TrueClass, FalseClass
|
|
223
|
+
value
|
|
224
|
+
else
|
|
225
|
+
value.to_s
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def format_number(num)
|
|
230
|
+
# Remove trailing zeros and decimal point if integer
|
|
231
|
+
formatted = format("%.2f", num).sub(/\.?0+$/, "")
|
|
232
|
+
formatted.empty? ? "0" : formatted
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|