npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@runiq/layout-base

v1.2.0

Published

Layout engine interfaces and adapters for Runiq

Downloads

354

Readme

@runiq/layout-base

Layout engine package for Runiq diagram system. Provides automatic graph layout using ELK (Eclipse Layout Kernel).

Features

  • ELK Layout Engine - Modern, actively maintained (replaced Dagre in Oct 2025)
  • Multiple Algorithms - Layered (hierarchical), Force, Stress, Tree, Radial, Box
  • Superior Quality - Better edge routing, crossing minimization
  • Manual Positioning - Support for fixed node positions (hybrid layout)
  • Flexible Configuration - Direction, spacing, and algorithm options
  • High Performance - Handles 50+ nodes efficiently
  • TypeScript - Full type safety

Installation

pnpm add @runiq/layout-base

Usage

Basic Usage

import { ElkLayoutEngine } from '@runiq/layout-base';
import type { DiagramAst } from '@runiq/core';

const engine = new ElkLayoutEngine();

const diagram: DiagramAst = {
  astVersion: '1.0',
  nodes: [
    { id: 'A', shape: 'rounded', label: 'Start' },
    { id: 'B', shape: 'rect', label: 'Process' },
    { id: 'C', shape: 'rounded', label: 'End' },
  ],
  edges: [
    { from: 'A', to: 'B' },
    { from: 'B', to: 'C' },
  ],
};

const result = await engine.layout(diagram);

console.log(result.nodes); // Positioned nodes with x, y, width, height
console.log(result.edges); // Routed edges with points
console.log(result.size); // Overall diagram dimensions

Layout Options

// Direction
await engine.layout(diagram, { direction: 'LR' }); // Left-to-Right
await engine.layout(diagram, { direction: 'TB' }); // Top-to-Bottom (default)
await engine.layout(diagram, { direction: 'BT' }); // Bottom-to-Top
await engine.layout(diagram, { direction: 'RL' }); // Right-to-Left

// Spacing
await engine.layout(diagram, { spacing: 50 }); // Compact
await engine.layout(diagram, { spacing: 80 }); // Default
await engine.layout(diagram, { spacing: 150 }); // Spacious

// Combined
await engine.layout(diagram, {
  direction: 'LR',
  spacing: 100,
});

Result Structure

interface LaidOutDiagram {
  nodes: PositionedNode[];
  edges: RoutedEdge[];
  size: { width: number; height: number };
}

interface PositionedNode {
  id: string;
  x: number; // Top-left corner
  y: number; // Top-left corner
  width: number;
  height: number;
}

interface RoutedEdge {
  from: string;
  to: string;
  points: { x: number; y: number }[]; // Routing waypoints
}

ELK Algorithm Details

The layout engine uses ELK's layered algorithm by default, which is optimal for:

  • ✅ Flowcharts
  • ✅ Process diagrams
  • ✅ State machines
  • ✅ Hierarchical graphs
  • ✅ Decision trees

ELK Configuration

The engine automatically configures ELK with optimal settings:

{
  'elk.algorithm': 'layered',
  'elk.direction': 'DOWN',
  'elk.spacing.nodeNode': '80',
  'elk.layered.spacing.nodeNodeBetweenLayers': '80',
  'elk.layered.crossingMinimization.strategy': 'LAYER_SWEEP',
  'elk.layered.nodePlacement.strategy': 'NETWORK_SIMPLEX',
  'elk.padding': '[top=20,left=20,bottom=20,right=20]',
}

Migration from Dagre

If you were using the old Dagre-based layout engine, migration is straightforward:

Before (Dagre)

import { DagreLayoutEngine } from '@runiq/layout-base';

const engine = new DagreLayoutEngine();
const result = await engine.layout(diagram);

After (ELK)

import { ElkLayoutEngine } from '@runiq/layout-base';

const engine = new ElkLayoutEngine();
const result = await engine.layout(diagram);

Changes:

  • ✅ Better layout quality (fewer edge crossings)
  • ✅ More reliable edge routing
  • ✅ Better handling of complex branching
  • ✅ Support for manual positioning (future)
  • ⚠️ Bundle size increased: 50KB → 300KB (acceptable tradeoff)

Performance

Benchmarks on a modern laptop:

| Nodes | Layout Time | Notes | | ----- | ----------- | --------------------------- | | 10 | ~10ms | Instant | | 50 | ~50ms | Fast | | 100 | ~100ms | Still responsive | | 500+ | ~500ms+ | May want progress indicator |

ELK is efficient for most real-world diagrams (< 100 nodes).

Why ELK?

