Graphora API
    Preparing search index...

    Module @graphora/layouts

    Layouts Package

    Package name: @graphora/layouts

    Responsibility: renderer-independent layout interfaces, built-in layout implementations, and future async or worker-backed layout execution.

    Layouts consume immutable graph snapshots from @graphora/core and return plain layout result objects. They do not mutate GraphStore, mount DOM nodes, or depend on renderer, interaction, or React packages.

    import { createGraphStore } from '@graphora/core'
    import {
    applyMeasuredNoOverlap,
    applyNoOverlap,
    createCircularLayout,
    createForceLayout,
    createGridLayout,
    createHierarchicalLayout,
    createWorkerLayout
    } from '@graphora/layouts'

    const store = createGraphStore({
    nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
    edges: [{ source: 'a', target: 'b' }]
    })
    const layout = createCircularLayout({ radius: 160 })
    const result = layout.run(store.snapshot())

    console.log(result.positions)
    console.log(result.bounds)

    const forceLayout = createForceLayout({
    iterations: 120,
    linkDistance: 80,
    chargeStrength: -120,
    center: { x: 0, y: 0 }
    })
    const forceResult = forceLayout.run(store.snapshot())

    const gridLayout = createGridLayout({ columns: 2, cellWidth: 140 })
    const gridResult = gridLayout.run(store.snapshot())
    const readableGridResult = applyNoOverlap(gridResult, { minimumDistance: 56 })

    const dependencyLayout = createHierarchicalLayout({
    direction: 'LR',
    rankSpacing: 100,
    nodeSpacing: 60
    })
    const dependencyResult = dependencyLayout.run(store.snapshot())

    const geometry = {
    units: 'world',
    provenance: {
    graphVersion: store.snapshot().version,
    styleRevision: 0,
    metricsRevision: 0
    },
    nodes: store.snapshot().nodes.map((node) => ({
    id: node.id,
    key: node.key,
    size: { width: 48, height: 48 },
    padding: { top: 8, right: 8, bottom: 8, left: 8 }
    })),
    labels: []
    } as const
    const measuredDependencyResult = dependencyLayout.run(store.snapshot(), {
    geometry
    })

    const separated = applyMeasuredNoOverlap(store.snapshot(), gridResult, geometry)
    if (separated.diagnostics.status !== 'separated') {
    console.warn(separated.diagnostics)
    }

    const workerLayout = createWorkerLayout({
    name: 'remote-layout',
    transport: {
    async run(request) {
    console.log(request.runId, request.layoutName)
    return forceLayout.run(request.snapshot)
    },
    cancel(runId) {
    console.log('cancel layout run', runId)
    },
    dispose() {
    console.log('release worker resources')
    }
    }
    })

    The default circular layout uses center { x: 0, y: 0 }, radius 100, start angle 0, and counterclockwise ordering. Multi-node graphs are positioned in snapshot order. Empty graphs return no positions and bounds: null; a single-node graph is placed at the configured center.

    Layout instances expose normalized options for inspection. The corresponding resolved option types are exported as ResolvedCircularLayoutOptions, ResolvedForceLayoutOptions, and ResolvedGridLayoutOptions.

    The force layout runs a bounded d3-force simulation behind the same public LayoutResult contract. It uses existing node positions as initial positions when available, deterministic fallback positions otherwise, and graph edges as link forces. The V0.1 option surface is intentionally small:

    • iterations: fixed simulation ticks, default 120
    • center: target center, default { x: 0, y: 0 }
    • linkDistance: desired linked-node distance, default 80
    • chargeStrength: many-body strength, default -120
    • centerStrength: x/y centering strength, default 0.08
    • collisionRadius: optional collision radius, default 0
    • seed: deterministic random source seed, default 1

    Layout results are intended to be applied by engine/store integration in later work. This package only computes positions.

    The grid layout places nodes in deterministic snapshot order. By default it chooses enough columns to make a compact grid from the node count. The option surface is intentionally small:

    • columns: optional fixed column count
    • cellWidth: horizontal spacing, default 120
    • cellHeight: vertical spacing, default 96
    • center: target center, default { x: 0, y: 0 }

    applyNoOverlap is a renderer-agnostic post-layout pass over LayoutResult. It can run after circular, force, grid, or future layouts. It separates node positions by a minimum distance and returns a new frozen result without mutating the input result or graph data.

    applyMeasuredNoOverlap(snapshot, candidate, geometry, options) is the rectangle-aware follow-up. It requires one candidate position per active node, preserves candidate order, treats missing optional measurements as zero-area points, and separates positive-area padded body rectangles. Touching boundaries are accepted. Candidate and computed bounds must remain finite and consistent; geometry view provenance must match an optional structural snapshot view. The returned result has complete padded bounds and geometry provenance; diagnostics reports separated, residual-overlap, or work-limit plus iterations, pair checks, moves, and the exact residual count when verification completed. The default sweep is bounded by 128 iterations and the larger of 4,096 or 128 pair checks per node. Dense candidates can still be quadratic inside that explicit total budget. Candidate route or label updates are rejected because the pass would make them stale.

    Run pnpm measured-no-overlap:diagnostic -- --nodes 2000 to record elapsed time and deterministic work counts. The timing is diagnostic only and is not a CI threshold. Optional fixedNodeKeys keep candidate positions fixed; a collision between two fixed bodies remains a reported residual. Unknown/duplicate keys reject. Route updates and label placement remain separate follow-ups.

    applyComponentPacking(snapshot, candidate, geometry, options) translates weakly connected components as whole units into deterministic shelves. It preserves internal positions relative to each other, uses padded body bounds and retains provenance. Set gap (default 40), optional rowWidth wrapping target and origin. A component containing any fixedNodeKeys member stays entirely fixed; other components pack to its right. The frozen result includes per-component bounds, translations, membership and diagnostics (packed, fixed-conflict, work-limit). maxPairChecks bounds verification of fixed-component conflicts; exhausted verification reports an unknown count. It does not repair overlaps within a component; run measured separation first. Missing measurements are zero-size points. Complete positions and matching provenance are required; candidates with route/label updates are rejected. No minimum-area packing guarantee is made.

    The measured-layout example includes Overlap components and Pack components. Run pnpm component-packing:diagnostic -- --nodes 2000 for a deterministic fixture.

    The hierarchical layout uses Dagre behind a Graphora-owned adapter for directed dependency graphs and DAGs. It returns node positions only; edge routing, labels, ports, groups, and incremental layout remain separate concerns. The adapter also produces deterministic, finite positions for cyclic graphs, disconnected components, self-loops, and parallel edges, but cyclic edges cannot all follow the chosen rank direction.

    Its optional geometry capability consumes padded world-space node boxes and returns complete padded bounds. Missing entries in a partial geometry snapshot remain zero-size points. With no geometry context, it preserves the original position-only behavior and point bounds. Asymmetric padding shifts the returned body center correctly relative to Dagre's padded rectangle.

    • direction: rank flow (TB, BT, LR, or RL), default TB
    • rankSpacing: distance between ranks, default 80
    • nodeSpacing: distance between nodes within a rank, default 50
    • edgeSpacing: separation used by Dagre between edge paths, default 20
    • center: world-space center of the returned node positions, default { x: 0, y: 0 }

    The dependency is @dagrejs/dagre 3.1.x. It supports the workspace's ESM build and ships its own TypeScript declarations.

    createWorkerLayout adapts async or worker-backed layout execution behind the same GraphLayout contract. The adapter accepts a small transport object with run, optional cancel, and optional dispose methods. Graphora does not expose browser Worker handles from the layout or engine APIs; applications or future optional packages can own bundler-specific Worker construction behind the transport boundary.

    createBrowserWorkerForceLayout supplies Graphora's runnable browser module Worker and force-layout entrypoint. It resolves the Worker from new URL('./force-worker.js', import.meta.url), which the performance example verifies through the repository's Vite build. The adapter terminates its active Worker after success, error, cancellation, or timeout; starting a newer run terminates obsolete CPU work. Call layout.dispose() when the application no longer owns it. GraphEngine.destroy() does not dispose layouts because one layout instance may be shared across multiple engine lifecycles.

    Worker requests preserve the optional LayoutRunContext, and browser responses validate and freeze geometry provenance plus the reserved route/label result schema. All boundary values must remain structured-clone-safe plain data.

    createObstacleRouter(options) / ObstacleRouter implements a cancellable, measured, route-only layout stage. Set complete geometry and explicitly request edge route output when running it through the engine. getDiagnostics() reports per-edge success/reuse/fallback and bounded work. cancel() aborts active work; dispose() also releases cached paths. Direct run contexts accept an optional AbortSignal. See the concept and example for defaults, selective rerouting after drag, parallel lanes, loops and limits.

    BrowserWorkerForceLayoutTransport
    CircularLayout
    ForceLayout
    GridLayout
    HierarchicalLayout
    ObstacleRouter
    WorkerLayout
    BrowserForceLayoutRequest
    BrowserWorkerForceLayoutOptions
    BrowserWorkerLike
    CircularLayoutOptions
    ComponentPackingOptions
    ComponentPackingResult
    ForceLayoutOptions
    GraphLayout
    GridLayoutOptions
    HierarchicalLayoutOptions
    LayoutCapabilities
    LayoutEdgeRoute
    LayoutGeometryProvenance
    LayoutGeometrySnapshot
    LayoutLabelGeometry
    LayoutLabelPlacement
    LayoutNodeGeometry
    LayoutNodePosition
    LayoutOutputRequest
    LayoutPadding
    LayoutResult
    LayoutRunContext
    LayoutSize
    LayoutViewIdentity
    MeasuredNoOverlapDiagnostics
    MeasuredNoOverlapOptions
    MeasuredNoOverlapResult
    NoOverlapOptions
    ObstacleEdgeDiagnostic
    ObstacleRouterOptions
    ObstacleRoutingContext
    ObstacleRoutingDiagnostics
    PackedComponent
    ResolvedCircularLayoutOptions
    ResolvedForceLayoutOptions
    ResolvedGridLayoutOptions
    ResolvedHierarchicalLayoutOptions
    WorkerLayoutOptions
    WorkerLayoutRequest
    WorkerLayoutTransport
    BrowserForceLayoutResponse
    BrowserWorkerFactory
    CircularLayoutDirection
    HierarchicalLayoutDirection
    LayoutArtifactRequest
    LayoutArtifactUpdate
    LayoutInput
    LayoutLabelOwnerType
    LayoutRevision
    MeasuredNoOverlapStatus
    DEFAULT_CIRCULAR_LAYOUT_RADIUS
    DEFAULT_FORCE_LAYOUT_CENTER_STRENGTH
    DEFAULT_FORCE_LAYOUT_CHARGE_STRENGTH
    DEFAULT_FORCE_LAYOUT_COLLISION_RADIUS
    DEFAULT_FORCE_LAYOUT_ITERATIONS
    DEFAULT_FORCE_LAYOUT_SEED
    DEFAULT_GRID_LAYOUT_CELL_HEIGHT
    DEFAULT_GRID_LAYOUT_CELL_WIDTH
    DEFAULT_HIERARCHICAL_LAYOUT_DIRECTION
    DEFAULT_HIERARCHICAL_LAYOUT_EDGE_SPACING
    DEFAULT_HIERARCHICAL_LAYOUT_NODE_SPACING
    DEFAULT_HIERARCHICAL_LAYOUT_RANK_SPACING
    DEFAULT_MEASURED_NO_OVERLAP_ITERATIONS
    DEFAULT_MEASURED_NO_OVERLAP_PAIR_CHECKS_PER_NODE
    DEFAULT_NO_OVERLAP_ITERATIONS
    DEFAULT_NO_OVERLAP_MINIMUM_DISTANCE
    layoutsPackageDependencies
    layoutsPackageInfo
    layoutsPackageName
    MINIMUM_MEASURED_NO_OVERLAP_PAIR_CHECKS
    applyComponentPacking
    applyMeasuredNoOverlap
    applyNoOverlap
    createBrowserWorkerForceLayout
    createCircularLayout
    createForceLayout
    createGridLayout
    createHierarchicalLayout
    createObstacleRouter
    createWorkerLayout
    freezeLayoutGeometryProvenance
    freezeLayoutGeometrySnapshot