@ngx-km/layout
v0.0.3
Published
Layout calculation library for Angular graph visualization. Uses an abstracted engine architecture (default: ELK.js) that can be swapped for alternative layout engines.
Downloads
331
Readme
@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
npm install @ngx-km/layout elkjsBasic Usage
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
// 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
// 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
// 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:
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:
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
nx test ngx-layout