Skip to content

feat(layout): Assign groups to disjoint swimlanes and improve port packing#34

Merged
mhiro2 merged 4 commits into
mainfrom
feat/layout-groups-and-ports
Apr 6, 2026
Merged

feat(layout): Assign groups to disjoint swimlanes and improve port packing#34
mhiro2 merged 4 commits into
mainfrom
feat/layout-groups-and-ports

Conversation

@mhiro2

@mhiro2 mhiro2 commented Apr 6, 2026

Copy link
Copy Markdown
Owner

Summary

  • Introduce a swimlane model that assigns each group a contiguous range on the secondary axis, eliminating
    overlapping group bounding boxes in multi-schema layouts
  • Replace naive horizontal port distribution with PAV-based constrained isotonic regression, preventing
    port collapse in join-table fan-out scenarios
  • Fix group label band padding and edge label clearance from FK crow's foot / arrow markers

Changes

  • 1101307 : fix(layout): pack horizontal edge ports with constrained isotonic regression
    • Prevent horizontal-side port collapse when source column order and target row order are reversed (e.g.
      product_categories with FKs to products and categories)
    • Sort candidates by remote_order, 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
    • Keep vertical-side (North/South) centered distribution unchanged; gating is controlled via
      AttachmentSide::is_horizontal
  • 5f7f97d : feat(layout): assign groups to disjoint swimlanes
    • Each group reserves a contiguous range on the secondary axis (Y for horizontal, X for vertical) sized
      by the worst-case per-rank span of its members
    • Nodes within a rank are stably sorted by canonical lane order, with ungrouped nodes falling into a
      trailing sentinel lane; an extra gap between adjacent lanes is respected by the collision resolver
  • 2e0d300 : fix(layout): reserve top padding for group label band
    • Bump the group rectangle's top padding to 44 px so the 34 px SVG/HTML label band sits above every node
      inside the group, while keeping the other three sides at 20 px
  • 6fdaaf8 : fix(layout): keep edge labels clear of endpoint markers
    • Model endpoint markers as explicit clearance rectangles during both the initial placement pass and the
      later label-relaxation pass
    • Clearance is keyed off the routed endpoint direction, working across grouped and ungrouped layouts,
      orthogonal and straight routes, and FK versus generic arrow markers

mhiro2 added 4 commits April 6, 2026 19:41
…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.
@mhiro2 mhiro2 self-assigned this Apr 6, 2026
@mhiro2 mhiro2 added the enhancement New feature or request label Apr 6, 2026
@github-actions

github-actions Bot commented Apr 6, 2026

Copy link
Copy Markdown

Code Metrics Report

main (b1ae5e4) #34 (41c171b) +/-
Coverage 93.5% 93.6% +0.0%
Test Execution Time 1m44s 1m34s -10s
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%)

Files Coverage +/- Status
crates/relune-layout/src/layout.rs 94.8% +0.3% modified
crates/relune-layout/src/port.rs 96.7% -1.5% modified
crates/relune-layout/src/route/mod.rs 98.6% 0.0% modified

Reported by octocov

@mhiro2
mhiro2 merged commit ad7fc6b into main Apr 6, 2026
3 checks passed
@mhiro2
mhiro2 deleted the feat/layout-groups-and-ports branch April 6, 2026 13:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant