Skip to content

Layout engine wastes canvas space: label-less wrappers reserve padding/header + icon size is a fixed constant #58

Description

@hungdo-sami

Summary

When building multi-tab architecture decks with the layout engine, the rendered diagrams carry a lot of dead whitespace and the icons look tiny once the figure is fit to a page (e.g. embedded in a Word/PDF page at ~6 in wide). Getting a dense, readable diagram currently requires a lot of manual per-diagram tuning. The root causes are all in src/layout-engine.mjs defaults, and I think a few small default changes + one new option would fix the common case out of the box.

Environment

  • Kit around feat(layout): numbered step badges, balanced fill, thin black edges + polished templates (v1.0.x), src/layout-engine.mjs.
  • Use case: a 6-tab Databricks lakehouse deck (medallion + serving/integration/MLOps pillars), rendered to PNG and embedded in a document at page width.

Root causes (with current defaults)

1. Label-less layout wrappers still reserve a title strip + heavy padding.
group / frame / phantom / grid all default to:

gap: opts.gap ?? 30,
pad: opts.pad ?? 24,
header: label ? (opts.header ?? 36) : (opts.header ?? 14),

A label-less grouping wrapper (the common row()/col() = phantom with no title) still gets pad: 24 (→ 48 px horizontal + 48 px vertical) and a header: 14 dead strip. Because composing a real layout nests several of these wrappers, the invisible padding compounds: a couple of nested row/col wrappers can silently add 100–150 px of empty margin around content that has no visual container at all.

2. Icon size is a hard-coded module constant.

const ICON = 48;

There is no build-API way to make icons bigger — not per-icon, not per-diagram, not global. Meanwhile mIcon floors the icon cell width by the label (Math.max(96, Math.min(200, label.length*7+24))), so a 48 px glyph sits in a 96–200 px cell. The icon:label ratio is fixed and small, so at page scale the glyphs are hard to read.

3. No compact primitive for single-item full-width bands.
A section/frame given a wide width with one centered icon (e.g. a "Governance" or "Connectivity" band spanning the diagram) renders as a big mostly-empty box. There's no built-in "title band + icon laid out horizontally" strip, so authors hand-roll one.

4. Frames don't shrink-to-content, and over-wide widths are silent.
Widths are passed explicitly and act as a floor; a width larger than the content just becomes whitespace, with no validator signal (unlike the existing palette / edge-crossing advice).

Impact / what I had to do manually

To turn a sprawling deck into a dense, readable one for the same content I had to, on every page: set pad: 0 on all label-less row/col wrappers, cut default gaps ~40–50%, shrink forced frame/band widths down to actual content, convert single-icon full-width bands into horizontal title+icon strips, and 2-column a tall 6-item stack. Concretely one page went from a 1742×1231 canvas to ~1376×485 for identical content — after which the (still 48 px) icons finally read clearly at page width. That's a lot of fiddling for something the engine could default better.

Suggestions

  1. Zero pad/header for label-less wrappers. A phantom/row/col with no title shouldn't reserve a 14 px header or 24 px pad — default those to 0 when label is empty (keep the current values when a label is present). Biggest single win, low risk.
  2. Make icon size configurable. Support new Diagram({ iconSize }) and/or icon(id, name, label, { size }), and have mIcon/place/emit honor it instead of the module const. (Keep 48 as the default.)
  3. Add a compact band primitive — title band + icon(s) laid out horizontally — for single-item full-width bands, so they don't render as big empty boxes.
  4. Shrink-to-content by default — treat an explicit frame width as a max/hint rather than a hard floor beyond the title-width floor; and add a validator advice for "diagram is mostly empty / nodes fill < N% of their frame", matching the existing palette / edge-crossing advisories.
  5. (Optional) a single Diagram({ density: 'compact' }) switch that applies tighter gap/pad/icon defaults in one place.

Items 1 + 2 alone would remove most of the manual tuning. Happy to send a PR for #1 if that direction sounds good.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions