feat(layout): Assign groups to disjoint swimlanes and improve port packing#34
Merged
Conversation
…ression When multiple edges share the same horizontal (East/West) attachment side, `slot_offset` and `row_offset` were added independently. In join-table cases where the source column order and target row order are reversed (e.g. `product_categories` with FKs to `products` and `categories`), the two offsets cancelled out and the source-side ports collapsed to ~4px apart. Replace the naive centered distribution on horizontal sides with a PAV-based constrained isotonic regression: sort candidates by `remote_order` (to keep the source fan-out free of self-crossings), pack toward each edge's `row_order` target under a `>= gap` monotonicity constraint, and derive `slot_offset` so the renderer's `center + slot + row` sum lands on the packed position. `effective_gap` is clamped to the available node span up-front so the post-pack uniform shift cannot re-violate min-gap. Vertical sides (North/South) keep the previous centered distribution since `col_offset` does not affect the port X there. The horizontal-only gating is controlled via `AttachmentSide::is_horizontal`, which is now `pub(crate)`. Add three regression tests covering: the LTR join-table collapse, the TTB vertical-side untouched path, and a TTB cross-axis (same-rank) case where the horizontal packing must still apply. Snapshots update only for LTR/RTL matrices; TTB/BTT matrices are unchanged, confirming the gating.
Hierarchical layout previously ignored groups when positioning nodes, so group bounding boxes were just envelopes over whichever nodes happened to share a schema. Nodes from different schemas were interleaved by rank, which caused group rectangles to overlap and labels to disappear behind unrelated tables. Introduce a swimlane model: each group reserves a contiguous range on the secondary axis (Y for horizontal layouts, X for vertical ones) sized by the worst-case per-rank span of its members. Nodes within a rank are stably sorted by canonical lane order, and ungrouped nodes fall into a trailing sentinel lane. An extra gap is inserted between adjacent lanes and respected by the collision resolver so lanes never merge. With this change, group bounding boxes are pairwise disjoint on the secondary axis for every schema in multi_schema.sql, and the ungrouped path is unaffected (existing snapshots are unchanged).
The group rectangle's top padding was the same 20px used for all sides, but SVG/HTML backends draw a 34px label band along the top edge. As a result the band always overlapped the topmost row of nodes inside the group, hiding the label even after swimlane separation made the groups themselves non-overlapping. Bump the top padding to 44px so the band sits above every node in the group while leaving the other three sides at 20px.
Grouped hierarchical layouts exposed a broader label-placement bug: edge labels only treated nodes, prior labels, and sampled route segments as obstacles, so labels could settle directly on top of FK crow's foot or arrow markers near the route endpoints. Model endpoint markers as explicit clearance rectangles during both the initial placement pass and the later label-relaxation pass. The clearance is keyed off the routed endpoint direction so the same logic works across grouped and ungrouped layouts, orthogonal and straight routes, and FK versus generic arrow markers. Add focused layout regressions for marker avoidance and refresh the layout and SVG snapshots to lock in the new label positions.
Code Metrics Report
Details | | main (b1ae5e4) | #34 (41c171b) | +/- |
|---------------------|----------------|---------------|-------|
+ | Coverage | 93.5% | 93.6% | +0.0% |
| Files | 71 | 71 | 0 |
| Lines | 27913 | 28475 | +562 |
+ | Covered | 26126 | 26671 | +545 |
+ | Test Execution Time | 1m44s | 1m34s | -10s |Code coverage of files in pull request scope (95.3% → 95.5%)
Reported by octocov |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
overlapping group bounding boxes in multi-schema layouts
port collapse in join-table fan-out scenarios
Changes
product_categorieswith FKs toproductsandcategories)remote_order, pack toward each edge'srow_ordertarget under a>= gapmonotonicity constraint, and derive
slot_offsetso the renderer'scenter + slot + rowsum lands on thepacked position
AttachmentSide::is_horizontalby the worst-case per-rank span of its members
trailing sentinel lane; an extra gap between adjacent lanes is respected by the collision resolver
inside the group, while keeping the other three sides at 20 px
later label-relaxation pass
orthogonal and straight routes, and FK versus generic arrow markers