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.
Files changed (144) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +364 -0
  3. data/LICENSE +25 -0
  4. data/README.adoc +695 -557
  5. data/exe/elkrb +36 -2
  6. data/lib/elkrb/atomic_destination.rb +53 -0
  7. data/lib/elkrb/best_effort_write.rb +49 -0
  8. data/lib/elkrb/cli/layout_flag_options.rb +54 -0
  9. data/lib/elkrb/cli.rb +155 -82
  10. data/lib/elkrb/command_resolver.rb +186 -0
  11. data/lib/elkrb/commands/batch_command.rb +28 -5
  12. data/lib/elkrb/commands/convert_command.rb +22 -52
  13. data/lib/elkrb/commands/diagram_command.rb +234 -102
  14. data/lib/elkrb/commands/options_command.rb +87 -0
  15. data/lib/elkrb/commands/render_command.rb +6 -1
  16. data/lib/elkrb/commands/validate_command.rb +146 -91
  17. data/lib/elkrb/errors.rb +20 -1
  18. data/lib/elkrb/format_sniffer.rb +362 -0
  19. data/lib/elkrb/geometry/bezier.rb +14 -4
  20. data/lib/elkrb/geometry/point.rb +5 -6
  21. data/lib/elkrb/geometry/rectangle.rb +2 -2
  22. data/lib/elkrb/graph/deep_stringify_keys.rb +30 -0
  23. data/lib/elkrb/graph/edge.rb +89 -3
  24. data/lib/elkrb/graph/graph.rb +43 -15
  25. data/lib/elkrb/graph/label.rb +16 -2
  26. data/lib/elkrb/graph/node.rb +13 -2
  27. data/lib/elkrb/graph/node_constraints.rb +139 -8
  28. data/lib/elkrb/graph/normalize_option_keys.rb +39 -0
  29. data/lib/elkrb/graph/port.rb +29 -21
  30. data/lib/elkrb/graph/read_only_mapping.rb +21 -0
  31. data/lib/elkrb/graphviz_wrapper.rb +152 -44
  32. data/lib/elkrb/layout/algorithm_registry.rb +65 -8
  33. data/lib/elkrb/layout/algorithms/base_algorithm.rb +74 -74
  34. data/lib/elkrb/layout/algorithms/box.rb +47 -20
  35. data/lib/elkrb/layout/algorithms/disco.rb +150 -61
  36. data/lib/elkrb/layout/algorithms/fixed.rb +70 -10
  37. data/lib/elkrb/layout/algorithms/force.rb +145 -86
  38. data/lib/elkrb/layout/algorithms/layered/bk/aligner.rb +104 -0
  39. data/lib/elkrb/layout/algorithms/layered/bk/compactor.rb +87 -0
  40. data/lib/elkrb/layout/algorithms/layered/bk/view.rb +103 -0
  41. data/lib/elkrb/layout/algorithms/layered/bk_node_placer.rb +170 -0
  42. data/lib/elkrb/layout/algorithms/layered/components.rb +170 -0
  43. data/lib/elkrb/layout/algorithms/layered/crossing_minimizer.rb +306 -0
  44. data/lib/elkrb/layout/algorithms/layered/cycle_breaker.rb +118 -49
  45. data/lib/elkrb/layout/algorithms/layered/discovery_order.rb +75 -0
  46. data/lib/elkrb/layout/algorithms/layered/dummy_slot.rb +31 -0
  47. data/lib/elkrb/layout/algorithms/layered/layer_assigner.rb +188 -48
  48. data/lib/elkrb/layout/algorithms/layered/layer_constraints.rb +82 -0
  49. data/lib/elkrb/layout/algorithms/layered/layer_sweep.rb +195 -0
  50. data/lib/elkrb/layout/algorithms/layered/node_placer.rb +214 -36
  51. data/lib/elkrb/layout/algorithms/layered/orthogonal/gap_solver.rb +219 -0
  52. data/lib/elkrb/layout/algorithms/layered/orthogonal/hyper_edge_segment.rb +152 -0
  53. data/lib/elkrb/layout/algorithms/layered/orthogonal/junction_points.rb +58 -0
  54. data/lib/elkrb/layout/algorithms/layered/orthogonal/segment_order.rb +134 -0
  55. data/lib/elkrb/layout/algorithms/layered/orthogonal_router.rb +253 -0
  56. data/lib/elkrb/layout/algorithms/layered/port_order.rb +188 -0
  57. data/lib/elkrb/layout/algorithms/layered/port_spread.rb +42 -0
  58. data/lib/elkrb/layout/algorithms/layered.rb +548 -13
  59. data/lib/elkrb/layout/algorithms/libavoid.rb +951 -187
  60. data/lib/elkrb/layout/algorithms/mrtree.rb +585 -65
  61. data/lib/elkrb/layout/algorithms/radial.rb +188 -29
  62. data/lib/elkrb/layout/algorithms/random.rb +131 -17
  63. data/lib/elkrb/layout/algorithms/rectpacking.rb +28 -40
  64. data/lib/elkrb/layout/algorithms/spore_compaction.rb +57 -100
  65. data/lib/elkrb/layout/algorithms/spore_overlap.rb +78 -84
  66. data/lib/elkrb/layout/algorithms/stress.rb +391 -84
  67. data/lib/elkrb/layout/algorithms/topdown_packing.rb +95 -31
  68. data/lib/elkrb/layout/algorithms/vertiflex.rb +12 -47
  69. data/lib/elkrb/layout/constraints/alignment_constraint.rb +49 -12
  70. data/lib/elkrb/layout/constraints/base_constraint.rb +3 -15
  71. data/lib/elkrb/layout/constraints/constraint_processor.rb +9 -23
  72. data/lib/elkrb/layout/constraints/fixed_position_constraint.rb +22 -13
  73. data/lib/elkrb/layout/constraints/layer_constraint.rb +10 -33
  74. data/lib/elkrb/layout/constraints/relative_position_constraint.rb +94 -50
  75. data/lib/elkrb/layout/edge_router.rb +533 -337
  76. data/lib/elkrb/layout/hierarchical_processor.rb +19 -276
  77. data/lib/elkrb/layout/java_random.rb +59 -0
  78. data/lib/elkrb/layout/label_placer.rb +165 -157
  79. data/lib/elkrb/layout/layout_engine.rb +46 -23
  80. data/lib/elkrb/layout/node_index.rb +149 -0
  81. data/lib/elkrb/layout/polyomino/component_compactor.rb +138 -0
  82. data/lib/elkrb/layout/polyomino/footprint.rb +94 -0
  83. data/lib/elkrb/layout/polyomino/grid.rb +106 -0
  84. data/lib/elkrb/layout/polyomino/packer.rb +95 -0
  85. data/lib/elkrb/layout/polyomino/piece.rb +52 -0
  86. data/lib/elkrb/layout/port_constraint_processor.rb +131 -44
  87. data/lib/elkrb/layout/spore/body.rb +65 -0
  88. data/lib/elkrb/layout/spore/depth_first_compaction.rb +127 -0
  89. data/lib/elkrb/layout/spore/edge_cost.rb +69 -0
  90. data/lib/elkrb/layout/spore/gap.rb +76 -0
  91. data/lib/elkrb/layout/spore/geometry.rb +104 -0
  92. data/lib/elkrb/layout/spore/overlap_sweep.rb +90 -0
  93. data/lib/elkrb/layout/spore/placement.rb +57 -0
  94. data/lib/elkrb/layout/spore/rays.rb +119 -0
  95. data/lib/elkrb/layout/spore/spanning_tree.rb +53 -0
  96. data/lib/elkrb/layout/spore/triangulation.rb +159 -0
  97. data/lib/elkrb/layout_flags.rb +69 -0
  98. data/lib/elkrb/options/decimal.rb +50 -0
  99. data/lib/elkrb/options/element_options.rb +43 -0
  100. data/lib/elkrb/options/elk_padding.rb +20 -14
  101. data/lib/elkrb/options/k_vector.rb +6 -7
  102. data/lib/elkrb/options/k_vector_chain.rb +12 -20
  103. data/lib/elkrb/options/option_map.rb +85 -0
  104. data/lib/elkrb/options/registry.rb +335 -0
  105. data/lib/elkrb/options/resolver.rb +151 -0
  106. data/lib/elkrb/options/spellings.rb +62 -0
  107. data/lib/elkrb/options/unhonoured_report.rb +194 -0
  108. data/lib/elkrb/parsers/elkt/lexer.rb +309 -0
  109. data/lib/elkrb/parsers/elkt/parser.rb +424 -0
  110. data/lib/elkrb/parsers/elkt/resolver.rb +142 -0
  111. data/lib/elkrb/parsers/elkt/token.rb +40 -0
  112. data/lib/elkrb/parsers/elkt_parser.rb +32 -230
  113. data/lib/elkrb/serializers/dot_serializer.rb +342 -54
  114. data/lib/elkrb/serializers/elkt_serializer.rb +386 -155
  115. data/lib/elkrb/version.rb +1 -1
  116. data/lib/elkrb.rb +60 -61
  117. metadata +71 -49
  118. data/.rspec +0 -3
  119. data/.rubocop.yml +0 -11
  120. data/Gemfile +0 -13
  121. data/Rakefile +0 -64
  122. data/benchmarks/README.md +0 -172
  123. data/benchmarks/elkjs_benchmark.js +0 -140
  124. data/benchmarks/elkrb_benchmark.rb +0 -145
  125. data/benchmarks/fixtures/graphs.json +0 -10777
  126. data/benchmarks/generate_report.rb +0 -241
  127. data/benchmarks/generate_test_graphs.rb +0 -154
  128. data/benchmarks/results/elkrb_results.json +0 -280
  129. data/benchmarks/results/elkrb_summary.json +0 -285
  130. data/elkrb.gemspec +0 -39
  131. data/examples/dot_export_demo.rb +0 -133
  132. data/examples/hierarchical_graph.rb +0 -19
  133. data/examples/layout_constraints_demo.rb +0 -272
  134. data/examples/port_constraints_demo.rb +0 -291
  135. data/examples/self_loop_demo.rb +0 -391
  136. data/examples/simple_graph.rb +0 -50
  137. data/examples/spline_routing_demo.rb +0 -235
  138. data/lib/elkrb/graph/layout_options.rb +0 -247
  139. data/sig/elkrb/constraints.rbs +0 -114
  140. data/sig/elkrb/geometry.rbs +0 -61
  141. data/sig/elkrb/graph.rbs +0 -112
  142. data/sig/elkrb/layout.rbs +0 -107
  143. data/sig/elkrb/options.rbs +0 -81
  144. data/sig/elkrb.rbs +0 -32
@@ -21,25 +21,18 @@ module Elkrb
21
21
  class RelativePositionConstraint < BaseConstraint
22
22
  # Apply relative position constraint
23
23
  #
24
- # Positions nodes relative to their reference nodes.
25
- # Processes in order of position_priority to handle chains.
24
+ # Positions nodes relative to their reference nodes, a node only
25
+ # after the node it is relative to. Among nodes whose references are
26
+ # settled, higher position_priority goes first, then input order.
27
+ # Nodes on a relative_to cycle, and nodes whose reference is missing
28
+ # or has no coordinates, are left where they are; #validate reports
29
+ # them.
26
30
  #
27
31
  # @param graph [Graph::Graph] The graph
28
32
  # @return [Graph::Graph] The modified graph
29
33
  def apply(graph)
30
- nodes_with_relative = all_nodes(graph).select do |node|
31
- node.constraints&.relative_to &&
32
- node.constraints.relative_offset
33
- end
34
-
35
- # Sort by priority (higher priority processed first)
36
- nodes_with_relative.sort_by! do |node|
37
- -(node.constraints.position_priority || 0)
38
- end
39
-
40
- # Apply relative positioning
41
- nodes_with_relative.each do |node|
42
- apply_relative_position(node, graph)
34
+ resolution_order(graph).each do |node|
35
+ position_relative(node, graph)
43
36
  end
