elkrb 1.0.2 → 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
|
@@ -1,299 +1,42 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "algorithm_registry"
|
|
4
|
+
require_relative "../options/resolver"
|
|
5
|
+
|
|
3
6
|
module Elkrb
|
|
4
7
|
module Layout
|
|
5
|
-
#
|
|
6
|
-
# Handles recursive layout, parent-child constraints, and cross-hierarchy
|
|
7
|
-
# edges.
|
|
8
|
+
# Sizes compound nodes bottom-up, one graph level per algorithm instance.
|
|
8
9
|
module HierarchicalProcessor
|
|
9
|
-
# Layout
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
# @param options [Hash] Layout options
|
|
13
|
-
# @return [Graph::Graph] The laid out graph
|
|
14
|
-
def layout_hierarchical(graph, options = {})
|
|
15
|
-
return layout_flat(graph, options) unless graph.hierarchical?
|
|
16
|
-
|
|
17
|
-
# First, recursively layout all child nodes
|
|
18
|
-
layout_children_recursively(graph)
|
|
19
|
-
|
|
20
|
-
# Then layout the top-level graph
|
|
21
|
-
layout_flat(graph, options)
|
|
22
|
-
|
|
23
|
-
# Apply parent constraints
|
|
24
|
-
apply_parent_constraints(graph)
|
|
25
|
-
|
|
26
|
-
# Handle cross-hierarchy edges
|
|
27
|
-
handle_cross_hierarchy_edges(graph)
|
|
28
|
-
|
|
29
|
-
# Update parent bounds
|
|
30
|
-
update_parent_bounds(graph)
|
|
31
|
-
|
|
32
|
-
graph
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
37
|
-
# Layout all children of nodes in the graph recursively.
|
|
38
|
-
def layout_children_recursively(graph)
|
|
39
|
-
return unless graph.children
|
|
40
|
-
|
|
10
|
+
# Layout each compound as its own graph before its parent level.
|
|
11
|
+
def size_compound_children(graph)
|
|
12
|
+
child_resolver = Options::Resolver.new
|
|
41
13
|
graph.children.each do |node|
|
|
42
14
|
next unless node.hierarchical?
|
|
43
15
|
|
|
44
|
-
# Create a temporary graph for the node's children
|
|
45
16
|
child_graph = create_child_graph(node)
|
|
17
|
+
pin = child_resolver.get("elk.algorithm", child_graph, default: nil)
|
|
18
|
+
algorithm = pin ? AlgorithmRegistry.get(pin) : self.class
|
|
19
|
+
raise AlgorithmNotFoundError.new(pin) unless algorithm
|
|
46
20
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
# Apply the layout back to the node
|
|
51
|
-
apply_child_layout(node, child_graph)
|
|
21
|
+
algorithm.new(@options).layout(child_graph)
|
|
22
|
+
node.width = child_graph.width
|
|
23
|
+
node.height = child_graph.height
|
|
52
24
|
end
|
|
53
25
|
end
|
|
54
26
|
|
|
55
|
-
|
|
56
|
-
def layout_flat(graph, options = {})
|
|
57
|
-
# This should be implemented by the including algorithm class
|
|
58
|
-
raise NotImplementedError,
|
|
59
|
-
"#{self.class.name} must implement #layout_flat"
|
|
60
|
-
end
|
|
27
|
+
private
|
|
61
28
|
|
|
62
|
-
#
|
|
29
|
+
# Build the graph view shared by compound sizing and nested edge routing.
|
|
63
30
|
def create_child_graph(node)
|
|
31
|
+
(node.edges || []).each { |edge| edge.container = node.id }
|
|
64
32
|
Graph::Graph.new(
|
|
65
33
|
id: "#{node.id}_children",
|
|
66
|
-
children: node.children
|
|
67
|
-
edges: node.edges
|
|
34
|
+
children: node.children,
|
|
35
|
+
edges: node.edges,
|
|
68
36
|
layout_options: node.layout_options,
|
|
37
|
+
properties: node.properties,
|
|
69
38
|
)
|
|
70
39
|
end
|
|
71
|
-
|
|
72
|
-
# Extract layout options from a node.
|
|
73
|
-
def extract_node_options(node)
|
|
74
|
-
return {} unless node.layout_options
|
|
75
|
-
|
|
76
|
-
node.layout_options.properties || {}
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Apply the child graph layout back to the parent node.
|
|
80
|
-
def apply_child_layout(node, child_graph)
|
|
81
|
-
# Copy positions from child_graph children to node children
|
|
82
|
-
return unless child_graph.children
|
|
83
|
-
|
|
84
|
-
child_graph.children.each_with_index do |child, index|
|
|
85
|
-
if node.children && node.children[index]
|
|
86
|
-
node.children[index].x = child.x
|
|
87
|
-
node.children[index].y = child.y
|
|
88
|
-
node.children[index].width = child.width
|
|
89
|
-
node.children[index].height = child.height
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
# Apply parent constraints to ensure children fit within parent bounds.
|
|
95
|
-
def apply_parent_constraints(graph)
|
|
96
|
-
return unless graph.children
|
|
97
|
-
|
|
98
|
-
graph.children.each do |node|
|
|
99
|
-
next unless node.hierarchical?
|
|
100
|
-
|
|
101
|
-
# Get padding from node options
|
|
102
|
-
padding = get_padding(node)
|
|
103
|
-
|
|
104
|
-
# Adjust child positions to account for padding
|
|
105
|
-
adjust_children_for_padding(node, padding)
|
|
106
|
-
|
|
107
|
-
# Recursively apply to nested children
|
|
108
|
-
if node.children
|
|
109
|
-
temp_graph = Graph::Graph.new(children: node.children)
|
|
110
|
-
apply_parent_constraints(temp_graph)
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
# Get padding for a node from its layout options.
|
|
116
|
-
def get_padding(node)
|
|
117
|
-
return default_padding unless node.layout_options
|
|
118
|
-
|
|
119
|
-
padding_option = node.layout_options.properties&.[]("padding") ||
|
|
120
|
-
node.layout_options.properties&.[]("elk.padding")
|
|
121
|
-
|
|
122
|
-
return default_padding unless padding_option
|
|
123
|
-
|
|
124
|
-
parse_padding(padding_option)
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
# Default padding values.
|
|
128
|
-
def default_padding
|
|
129
|
-
{ top: 12.0, right: 12.0, bottom: 12.0, left: 12.0 }
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
# Parse padding from various formats.
|
|
133
|
-
def parse_padding(padding)
|
|
134
|
-
case padding
|
|
135
|
-
when Hash
|
|
136
|
-
{
|
|
137
|
-
top: padding[:top] || padding["top"] || 12.0,
|
|
138
|
-
right: padding[:right] || padding["right"] || 12.0,
|
|
139
|
-
bottom: padding[:bottom] || padding["bottom"] || 12.0,
|
|
140
|
-
left: padding[:left] || padding["left"] || 12.0,
|
|
141
|
-
}
|
|
142
|
-
when Numeric
|
|
143
|
-
{ top: padding, right: padding, bottom: padding, left: padding }
|
|
144
|
-
else
|
|
145
|
-
default_padding
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
# Adjust children positions to account for parent padding.
|
|
150
|
-
def adjust_children_for_padding(node, padding)
|
|
151
|
-
return unless node.children
|
|
152
|
-
|
|
153
|
-
node.children.each do |child|
|
|
154
|
-
child.x = (child.x || 0.0) + padding[:left]
|
|
155
|
-
child.y = (child.y || 0.0) + padding[:top]
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
# Handle edges that cross hierarchy boundaries.
|
|
160
|
-
def handle_cross_hierarchy_edges(graph)
|
|
161
|
-
return unless graph.edges
|
|
162
|
-
|
|
163
|
-
graph.edges.each do |edge|
|
|
164
|
-
# Get source and target IDs from edge
|
|
165
|
-
source_id = edge.sources&.first
|
|
166
|
-
target_id = edge.targets&.first
|
|
167
|
-
|
|
168
|
-
next unless source_id && target_id
|
|
169
|
-
|
|
170
|
-
source_node = find_node_in_hierarchy(graph, source_id)
|
|
171
|
-
target_node = find_node_in_hierarchy(graph, target_id)
|
|
172
|
-
|
|
173
|
-
next unless source_node && target_node
|
|
174
|
-
|
|
175
|
-
# Check if edge crosses hierarchy levels
|
|
176
|
-
if crosses_hierarchy?(source_node, target_node, graph)
|
|
177
|
-
adjust_edge_for_hierarchy(edge, source_node, target_node)
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
# Find a node anywhere in the graph hierarchy.
|
|
183
|
-
def find_node_in_hierarchy(graph, node_id)
|
|
184
|
-
return nil unless node_id
|
|
185
|
-
|
|
186
|
-
# Handle both direct node IDs and port IDs
|
|
187
|
-
node_id_str = node_id.is_a?(String) ? node_id : node_id.to_s
|
|
188
|
-
graph.find_node(node_id_str)
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
# Check if an edge crosses hierarchy boundaries.
|
|
192
|
-
def crosses_hierarchy?(source_node, target_node, graph)
|
|
193
|
-
return false unless source_node && target_node
|
|
194
|
-
|
|
195
|
-
source_depth = node_depth(source_node, graph)
|
|
196
|
-
target_depth = node_depth(target_node, graph)
|
|
197
|
-
|
|
198
|
-
source_depth != target_depth
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
# Calculate the depth of a node in the hierarchy.
|
|
202
|
-
def node_depth(node, graph, depth = 0)
|
|
203
|
-
return depth if graph.children&.include?(node)
|
|
204
|
-
|
|
205
|
-
graph.children&.each do |child|
|
|
206
|
-
if child.hierarchical? && child.children
|
|
207
|
-
child_graph = Graph::Graph.new(children: child.children)
|
|
208
|
-
found_depth = node_depth(node, child_graph, depth + 1)
|
|
209
|
-
return found_depth if found_depth > depth
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
depth
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
# Adjust edge routing for cross-hierarchy edges.
|
|
217
|
-
def adjust_edge_for_hierarchy(edge, _source_node, _target_node)
|
|
218
|
-
# Add additional bend points to route around parent boundaries
|
|
219
|
-
# This is a simplified version - could be enhanced with proper routing
|
|
220
|
-
return unless edge.sections && !edge.sections.empty?
|
|
221
|
-
|
|
222
|
-
section = edge.sections.first
|
|
223
|
-
|
|
224
|
-
# Calculate midpoint
|
|
225
|
-
mid_x = (section.start_point.x + section.end_point.x) / 2.0
|
|
226
|
-
mid_y = (section.start_point.y + section.end_point.y) / 2.0
|
|
227
|
-
|
|
228
|
-
# Add a bend point at the midpoint
|
|
229
|
-
section.add_bend_point(mid_x, mid_y)
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
# Update parent node bounds to contain all children.
|
|
233
|
-
def update_parent_bounds(graph)
|
|
234
|
-
return unless graph.children
|
|
235
|
-
|
|
236
|
-
graph.children.each do |node|
|
|
237
|
-
next unless node.hierarchical?
|
|
238
|
-
|
|
239
|
-
# Recursively update nested children first
|
|
240
|
-
if node.children
|
|
241
|
-
temp_graph = Graph::Graph.new(children: node.children)
|
|
242
|
-
update_parent_bounds(temp_graph)
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
# Calculate bounds from children
|
|
246
|
-
bounds = calculate_children_bounds(node)
|
|
247
|
-
|
|
248
|
-
# Get padding
|
|
249
|
-
padding = get_padding(node)
|
|
250
|
-
|
|
251
|
-
# Update node dimensions
|
|
252
|
-
node.width = bounds[:width] + padding[:left] + padding[:right]
|
|
253
|
-
node.height = bounds[:height] + padding[:top] + padding[:bottom]
|
|
254
|
-
|
|
255
|
-
# Update position if needed (ensure children are at positive coords)
|
|
256
|
-
if bounds[:min_x].negative?
|
|
257
|
-
offset_x = -bounds[:min_x] + padding[:left]
|
|
258
|
-
node.children&.each { |child| child.x = (child.x || 0) + offset_x }
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
if bounds[:min_y].negative?
|
|
262
|
-
offset_y = -bounds[:min_y] + padding[:top]
|
|
263
|
-
node.children&.each { |child| child.y = (child.y || 0) + offset_y }
|
|
264
|
-
end
|
|
265
|
-
end
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
# Calculate the bounding box of a node's children.
|
|
269
|
-
def calculate_children_bounds(node)
|
|
270
|
-
return { min_x: 0, min_y: 0, width: 0, height: 0 } unless
|
|
271
|
-
node.children && !node.children.empty?
|
|
272
|
-
|
|
273
|
-
min_x = Float::INFINITY
|
|
274
|
-
min_y = Float::INFINITY
|
|
275
|
-
max_x = -Float::INFINITY
|
|
276
|
-
max_y = -Float::INFINITY
|
|
277
|
-
|
|
278
|
-
node.children.each do |child|
|
|
279
|
-
x = child.x || 0.0
|
|
280
|
-
y = child.y || 0.0
|
|
281
|
-
w = child.width || 0.0
|
|
282
|
-
h = child.height || 0.0
|
|
283
|
-
|
|
284
|
-
min_x = [min_x, x].min
|
|
285
|
-
min_y = [min_y, y].min
|
|
286
|
-
max_x = [max_x, x + w].max
|
|
287
|
-
max_y = [max_y, y + h].max
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
{
|
|
291
|
-
min_x: min_x,
|
|
292
|
-
min_y: min_y,
|
|
293
|
-
width: max_x - min_x,
|
|
294
|
-
height: max_y - min_y,
|
|
295
|
-
}
|
|
296
|
-
end
|
|
297
40
|
end
|
|
298
41
|
end
|
|
299
42
|
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Elkrb
|
|
4
|
+
module Layout
|
|
5
|
+
# java.util.Random's 48-bit linear congruential generator. Java ELK draws
|
|
6
|
+
# its random layouts from it, so the same seed only gives the same
|
|
7
|
+
# numbers here if the generator is the same one.
|
|
8
|
+
class JavaRandom
|
|
9
|
+
MULTIPLIER = 0x5DEECE66D
|
|
10
|
+
ADDEND = 0xB
|
|
11
|
+
MASK = (1 << 48) - 1
|
|
12
|
+
INT_LIMIT = 1 << 31
|
|
13
|
+
|
|
14
|
+
def initialize(seed)
|
|
15
|
+
self.seed = seed
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Restarts the sequence, like Random#setSeed.
|
|
19
|
+
def seed=(seed)
|
|
20
|
+
@state = (seed ^ MULTIPLIER) & MASK
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# The top `bits` bits of the next state, as a Java int (bits <= 32).
|
|
24
|
+
def next_bits(bits)
|
|
25
|
+
@state = ((@state * MULTIPLIER) + ADDEND) & MASK
|
|
26
|
+
value = @state >> (48 - bits)
|
|
27
|
+
value >= INT_LIMIT ? value - (1 << 32) : value
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# A signed 64-bit integer, like Random#nextLong.
|
|
31
|
+
def next_long
|
|
32
|
+
value = (next_bits(32) << 32) + next_bits(32)
|
|
33
|
+
value >= 1 << 63 ? value - (1 << 64) : value
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# A float in [0, 1) with 24 random bits, like Random#nextFloat.
|
|
37
|
+
def next_float
|
|
38
|
+
next_bits(24) / 16_777_216.0
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# A double in [0, 1) with 53 random bits, like Random#nextDouble.
|
|
42
|
+
def next_double
|
|
43
|
+
((next_bits(26) << 27) + next_bits(27)) / 9_007_199_254_740_992.0
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# An integer in [0, bound), like Random#nextInt(bound).
|
|
47
|
+
def next_int(bound)
|
|
48
|
+
raise ArgumentError, "bound must be positive" unless bound.positive?
|
|
49
|
+
return (bound * next_bits(31)) >> 31 if bound.nobits?(bound - 1)
|
|
50
|
+
|
|
51
|
+
loop do
|
|
52
|
+
bits = next_bits(31)
|
|
53
|
+
value = bits % bound
|
|
54
|
+
return value if bits - value + (bound - 1) < INT_LIMIT
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|