npm package: @ngx-km/layout
Description: Layout calculation library for Angular graph visualization. Uses an abstracted engine architecture (default: ELK.js) that can be swapped for alternative layout engines.
Latest version: 0.0.3

# @ngx-km/layout

Layout calculation library for Angular graph visualization. Uses an abstracted engine architecture (default: ELK.js) that can be swapped for alternative layout engines.

## Features

- Pluggable layout engine architecture
- ELK.js implementation included (default)
- Multiple layout algorithms: layered, force, stress, radial, tree
- Configurable spacing, direction, and padding
- Incremental layout support (add/remove nodes while preserving positions)
- Returns node coordinates only (no rendering)
- Validation of input graphs

## Installation

```bash
npm install @ngx-km/layout elkjs
```

## Basic Usage

```typescript
import { Component, inject } from '@angular/core';
import { LayoutService, LayoutNode, LayoutEdge } from '@ngx-km/layout';

@Component({
  providers: [LayoutService],
  // ...
})
export class MyComponent {
  private layoutService = inject(LayoutService);

  async calculateLayout() {
    const nodes: LayoutNode[] = [
      { id: 'a', width: 100, height: 50 },
      { id: 'b', width: 100, height: 50 },
      { id: 'c', width: 100, height: 50 },
    ];

    const edges: LayoutEdge[] = [
      { id: 'e1', sourceId: 'a', targetId: 'b' },
      { id: 'e2', sourceId: 'a', targetId: 'c' },
    ];

    const result = await this.layoutService.layout(nodes, edges, {
      algorithm: 'layered',
      direction: 'DOWN',
      nodeSpacing: 50,
      layerSpacing: 100,
    });

    // result.nodes contains positioned nodes with x, y coordinates
    console.log(result.nodes);
  }
}
```

## Incremental Layout

The library supports incremental layout operations for dynamic graphs:

### Adding Nodes

```typescript
// Start with initial layout
const initialResult = await layoutService.layout(nodes, edges);

// Add new nodes while preserving existing positions
const newNodes = [{ id: 'd', width: 100, height: 50 }];
const allEdges = [...edges, { id: 'e3', sourceId: 'c', targetId: 'd' }];

const updatedResult = await layoutService.addNodesToLayout(
  initialResult,
  newNodes,
  allEdges
);
```

### Removing Nodes

```typescript
// Remove nodes (connected edges are automatically filtered)
const result = await layoutService.removeNodesFromLayout(
  previousResult,
  ['nodeIdToRemove'],
  allEdges,
  options,
  true // preservePositions: remaining nodes keep their positions
);
```

### Recalculating with Position Preservation

```typescript
// Manually specify which positions to preserve
const existingPositions = new Map<string, { x: number; y: number }>();
existingPositions.set('a', { x: 100, y: 50 });
existingPositions.set('b', { x: 200, y: 50 });

const result = await layoutService.recalculateLayout(
  allNodes,
  allEdges,
  existingPositions,
  options
);
```

## Layout Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `algorithm` | `'layered' \| 'force' \| 'stress' \| 'radial' \| 'tree'` | `'layered'` | Layout algorithm |
| `direction` | `'DOWN' \| 'UP' \| 'RIGHT' \| 'LEFT'` | `'DOWN'` | Direction for hierarchical layouts |
| `nodeSpacing` | `number` | `50` | Horizontal spacing between nodes |
| `layerSpacing` | `number` | `50` | Vertical spacing between layers |
| `padding` | `number` | `20` | Padding around the graph |
| `considerEdges` | `boolean` | `true` | Whether edges affect layout |

## Edge Types

Edges can be directed (default) or bidirectional:

```typescript
const edges: LayoutEdge[] = [
  { id: 'e1', sourceId: 'a', targetId: 'b' },                    // directed (default)
  { id: 'e2', sourceId: 'b', targetId: 'c', type: 'directed' },  // explicitly directed
  { id: 'e3', sourceId: 'c', targetId: 'd', type: 'bidirectional' }, // two-way
];
```

## Custom Layout Engine

You can provide a custom layout engine by implementing the `LayoutEngine` interface:

```typescript
import { LAYOUT_ENGINE, LayoutEngine, LayoutInput, LayoutResult } from '@ngx-km/layout';

class CustomLayoutEngine implements LayoutEngine {
  readonly name = 'Custom';

  async calculateLayout(input: LayoutInput): Promise<LayoutResult> {
    // Your custom layout logic
  }

  dispose?(): void {
    // Optional cleanup
  }
}

// Provide in component or module
providers: [
  LayoutService,
  { provide: LAYOUT_ENGINE, useClass: CustomLayoutEngine }
]
```

## API Reference

### LayoutService Methods

| Method | Description |
|--------|-------------|
| `calculateLayout(input)` | Calculate layout from LayoutInput object |
| `layout(nodes, edges, options?)` | Convenience method with separate parameters |
| `calculateLayoutMap(input)` | Returns Map of node ID to position |
| `recalculateLayout(nodes, edges, existingPositions, options?)` | Recalculate with position preservation |
| `addNodesToLayout(previousResult, newNodes, allEdges, options?)` | Add nodes incrementally |
| `removeNodesFromLayout(previousResult, nodeIds, allEdges, options?, preservePositions?)` | Remove nodes |

### Types

| Type | Description |
|------|-------------|
| `LayoutNode` | Input node with id, width, height, optional fixedX/fixedY |
| `LayoutEdge` | Edge with id, sourceId, targetId, optional type |
| `LayoutOptions` | Configuration for layout algorithm |
| `LayoutResult` | Output with positioned nodes and graph dimensions |
| `LayoutNodeResult` | Positioned node with x, y coordinates |
| `LayoutEngine` | Interface for custom layout engines |

## Running unit tests

```bash
nx test ngx-layout
```