44
37
 
45
38
  graph
@@ -47,63 +40,114 @@ module Elkrb
47
40
 
48
41
  # Validate relative position constraints
49
42
  #
50
- # Checks that reference nodes exist and positions are correct.
43
+ # Checks that reference nodes exist, that relative_to does not form a
44
+ # cycle, and that positions are correct.
51
45
  #
52
46
  # @param graph [Graph::Graph] The graph to validate
53
47
  # @return [Array<String>] List of validation errors
54
48
  def validate(graph)
55
- errors = []
56
-
57
- all_nodes(graph).each do |node|
58
- next unless node.constraints&.relative_to
49
+ cyclic = cyclic_nodes(graph)
59
50
 
60
- ref_id = node.constraints.relative_to
61
- ref_node = find_node(graph, ref_id)
51
+ all_nodes(graph).flat_map do |node|
52
+ next [] unless node.constraints&.relative_to
62
53
 
63
- if ref_node.nil?
64
- errors << "Node '#{node.id}' has relative_to constraint " \
65
- "referencing '#{ref_id}' which doesn't exist"
66
- next
67
- end
68
-
69
- # Validate position if offset is specified
70
- if node.constraints.relative_offset
71
- expected_x = ref_node.x + node.constraints.relative_offset.x
72
- expected_y = ref_node.y + node.constraints.relative_offset.y
73
-
74
- tolerance = 0.01
75
- if (node.x - expected_x).abs > tolerance ||
76
- (node.y - expected_y).abs > tolerance
77
- errors << "Node '#{node.id}' relative position incorrect. " \
78
- "Expected (#{expected_x}, #{expected_y}), " \
79
- "got (#{node.x}, #{node.y})"
80
- end
54
+ if cyclic.any? { |member| member.equal?(node) }
55
+ ["Node '#{node.id}' is on, or depends on, a relative_to cycle"]
56
+ else
57
+ check_relative_node(node, graph)
81
58
  end
82
59
  end
83
-
84
- errors
85
60
  end
86
61
 
87
62
  private
88
63
 
89
- # Apply relative position to a single node
90
- def apply_relative_position(node, graph)
64
+ def check_relative_node(node, graph)
91
65
  ref_id = node.constraints.relative_to
92
66
  ref_node = find_node(graph, ref_id)
93
67
 
94
- unless ref_node
95
- warn "Warning: Node '#{node.id}' references non-existent " \
96
- "node '#{ref_id}' for relative positioning"
97
- return
68
+ if ref_node.nil?
69
+ return ["Node '#{node.id}' has relative_to constraint " \
70
+ "referencing '#{ref_id}' which doesn't exist"]
98
71
  end
99
72
 
100
73
  offset = node.constraints.relative_offset
101
- return unless offset
74
+ return [] unless offset
75
+
76
+ unless coordinates?(node) && coordinates?(ref_node)
77
+ return ["Node '#{node.id}' relative position cannot be checked: " \
78
+ "'#{node.id}' or '#{ref_id}' has no coordinates"]
79
+ end
80
+
81
+ expected_x = ref_node.x + offset.x
82
+ expected_y = ref_node.y + offset.y
83
+ return [] if within_tolerance?(node, expected_x, expected_y)
84
+
85
+ ["Node '#{node.id}' relative position incorrect. " \
86
+ "Expected (#{expected_x}, #{expected_y}), " \
87
+ "got (#{node.x}, #{node.y})"]
88
+ end
89
+
90
+ def within_tolerance?(node, expected_x, expected_y)
91
+ tolerance = 0.01
92
+ (node.x - expected_x).abs <= tolerance &&
93
+ (node.y - expected_y).abs <= tolerance
94
+ end
95
+
96
+ def coordinates?(node)
97
+ !node.x.nil? && !node.y.nil?
98
+ end
99
+
100
+ # Apply relative position to a single node
101
+ def position_relative(node, graph)
102
+ ref_node = find_node(graph, node.constraints.relative_to)
103
+ offset = node.constraints.relative_offset
104
+ return unless ref_node && offset && coordinates?(ref_node)
102
105
 
103
- # Calculate and set new position
104
106
  node.x = ref_node.x + offset.x
105
107
  node.y = ref_node.y + offset.y
106
108
  end
109
+
110
+ # Nodes to position, each after the node it is relative to.
111
+ def resolution_order(graph)
112
+ pending = positioned_nodes(graph)
113
+ order = []
114
+
115
+ until pending.empty?
116
+ ready = pending.select { |node| ready?(node, graph, pending) }
117
+ break if ready.empty?
118
+
119
+ node = ready.min_by do |candidate|
120
+ [-(candidate.constraints.position_priority || 0),
121
+ pending.index { |p| p.equal?(candidate) }]
122
+ end
123
+ pending.delete_if { |p| p.equal?(node) }
124
+ order << node
125
+ end
126
+
127
+ order
128
+ end
129
+
130
+ # A node is ready when the node it is relative to is not itself
131
+ # still waiting to be positioned.
132
+ def ready?(node, graph, pending)
133
+ ref_node = find_node(graph, node.constraints.relative_to)
134
+ ref_node.nil? || pending.none? { |p| p.equal?(ref_node) }
135
+ end
136
+
137
+ def positioned_nodes(graph)
138
+ all_nodes(graph).select do |node|
139
+ node.constraints&.relative_to && node.constraints.relative_offset
140
+ end
141
+ end
142
+
143
+ # Nodes that can never be ready because relative_to leads back to
144
+ # them.
145
+ def cyclic_nodes(graph)
146
+ ordered = resolution_order(graph)
147
+ positioned_nodes(graph).reject do |node|
148
+ ordered.any? { |placed| placed.equal?(node) }
149
+ end
150
+ end
107
151
  end
108
152
  end
109
153
  end