We migrated from Dagre to ELK in October 2025 because:

  1. Dagre is Abandoned - No updates since 2022, multiple known bugs
  2. Mermaid Migrated Too - Mermaid.js moved to ELK for the same reasons
  3. Better Quality - ELK produces superior layouts with fewer edge crossings
  4. Active Maintenance - ELK is backed by Eclipse Foundation
  5. More Features - Multiple algorithms, better configuration options
  6. Manual Positioning - Supports hybrid manual/auto layout (future feature)

Available Algorithms

ELK supports multiple layout algorithms via the algorithm option:

  • layered (default) - Hierarchical layout for flowcharts, processes
  • force - Force-directed layout for networks, organic graphs
  • stress - Stress minimization for similarity graphs
  • radial - Radial tree layout
  • mrtree - Multi-root tree layout

See Force-Directed Networks Guide for details.

Future Features

Planned enhancements:

  • 🔜 Hybrid Layout - Mix manual + automatic positioning
  • 🔜 Constraint System - Relative positioning (e.g., "B below A")
  • 🔜 Subgraph Support - Nested diagram containers
  • 🔜 Port Constraints - Control edge attachment points

API

ElkLayoutEngine

class ElkLayoutEngine implements LayoutEngine {
  id: string; // 'elk'
  supportsManualPositions: boolean; // true

  layout(diagram: DiagramAst, options?: LayoutOptions): Promise<LaidOutDiagram>;
}

LayoutOptions

interface LayoutOptions {
  direction?: 'TB' | 'BT' | 'LR' | 'RL'; // Layout direction
  spacing?: number; // Node spacing in pixels
}

Examples

Flowchart

const flowchart: DiagramAst = {
  astVersion: '1.0',
  nodes: [
    { id: 'Start', shape: 'rounded' },
    { id: 'Process', shape: 'rect' },
    { id: 'Decision', shape: 'rhombus' },
    { id: 'Action1', shape: 'rect' },
    { id: 'Action2', shape: 'rect' },
    { id: 'End', shape: 'rounded' },
  ],
  edges: [
    { from: 'Start', to: 'Process' },
    { from: 'Process', to: 'Decision' },
    { from: 'Decision', to: 'Action1', label: 'Yes' },
    { from: 'Decision', to: 'Action2', label: 'No' },
    { from: 'Action1', to: 'End' },
    { from: 'Action2', to: 'End' },
  ],
};

const layout = await engine.layout(flowchart);

Linear Chain

const chain: DiagramAst = {
  astVersion: '1.0',
  direction: 'LR',
  nodes: [
    { id: 'Step1', shape: 'rect', label: 'Input' },
    { id: 'Step2', shape: 'rect', label: 'Transform' },
    { id: 'Step3', shape: 'rect', label: 'Validate' },
    { id: 'Step4', shape: 'rect', label: 'Output' },
  ],
  edges: [
    { from: 'Step1', to: 'Step2' },
    { from: 'Step2', to: 'Step3' },
    { from: 'Step3', to: 'Step4' },
  ],
};

const layout = await engine.layout(chain, { spacing: 120 });

Complex Branching

const branches: DiagramAst = {
  astVersion: '1.0',
  nodes: Array.from({ length: 20 }, (_, i) => ({
    id: `Node${i}`,
    shape: 'rect',
  })),
  edges: [
    // Linear backbone
    ...Array.from({ length: 19 }, (_, i) => ({
      from: `Node${i}`,
      to: `Node${i + 1}`,
    })),
    // Cross-connections
    { from: 'Node0', to: 'Node5' },
    { from: 'Node5', to: 'Node10' },
    { from: 'Node10', to: 'Node15' },
  ],
};

const layout = await engine.layout(branches);

Testing

Run tests:

pnpm test

The package includes comprehensive tests:

  • ✅ Basic layouts (single, chain, branching)
  • ✅ Direction options (TB, LR, BT, RL)
  • ✅ Spacing configuration
  • ✅ Edge routing
  • ✅ Node labels
  • ✅ Different shape sizes
  • ✅ Error handling
  • ✅ Large diagrams (20-50 nodes)
  • ✅ Deterministic output
  • ✅ Edge crossing minimization

24 tests, all passing

Resources

  • ELK Documentation: https://www.eclipse.org/elk/
  • ELK.js (JavaScript wrapper): https://github.com/kieler/elkjs
  • ELK Algorithms: https://www.eclipse.org/elk/reference.html
  • Runiq Core Types: @runiq/core package

License

MIT

Contributing

Issues and PRs welcome! Please include tests for new features.

Changelog

v0.1.0 (October 2025)

  • ✅ Migrated from Dagre to ELK
  • ✅ Implemented layered algorithm
  • ✅ 24 comprehensive tests
  • ✅ Direction and spacing options
  • ✅ Superior layout quality
  • ✅ Faster performance on large diagrams