Skip to content

Viewer: enforce minimum font-size readability threshold on zoom #6

Description

@hugolytics

Problem

At default zoom / fit-to-screen, dense D2 diagrams are unreadable — labels are tiny, elements overlap visually, and there is no way to understand the structure without zooming in manually. Screenshot evidence: diagram with ~20+ nodes renders at fit-zoom where nothing is legible.

This is fundamentally not just a font-size problem. It is a level-of-detail problem: the viewer needs to be smart about which elements to show, at what size, and with how much spacing — depending on the current zoom level and the available viewport area.

Expanded scope (beyond font clamping)

  1. Semantic zoom / Level of Detail (LOD) — at low zoom, suppress or collapse low-priority elements (edge labels, nested annotations, secondary nodes); at high zoom, reveal full detail.
  2. Label visibility culling — hide labels whose rendered bounding box would be smaller than a legibility threshold; optionally show them on hover.
  3. Element spacing awareness — when zoomed out far enough that elements overlap, either cluster them or indicate density rather than rendering illegible overlapping boxes.
  4. Minimum font-size floor — even when elements are shown, clamp rendered font size to a readable minimum (original scope).

Constraint: D2 produces a static SVG

DiaScope renders a pre-computed SVG from D2. We cannot re-run the D2 layout at runtime. Any LOD logic must operate as a post-processing pass on the SVG DOM at render/zoom time.

Candidate libraries to evaluate (rather than reinvent)

Library What it brings Notes
labelgun Label collision detection & priority-based hiding — widely used in mapping Designed for exactly this: hide overlapping labels, keep high-priority ones
rbush Fast R-tree spatial index for 2D bounding-box collision queries Lightweight; powers labelgun and mapbox; useful for building custom culling
d3-zoom Zoom transform + event hooks where LOD logic can be wired Already likely in play; the zoom event is the right hook for LOD updates
@interactjs/interact Alternative if more gesture control is needed Probably overkill
Cartography / MapLibre label placement Art-of-the-state label placement from the mapping world Too heavy to adopt wholesale, but algorithms are worth studying

Recommended starting point: rbush for spatial indexing + a custom SVG post-processor that culls/shows <text> and <g> elements based on their rendered bounding box at the current zoom transform. labelgun is worth prototyping if the rbush approach feels like too much custom logic.

Proposed behaviour (revised)

  • At zoom-to-fit / low zoom: apply LOD pass — hide labels below legibility threshold, optionally replace dense clusters with a summary indicator.
  • At medium zoom: show primary labels; suppress secondary/nested annotations.
  • At high zoom (zoomed in): show everything; clamp font size to minimum floor so nothing is ever tinier than ~11 px rendered.
  • All of this behind a "Smart labels" toggle — off means pure geometric SVG scaling (current behaviour).

Open questions

  • Which SVG elements map to "primary" vs "secondary" in D2 output — needs analysis of D2 SVG structure.
  • Should culled labels be replaced with a dot/indicator, hidden entirely, or shown on hover?
  • Does labelgun work cleanly against SVG getBoundingClientRect or does it need a coordinate transform layer?
  • Should the LOD thresholds be fixed, derived from container size, or user-configurable?

Acceptance criteria

  • At fit-zoom on a dense diagram, primary node labels are legible (not culled, not overlapping).
  • Secondary labels / annotations are suppressed at low zoom and revealed as zoom increases.
  • No label ever renders below ~11 px on screen.
  • "Smart labels" mode is togglable (UI button or keyboard shortcut).
  • No regression on zoom, pan, fit, or reset behaviour.
  • Evaluated and decided on labelgun vs rbush-custom before implementation begins.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions