@ngx-km/layout 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,186 @@
1
+ # @ngx-km/layout
2
+
3
+ Layout calculation library for Angular graph visualization. Uses an abstracted engine architecture (default: ELK.js) that can be swapped for alternative layout engines.
4
+
5
+ ## Features
6
+
7
+ - Pluggable layout engine architecture
8
+ - ELK.js implementation included (default)
9
+ - Multiple layout algorithms: layered, force, stress, radial, tree
10
+ - Configurable spacing, direction, and padding
11
+ - Incremental layout support (add/remove nodes while preserving positions)
12
+ - Returns node coordinates only (no rendering)
13
+ - Validation of input graphs
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @ngx-km/layout elkjs
19
+ ```
20
+
21
+ ## Basic Usage
22
+
23
+ ```typescript
24
+ import { Component, inject } from '@angular/core';
25
+ import { LayoutService, LayoutNode, LayoutEdge } from '@ngx-km/layout';
26
+
27
+ @Component({
28
+ providers: [LayoutService],
29
+ // ...
30
+ })
31
+ export class MyComponent {
32
+ private layoutService = inject(LayoutService);
33
+
34
+ async calculateLayout() {
35
+ const nodes: LayoutNode[] = [
36
+ { id: 'a', width: 100, height: 50 },
37
+ { id: 'b', width: 100, height: 50 },
38
+ { id: 'c', width: 100, height: 50 },
39
+ ];
40
+
41
+ const edges: LayoutEdge[] = [
42
+ { id: 'e1', sourceId: 'a', targetId: 'b' },
43
+ { id: 'e2', sourceId: 'a', targetId: 'c' },
44
+ ];
45
+
46
+ const result = await this.layoutService.layout(nodes, edges, {
47
+ algorithm: 'layered',
48
+ direction: 'DOWN',
49
+ nodeSpacing: 50,
50
+ layerSpacing: 100,
51
+ });
52
+
53
+ // result.nodes contains positioned nodes with x, y coordinates
54
+ console.log(result.nodes);
55
+ }
56
+ }
57
+ ```
58
+
59
+ ## Incremental Layout
60
+
61
+ The library supports incremental layout operations for dynamic graphs:
62
+
63
+ ### Adding Nodes
64
+
65
+ ```typescript
66
+ // Start with initial layout
67
+ const initialResult = await layoutService.layout(nodes, edges);
68
+
69
+ // Add new nodes while preserving existing positions
70
+ const newNodes = [{ id: 'd', width: 100, height: 50 }];
71
+ const allEdges = [...edges, { id: 'e3', sourceId: 'c', targetId: 'd' }];
72
+
73
+ const updatedResult = await layoutService.addNodesToLayout(
74
+ initialResult,
75
+ newNodes,
76
+ allEdges
77
+ );
78
+ ```
79
+
80
+ ### Removing Nodes
81
+
82
+ ```typescript
83
+ // Remove nodes (connected edges are automatically filtered)
84
+ const result = await layoutService.removeNodesFromLayout(
85
+ previousResult,
86
+ ['nodeIdToRemove'],
87
+ allEdges,
88
+ options,
89
+ true // preservePositions: remaining nodes keep their positions
90
+ );
91
+ ```
92
+
93
+ ### Recalculating with Position Preservation
94
+
95
+ ```typescript
96
+ // Manually specify which positions to preserve
97
+ const existingPositions = new Map<string, { x: number; y: number }>();
98
+ existingPositions.set('a', { x: 100, y: 50 });
99
+ existingPositions.set('b', { x: 200, y: 50 });
100
+
101
+ const result = await layoutService.recalculateLayout(
102
+ allNodes,
103
+ allEdges,
104
+ existingPositions,
105
+ options
106
+ );
107
+ ```
108
+
109
+ ## Layout Options
110
+
111
+ | Option | Type | Default | Description |
112
+ |--------|------|---------|-------------|
113
+ | `algorithm` | `'layered' \| 'force' \| 'stress' \| 'radial' \| 'tree'` | `'layered'` | Layout algorithm |
114
+ | `direction` | `'DOWN' \| 'UP' \| 'RIGHT' \| 'LEFT'` | `'DOWN'` | Direction for hierarchical layouts |
115
+ | `nodeSpacing` | `number` | `50` | Horizontal spacing between nodes |
116
+ | `layerSpacing` | `number` | `50` | Vertical spacing between layers |
117
+ | `padding` | `number` | `20` | Padding around the graph |
118
+ | `considerEdges` | `boolean` | `true` | Whether edges affect layout |
119
+
120
+ ## Edge Types
121
+
122
+ Edges can be directed (default) or bidirectional:
123
+
124
+ ```typescript
125
+ const edges: LayoutEdge[] = [
126
+ { id: 'e1', sourceId: 'a', targetId: 'b' }, // directed (default)
127
+ { id: 'e2', sourceId: 'b', targetId: 'c', type: 'directed' }, // explicitly directed
128
+ { id: 'e3', sourceId: 'c', targetId: 'd', type: 'bidirectional' }, // two-way
129
+ ];
130
+ ```
131
+
132
+ ## Custom Layout Engine
133
+
134
+ You can provide a custom layout engine by implementing the `LayoutEngine` interface:
135
+
136
+ ```typescript
137
+ import { LAYOUT_ENGINE, LayoutEngine, LayoutInput, LayoutResult } from '@ngx-km/layout';
138
+
139
+ class CustomLayoutEngine implements LayoutEngine {
140
+ readonly name = 'Custom';
141
+
142
+ async calculateLayout(input: LayoutInput): Promise<LayoutResult> {
143
+ // Your custom layout logic
144
+ }
145
+
146
+ dispose?(): void {
147
+ // Optional cleanup
148
+ }
149
+ }
150
+
151
+ // Provide in component or module
152
+ providers: [
153
+ LayoutService,
154
+ { provide: LAYOUT_ENGINE, useClass: CustomLayoutEngine }
155
+ ]
156
+ ```
157
+
158
+ ## API Reference
159
+
160
+ ### LayoutService Methods
161
+
162
+ | Method | Description |
163
+ |--------|-------------|
164
+ | `calculateLayout(input)` | Calculate layout from LayoutInput object |
165
+ | `layout(nodes, edges, options?)` | Convenience method with separate parameters |
166
+ | `calculateLayoutMap(input)` | Returns Map of node ID to position |
167
+ | `recalculateLayout(nodes, edges, existingPositions, options?)` | Recalculate with position preservation |
168
+ | `addNodesToLayout(previousResult, newNodes, allEdges, options?)` | Add nodes incrementally |
169
+ | `removeNodesFromLayout(previousResult, nodeIds, allEdges, options?, preservePositions?)` | Remove nodes |
170
+
171
+ ### Types
172
+
173
+ | Type | Description |
174
+ |------|-------------|
175
+ | `LayoutNode` | Input node with id, width, height, optional fixedX/fixedY |
176
+ | `LayoutEdge` | Edge with id, sourceId, targetId, optional type |
177
+ | `LayoutOptions` | Configuration for layout algorithm |
178
+ | `LayoutResult` | Output with positioned nodes and graph dimensions |
179
+ | `LayoutNodeResult` | Positioned node with x, y coordinates |
180
+ | `LayoutEngine` | Interface for custom layout engines |
181
+
182
+ ## Running unit tests
183
+
184
+ ```bash
185
+ nx test ngx-layout
186
+ ```
@@ -0,0 +1,429 @@
1
+ import ELK from 'elkjs/lib/elk.bundled.js';
2
+ import * as i0 from '@angular/core';
3
+ import { InjectionToken, inject, Injectable } from '@angular/core';
4
+
5
+ /**
6
+ * Layout Library Models
7
+ *
8
+ * These interfaces are engine-agnostic and define the contract
9
+ * for any layout engine implementation (ELK, dagre, etc.)
10
+ */
11
+ /**
12
+ * Default layout options
13
+ */
14
+ const DEFAULT_LAYOUT_OPTIONS = {
15
+ algorithm: 'layered',
16
+ direction: 'DOWN',
17
+ nodeSpacing: 50,
18
+ layerSpacing: 50,
19
+ padding: 20,
20
+ considerEdges: true,
21
+ };
22
+
23
+ /**
24
+ * ELK algorithm identifiers
25
+ * Maps our abstract algorithm types to ELK-specific algorithm IDs
26
+ */
27
+ const ELK_ALGORITHMS = {
28
+ layered: 'layered',
29
+ force: 'force',
30
+ stress: 'stress',
31
+ radial: 'radial',
32
+ tree: 'mrtree',
33
+ };
34
+ /**
35
+ * ELK direction values
36
+ * Maps our direction type to ELK-specific direction values
37
+ */
38
+ const ELK_DIRECTIONS = {
39
+ DOWN: 'DOWN',
40
+ UP: 'UP',
41
+ RIGHT: 'RIGHT',
42
+ LEFT: 'LEFT',
43
+ };
44
+ /**
45
+ * ELK.js layout engine implementation
46
+ *
47
+ * Uses ELK.js (Eclipse Layout Kernel) for graph layout calculation.
48
+ * This is a high-quality layout library that supports multiple algorithms.
49
+ */
50
+ class ElkLayoutEngine {
51
+ name = 'ELK';
52
+ elk;
53
+ constructor() {
54
+ this.elk = new ELK();
55
+ }
56
+ async calculateLayout(input) {
57
+ const options = { ...DEFAULT_LAYOUT_OPTIONS, ...input.options };
58
+ // Build ELK graph structure
59
+ const elkGraph = this.buildElkGraph(input, options);
60
+ // Calculate layout
61
+ const layoutedGraph = await this.elk.layout(elkGraph);
62
+ // Extract results
63
+ return this.extractResults(layoutedGraph);
64
+ }
65
+ dispose() {
66
+ // ELK doesn't require explicit cleanup, but we provide
67
+ // this method for consistency with the interface
68
+ }
69
+ /**
70
+ * Convert our abstract input to ELK-specific graph structure
71
+ */
72
+ buildElkGraph(input, options) {
73
+ const elkOptions = this.buildElkOptions(options);
74
+ // Build nodes
75
+ const children = input.nodes.map((node) => {
76
+ const elkNode = {
77
+ id: node.id,
78
+ width: node.width,
79
+ height: node.height,
80
+ };
81
+ // Handle fixed positions
82
+ if (node.fixedX !== undefined && node.fixedY !== undefined) {
83
+ elkNode.x = node.fixedX;
84
+ elkNode.y = node.fixedY;
85
+ // Mark as fixed in ELK
86
+ elkNode.layoutOptions = {
87
+ 'elk.position': `(${node.fixedX}, ${node.fixedY})`,
88
+ };
89
+ }
90
+ return elkNode;
91
+ });
92
+ // Build edges (handling bidirectional edges)
93
+ const edges = options.considerEdges
94
+ ? this.buildElkEdges(input.edges)
95
+ : [];
96
+ return {
97
+ id: 'root',
98
+ layoutOptions: elkOptions,
99
+ children,
100
+ edges,
101
+ };
102
+ }
103
+ /**
104
+ * Convert layout edges to ELK edges, expanding bidirectional edges
105
+ */
106
+ buildElkEdges(edges) {
107
+ const elkEdges = [];
108
+ for (const edge of edges) {
109
+ // Add the primary edge (source → target)
110
+ elkEdges.push({
111
+ id: edge.id,
112
+ sources: [edge.sourceId],
113
+ targets: [edge.targetId],
114
+ });
115
+ // For bidirectional edges, add reverse edge (target → source)
116
+ if (edge.type === 'bidirectional') {
117
+ elkEdges.push({
118
+ id: `${edge.id}_reverse`,
119
+ sources: [edge.targetId],
120
+ targets: [edge.sourceId],
121
+ });
122
+ }
123
+ }
124
+ return elkEdges;
125
+ }
126
+ /**
127
+ * Convert our abstract options to ELK-specific layout options
128
+ */
129
+ buildElkOptions(options) {
130
+ const elkOptions = {
131
+ 'elk.algorithm': ELK_ALGORITHMS[options.algorithm],
132
+ 'elk.spacing.nodeNode': String(options.nodeSpacing),
133
+ 'elk.padding': `[top=${options.padding}, left=${options.padding}, bottom=${options.padding}, right=${options.padding}]`,
134
+ };
135
+ // Algorithm-specific options
136
+ switch (options.algorithm) {
137
+ case 'layered':
138
+ elkOptions['elk.direction'] = ELK_DIRECTIONS[options.direction];
139
+ elkOptions['elk.layered.spacing.nodeNodeBetweenLayers'] = String(options.layerSpacing);
140
+ // Improve edge routing
141
+ elkOptions['elk.layered.spacing.edgeNodeBetweenLayers'] = String(options.layerSpacing / 2);
142
+ break;
143
+ case 'tree':
144
+ elkOptions['elk.direction'] = ELK_DIRECTIONS[options.direction];
145
+ elkOptions['elk.mrtree.weighting'] = 'CONSTRAINT';
146
+ break;
147
+ case 'force':
148
+ // Force-directed doesn't use direction
149
+ elkOptions['elk.force.iterations'] = '300';
150
+ break;
151
+ case 'stress':
152
+ // Stress-based layout options
153
+ elkOptions['elk.stress.desiredEdgeLength'] = String(options.nodeSpacing * 2);
154
+ break;
155
+ case 'radial':
156
+ // Radial layout options
157
+ elkOptions['elk.radial.radius'] = String(options.layerSpacing);
158
+ break;
159
+ }
160
+ return elkOptions;
161
+ }
162
+ /**
163
+ * Extract layout results from ELK's output
164
+ */
165
+ extractResults(elkGraph) {
166
+ const nodes = (elkGraph.children || []).map((child) => ({
167
+ id: child.id,
168
+ x: child.x ?? 0,
169
+ y: child.y ?? 0,
170
+ width: child.width ?? 0,
171
+ height: child.height ?? 0,
172
+ }));
173
+ return {
174
+ nodes,
175
+ width: elkGraph.width ?? 0,
176
+ height: elkGraph.height ?? 0,
177
+ };
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Injection token for providing a custom layout engine
183
+ * Use this to swap the default ELK engine with a different implementation
184
+ *
185
+ * @example
186
+ * ```typescript
187
+ * providers: [
188
+ * { provide: LAYOUT_ENGINE, useClass: CustomLayoutEngine }
189
+ * ]
190
+ * ```
191
+ */
192
+ const LAYOUT_ENGINE = new InjectionToken('LAYOUT_ENGINE');
193
+ /**
194
+ * Factory function to create the default layout engine
195
+ */
196
+ function defaultLayoutEngineFactory() {
197
+ return new ElkLayoutEngine();
198
+ }
199
+ /**
200
+ * Layout Service
201
+ *
202
+ * Provides graph layout calculation using a pluggable layout engine.
203
+ * By default uses ELK.js, but can be configured to use any engine
204
+ * that implements the LayoutEngine interface.
205
+ *
206
+ * @example
207
+ * ```typescript
208
+ * // Basic usage
209
+ * const result = await layoutService.calculateLayout({
210
+ * nodes: [
211
+ * { id: 'a', width: 100, height: 50 },
212
+ * { id: 'b', width: 100, height: 50 },
213
+ * ],
214
+ * edges: [
215
+ * { id: 'e1', sourceId: 'a', targetId: 'b' }
216
+ * ],
217
+ * options: { algorithm: 'layered', direction: 'DOWN' }
218
+ * });
219
+ * ```
220
+ */
221
+ class LayoutService {
222
+ engine;
223
+ constructor() {
224
+ // Try to inject a custom engine, fall back to default ELK engine
225
+ const injectedEngine = inject(LAYOUT_ENGINE, { optional: true });
226
+ this.engine = injectedEngine ?? defaultLayoutEngineFactory();
227
+ }
228
+ /**
229
+ * Get the name of the current layout engine
230
+ */
231
+ get engineName() {
232
+ return this.engine.name;
233
+ }
234
+ /**
235
+ * Calculate layout positions for nodes based on their relationships
236
+ *
237
+ * @param input Layout input containing nodes, edges, and options
238
+ * @returns Promise resolving to layout result with positioned nodes
239
+ */
240
+ async calculateLayout(input) {
241
+ // Validate input
242
+ this.validateInput(input);
243
+ return this.engine.calculateLayout(input);
244
+ }
245
+ /**
246
+ * Convenience method to calculate layout from separate parameters
247
+ *
248
+ * @param nodes Nodes to layout
249
+ * @param edges Edges/relationships between nodes
250
+ * @param options Layout options
251
+ * @returns Promise resolving to layout result
252
+ */
253
+ async layout(nodes, edges, options) {
254
+ return this.calculateLayout({ nodes, edges, options });
255
+ }
256
+ /**
257
+ * Calculate layout and return a map of node positions by ID
258
+ * Useful for quick position lookups
259
+ *
260
+ * @param input Layout input
261
+ * @returns Promise resolving to a Map of node ID to position
262
+ */
263
+ async calculateLayoutMap(input) {
264
+ const result = await this.calculateLayout(input);
265
+ const positionMap = new Map();
266
+ for (const node of result.nodes) {
267
+ positionMap.set(node.id, { x: node.x, y: node.y });
268
+ }
269
+ return positionMap;
270
+ }
271
+ /**
272
+ * Recalculate layout while preserving positions of existing nodes
273
+ *
274
+ * This is useful when adding new nodes to an existing layout.
275
+ * Existing nodes will keep their positions (as fixed points),
276
+ * and only new nodes will be positioned by the layout algorithm.
277
+ *
278
+ * @param nodes All nodes (existing + new)
279
+ * @param edges All edges
280
+ * @param existingPositions Map of existing node IDs to their positions
281
+ * @param options Layout options
282
+ * @returns Promise resolving to layout result
283
+ *
284
+ * @example
285
+ * ```typescript
286
+ * // Add a new node to existing layout
287
+ * const newNodes = [...existingNodes, { id: 'new', width: 100, height: 50 }];
288
+ * const result = await layoutService.recalculateLayout(
289
+ * newNodes,
290
+ * edges,
291
+ * existingPositions, // Map from previous layout
292
+ * options
293
+ * );
294
+ * ```
295
+ */
296
+ async recalculateLayout(nodes, edges, existingPositions, options) {
297
+ // Apply fixed positions to existing nodes
298
+ const nodesWithFixedPositions = nodes.map((node) => {
299
+ const existingPos = existingPositions.get(node.id);
300
+ if (existingPos) {
301
+ return {
302
+ ...node,
303
+ fixedX: existingPos.x,
304
+ fixedY: existingPos.y,
305
+ };
306
+ }
307
+ return node;
308
+ });
309
+ return this.calculateLayout({
310
+ nodes: nodesWithFixedPositions,
311
+ edges,
312
+ options,
313
+ });
314
+ }
315
+ /**
316
+ * Calculate incremental layout for new nodes only
317
+ *
318
+ * Takes a previous layout result and adds new nodes to it.
319
+ * Existing nodes keep their positions, new nodes are positioned.
320
+ *
321
+ * @param previousResult Previous layout result
322
+ * @param newNodes New nodes to add
323
+ * @param allEdges All edges (including edges for new nodes)
324
+ * @param options Layout options
325
+ * @returns Promise resolving to updated layout result
326
+ */
327
+ async addNodesToLayout(previousResult, newNodes, allEdges, options) {
328
+ // Build position map from previous result
329
+ const existingPositions = new Map();
330
+ for (const node of previousResult.nodes) {
331
+ existingPositions.set(node.id, { x: node.x, y: node.y });
332
+ }
333
+ // Combine existing nodes (with dimensions) and new nodes
334
+ const existingNodes = previousResult.nodes.map((n) => ({
335
+ id: n.id,
336
+ width: n.width,
337
+ height: n.height,
338
+ }));
339
+ const allNodes = [...existingNodes, ...newNodes];
340
+ return this.recalculateLayout(allNodes, allEdges, existingPositions, options);
341
+ }
342
+ /**
343
+ * Remove nodes from layout and recalculate
344
+ *
345
+ * Removes specified nodes and their connected edges,
346
+ * then recalculates layout for remaining nodes.
347
+ *
348
+ * @param previousResult Previous layout result
349
+ * @param nodeIdsToRemove IDs of nodes to remove
350
+ * @param allEdges All edges (will be filtered to remove orphaned edges)
351
+ * @param options Layout options
352
+ * @param preservePositions If true, remaining nodes keep their positions
353
+ * @returns Promise resolving to updated layout result
354
+ */
355
+ async removeNodesFromLayout(previousResult, nodeIdsToRemove, allEdges, options, preservePositions = true) {
356
+ const removeSet = new Set(nodeIdsToRemove);
357
+ // Filter out removed nodes
358
+ const remainingNodes = previousResult.nodes
359
+ .filter((n) => !removeSet.has(n.id))
360
+ .map((n) => ({
361
+ id: n.id,
362
+ width: n.width,
363
+ height: n.height,
364
+ }));
365
+ // Filter out edges connected to removed nodes
366
+ const remainingEdges = allEdges.filter((e) => !removeSet.has(e.sourceId) && !removeSet.has(e.targetId));
367
+ if (remainingNodes.length === 0) {
368
+ return { nodes: [], width: 0, height: 0 };
369
+ }
370
+ if (preservePositions) {
371
+ const existingPositions = new Map();
372
+ for (const node of previousResult.nodes) {
373
+ if (!removeSet.has(node.id)) {
374
+ existingPositions.set(node.id, { x: node.x, y: node.y });
375
+ }
376
+ }
377
+ return this.recalculateLayout(remainingNodes, remainingEdges, existingPositions, options);
378
+ }
379
+ return this.calculateLayout({
380
+ nodes: remainingNodes,
381
+ edges: remainingEdges,
382
+ options,
383
+ });
384
+ }
385
+ ngOnDestroy() {
386
+ this.engine.dispose?.();
387
+ }
388
+ /**
389
+ * Validate layout input
390
+ */
391
+ validateInput(input) {
392
+ if (!input.nodes || input.nodes.length === 0) {
393
+ throw new Error('LayoutService: At least one node is required');
394
+ }
395
+ // Check for duplicate node IDs
396
+ const nodeIds = new Set();
397
+ for (const node of input.nodes) {
398
+ if (nodeIds.has(node.id)) {
399
+ throw new Error(`LayoutService: Duplicate node ID "${node.id}"`);
400
+ }
401
+ nodeIds.add(node.id);
402
+ }
403
+ // Validate edges reference existing nodes
404
+ if (input.edges) {
405
+ for (const edge of input.edges) {
406
+ if (!nodeIds.has(edge.sourceId)) {
407
+ throw new Error(`LayoutService: Edge "${edge.id}" references unknown source node "${edge.sourceId}"`);
408
+ }
409
+ if (!nodeIds.has(edge.targetId)) {
410
+ throw new Error(`LayoutService: Edge "${edge.id}" references unknown target node "${edge.targetId}"`);
411
+ }
412
+ }
413
+ }
414
+ }
415
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: LayoutService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
416
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: LayoutService });
417
+ }
418
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: LayoutService, decorators: [{
419
+ type: Injectable
420
+ }], ctorParameters: () => [] });
421
+
422
+ // Models - Values
423
+
424
+ /**
425
+ * Generated bundle index. Do not edit.
426
+ */
427
+
428
+ export { DEFAULT_LAYOUT_OPTIONS, ElkLayoutEngine, LAYOUT_ENGINE, LayoutService, defaultLayoutEngineFactory };
429
+ //# sourceMappingURL=ngx-km-layout.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngx-km-layout.mjs","sources":["../../../../libs/ngx-km-layout/src/lib/models/layout.models.ts","../../../../libs/ngx-km-layout/src/lib/engines/elk-layout-engine.ts","../../../../libs/ngx-km-layout/src/lib/services/layout.service.ts","../../../../libs/ngx-km-layout/src/index.ts","../../../../libs/ngx-km-layout/src/ngx-km-layout.ts"],"sourcesContent":["/**\n * Layout Library Models\n *\n * These interfaces are engine-agnostic and define the contract\n * for any layout engine implementation (ELK, dagre, etc.)\n */\n\n/**\n * Direction for hierarchical/layered layouts\n */\nexport type LayoutDirection = 'DOWN' | 'UP' | 'RIGHT' | 'LEFT';\n\n/**\n * Available layout algorithm types\n * These are abstract algorithm categories that map to specific engine implementations\n */\nexport type LayoutAlgorithm =\n | 'layered' // Hierarchical/layered layout (Sugiyama-style)\n | 'force' // Force-directed layout\n | 'stress' // Stress-minimization layout\n | 'radial' // Radial tree layout\n | 'tree'; // Simple tree layout\n\n/**\n * Input node definition for layout calculation\n */\nexport interface LayoutNode {\n /** Unique identifier for the node */\n id: string;\n /** Width of the node in pixels */\n width: number;\n /** Height of the node in pixels */\n height: number;\n /** Optional: Fixed X position (node won't be moved if set) */\n fixedX?: number;\n /** Optional: Fixed Y position (node won't be moved if set) */\n fixedY?: number;\n}\n\n/**\n * Edge type for layout calculation\n * - 'directed': One-way relationship (source → target)\n * - 'bidirectional': Two-way relationship (source ↔ target)\n */\nexport type EdgeType = 'directed' | 'bidirectional';\n\n/**\n * Edge/relationship between nodes for layout calculation\n */\nexport interface LayoutEdge {\n /** Unique identifier for the edge */\n id: string;\n /** Source node ID */\n sourceId: string;\n /** Target node ID */\n targetId: string;\n /** Edge type (default: 'directed') */\n type?: EdgeType;\n}\n\n/**\n * Configuration options for layout calculation\n */\nexport interface LayoutOptions {\n /** Layout algorithm to use (default: 'layered') */\n algorithm?: LayoutAlgorithm;\n\n /** Direction for hierarchical layouts (default: 'DOWN') */\n direction?: LayoutDirection;\n\n /** Horizontal spacing between nodes in pixels (default: 50) */\n nodeSpacing?: number;\n\n /** Vertical spacing between layers in pixels (default: 50) */\n layerSpacing?: number;\n\n /** Padding around the entire graph in pixels (default: 20) */\n padding?: number;\n\n /** Whether edges should be considered for layout (default: true) */\n considerEdges?: boolean;\n}\n\n/**\n * Result of layout calculation for a single node\n */\nexport interface LayoutNodeResult {\n /** Node ID */\n id: string;\n /** Calculated X position (top-left corner) */\n x: number;\n /** Calculated Y position (top-left corner) */\n y: number;\n /** Node width (same as input) */\n width: number;\n /** Node height (same as input) */\n height: number;\n}\n\n/**\n * Complete result of layout calculation\n */\nexport interface LayoutResult {\n /** Positioned nodes */\n nodes: LayoutNodeResult[];\n /** Total width of the laid out graph */\n width: number;\n /** Total height of the laid out graph */\n height: number;\n}\n\n/**\n * Input data for layout calculation\n */\nexport interface LayoutInput {\n /** Nodes to layout */\n nodes: LayoutNode[];\n /** Edges/relationships between nodes */\n edges: LayoutEdge[];\n /** Layout options */\n options?: LayoutOptions;\n}\n\n/**\n * Default layout options\n */\nexport const DEFAULT_LAYOUT_OPTIONS: Required<LayoutOptions> = {\n algorithm: 'layered',\n direction: 'DOWN',\n nodeSpacing: 50,\n layerSpacing: 50,\n padding: 20,\n considerEdges: true,\n};\n","import ELK, { ElkNode, ElkExtendedEdge, LayoutOptions as ElkLayoutOptions } from 'elkjs/lib/elk.bundled.js';\nimport { LayoutEngine } from './layout-engine';\nimport {\n LayoutInput,\n LayoutResult,\n LayoutNodeResult,\n LayoutOptions,\n LayoutAlgorithm,\n LayoutDirection,\n LayoutEdge,\n DEFAULT_LAYOUT_OPTIONS,\n} from '../models/layout.models';\n\n/**\n * ELK algorithm identifiers\n * Maps our abstract algorithm types to ELK-specific algorithm IDs\n */\nconst ELK_ALGORITHMS: Record<LayoutAlgorithm, string> = {\n layered: 'layered',\n force: 'force',\n stress: 'stress',\n radial: 'radial',\n tree: 'mrtree',\n};\n\n/**\n * ELK direction values\n * Maps our direction type to ELK-specific direction values\n */\nconst ELK_DIRECTIONS: Record<LayoutDirection, string> = {\n DOWN: 'DOWN',\n UP: 'UP',\n RIGHT: 'RIGHT',\n LEFT: 'LEFT',\n};\n\n/**\n * ELK.js layout engine implementation\n *\n * Uses ELK.js (Eclipse Layout Kernel) for graph layout calculation.\n * This is a high-quality layout library that supports multiple algorithms.\n */\nexport class ElkLayoutEngine implements LayoutEngine {\n readonly name = 'ELK';\n\n private elk: InstanceType<typeof ELK>;\n\n constructor() {\n this.elk = new ELK();\n }\n\n async calculateLayout(input: LayoutInput): Promise<LayoutResult> {\n const options = { ...DEFAULT_LAYOUT_OPTIONS, ...input.options };\n\n // Build ELK graph structure\n const elkGraph = this.buildElkGraph(input, options);\n\n // Calculate layout\n const layoutedGraph = await this.elk.layout(elkGraph);\n\n // Extract results\n return this.extractResults(layoutedGraph);\n }\n\n dispose(): void {\n // ELK doesn't require explicit cleanup, but we provide\n // this method for consistency with the interface\n }\n\n /**\n * Convert our abstract input to ELK-specific graph structure\n */\n private buildElkGraph(\n input: LayoutInput,\n options: Required<LayoutOptions>\n ): ElkNode {\n const elkOptions = this.buildElkOptions(options);\n\n // Build nodes\n const children: ElkNode[] = input.nodes.map((node) => {\n const elkNode: ElkNode = {\n id: node.id,\n width: node.width,\n height: node.height,\n };\n\n // Handle fixed positions\n if (node.fixedX !== undefined && node.fixedY !== undefined) {\n elkNode.x = node.fixedX;\n elkNode.y = node.fixedY;\n // Mark as fixed in ELK\n elkNode.layoutOptions = {\n 'elk.position': `(${node.fixedX}, ${node.fixedY})`,\n };\n }\n\n return elkNode;\n });\n\n // Build edges (handling bidirectional edges)\n const edges: ElkExtendedEdge[] = options.considerEdges\n ? this.buildElkEdges(input.edges)\n : [];\n\n return {\n id: 'root',\n layoutOptions: elkOptions,\n children,\n edges,\n };\n }\n\n /**\n * Convert layout edges to ELK edges, expanding bidirectional edges\n */\n private buildElkEdges(edges: LayoutEdge[]): ElkExtendedEdge[] {\n const elkEdges: ElkExtendedEdge[] = [];\n\n for (const edge of edges) {\n // Add the primary edge (source → target)\n elkEdges.push({\n id: edge.id,\n sources: [edge.sourceId],\n targets: [edge.targetId],\n });\n\n // For bidirectional edges, add reverse edge (target → source)\n if (edge.type === 'bidirectional') {\n elkEdges.push({\n id: `${edge.id}_reverse`,\n sources: [edge.targetId],\n targets: [edge.sourceId],\n });\n }\n }\n\n return elkEdges;\n }\n\n /**\n * Convert our abstract options to ELK-specific layout options\n */\n private buildElkOptions(options: Required<LayoutOptions>): ElkLayoutOptions {\n const elkOptions: ElkLayoutOptions = {\n 'elk.algorithm': ELK_ALGORITHMS[options.algorithm],\n 'elk.spacing.nodeNode': String(options.nodeSpacing),\n 'elk.padding': `[top=${options.padding}, left=${options.padding}, bottom=${options.padding}, right=${options.padding}]`,\n };\n\n // Algorithm-specific options\n switch (options.algorithm) {\n case 'layered':\n elkOptions['elk.direction'] = ELK_DIRECTIONS[options.direction];\n elkOptions['elk.layered.spacing.nodeNodeBetweenLayers'] = String(\n options.layerSpacing\n );\n // Improve edge routing\n elkOptions['elk.layered.spacing.edgeNodeBetweenLayers'] = String(\n options.layerSpacing / 2\n );\n break;\n\n case 'tree':\n elkOptions['elk.direction'] = ELK_DIRECTIONS[options.direction];\n elkOptions['elk.mrtree.weighting'] = 'CONSTRAINT';\n break;\n\n case 'force':\n // Force-directed doesn't use direction\n elkOptions['elk.force.iterations'] = '300';\n break;\n\n case 'stress':\n // Stress-based layout options\n elkOptions['elk.stress.desiredEdgeLength'] = String(\n options.nodeSpacing * 2\n );\n break;\n\n case 'radial':\n // Radial layout options\n elkOptions['elk.radial.radius'] = String(options.layerSpacing);\n break;\n }\n\n return elkOptions;\n }\n\n /**\n * Extract layout results from ELK's output\n */\n private extractResults(elkGraph: ElkNode): LayoutResult {\n const nodes: LayoutNodeResult[] = (elkGraph.children || []).map((child) => ({\n id: child.id,\n x: child.x ?? 0,\n y: child.y ?? 0,\n width: child.width ?? 0,\n height: child.height ?? 0,\n }));\n\n return {\n nodes,\n width: elkGraph.width ?? 0,\n height: elkGraph.height ?? 0,\n };\n }\n}\n","import { Injectable, InjectionToken, inject, OnDestroy } from '@angular/core';\nimport { LayoutEngine } from '../engines/layout-engine';\nimport { ElkLayoutEngine } from '../engines/elk-layout-engine';\nimport {\n LayoutInput,\n LayoutResult,\n LayoutNode,\n LayoutNodeResult,\n LayoutEdge,\n LayoutOptions,\n} from '../models/layout.models';\n\n/**\n * Injection token for providing a custom layout engine\n * Use this to swap the default ELK engine with a different implementation\n *\n * @example\n * ```typescript\n * providers: [\n * { provide: LAYOUT_ENGINE, useClass: CustomLayoutEngine }\n * ]\n * ```\n */\nexport const LAYOUT_ENGINE = new InjectionToken<LayoutEngine>('LAYOUT_ENGINE');\n\n/**\n * Factory function to create the default layout engine\n */\nexport function defaultLayoutEngineFactory(): LayoutEngine {\n return new ElkLayoutEngine();\n}\n\n/**\n * Layout Service\n *\n * Provides graph layout calculation using a pluggable layout engine.\n * By default uses ELK.js, but can be configured to use any engine\n * that implements the LayoutEngine interface.\n *\n * @example\n * ```typescript\n * // Basic usage\n * const result = await layoutService.calculateLayout({\n * nodes: [\n * { id: 'a', width: 100, height: 50 },\n * { id: 'b', width: 100, height: 50 },\n * ],\n * edges: [\n * { id: 'e1', sourceId: 'a', targetId: 'b' }\n * ],\n * options: { algorithm: 'layered', direction: 'DOWN' }\n * });\n * ```\n */\n@Injectable()\nexport class LayoutService implements OnDestroy {\n private engine: LayoutEngine;\n\n constructor() {\n // Try to inject a custom engine, fall back to default ELK engine\n const injectedEngine = inject(LAYOUT_ENGINE, { optional: true });\n this.engine = injectedEngine ?? defaultLayoutEngineFactory();\n }\n\n /**\n * Get the name of the current layout engine\n */\n get engineName(): string {\n return this.engine.name;\n }\n\n /**\n * Calculate layout positions for nodes based on their relationships\n *\n * @param input Layout input containing nodes, edges, and options\n * @returns Promise resolving to layout result with positioned nodes\n */\n async calculateLayout(input: LayoutInput): Promise<LayoutResult> {\n // Validate input\n this.validateInput(input);\n\n return this.engine.calculateLayout(input);\n }\n\n /**\n * Convenience method to calculate layout from separate parameters\n *\n * @param nodes Nodes to layout\n * @param edges Edges/relationships between nodes\n * @param options Layout options\n * @returns Promise resolving to layout result\n */\n async layout(\n nodes: LayoutNode[],\n edges: LayoutEdge[],\n options?: LayoutOptions\n ): Promise<LayoutResult> {\n return this.calculateLayout({ nodes, edges, options });\n }\n\n /**\n * Calculate layout and return a map of node positions by ID\n * Useful for quick position lookups\n *\n * @param input Layout input\n * @returns Promise resolving to a Map of node ID to position\n */\n async calculateLayoutMap(\n input: LayoutInput\n ): Promise<Map<string, { x: number; y: number }>> {\n const result = await this.calculateLayout(input);\n\n const positionMap = new Map<string, { x: number; y: number }>();\n for (const node of result.nodes) {\n positionMap.set(node.id, { x: node.x, y: node.y });\n }\n\n return positionMap;\n }\n\n /**\n * Recalculate layout while preserving positions of existing nodes\n *\n * This is useful when adding new nodes to an existing layout.\n * Existing nodes will keep their positions (as fixed points),\n * and only new nodes will be positioned by the layout algorithm.\n *\n * @param nodes All nodes (existing + new)\n * @param edges All edges\n * @param existingPositions Map of existing node IDs to their positions\n * @param options Layout options\n * @returns Promise resolving to layout result\n *\n * @example\n * ```typescript\n * // Add a new node to existing layout\n * const newNodes = [...existingNodes, { id: 'new', width: 100, height: 50 }];\n * const result = await layoutService.recalculateLayout(\n * newNodes,\n * edges,\n * existingPositions, // Map from previous layout\n * options\n * );\n * ```\n */\n async recalculateLayout(\n nodes: LayoutNode[],\n edges: LayoutEdge[],\n existingPositions: Map<string, { x: number; y: number }>,\n options?: LayoutOptions\n ): Promise<LayoutResult> {\n // Apply fixed positions to existing nodes\n const nodesWithFixedPositions = nodes.map((node) => {\n const existingPos = existingPositions.get(node.id);\n if (existingPos) {\n return {\n ...node,\n fixedX: existingPos.x,\n fixedY: existingPos.y,\n };\n }\n return node;\n });\n\n return this.calculateLayout({\n nodes: nodesWithFixedPositions,\n edges,\n options,\n });\n }\n\n /**\n * Calculate incremental layout for new nodes only\n *\n * Takes a previous layout result and adds new nodes to it.\n * Existing nodes keep their positions, new nodes are positioned.\n *\n * @param previousResult Previous layout result\n * @param newNodes New nodes to add\n * @param allEdges All edges (including edges for new nodes)\n * @param options Layout options\n * @returns Promise resolving to updated layout result\n */\n async addNodesToLayout(\n previousResult: LayoutResult,\n newNodes: LayoutNode[],\n allEdges: LayoutEdge[],\n options?: LayoutOptions\n ): Promise<LayoutResult> {\n // Build position map from previous result\n const existingPositions = new Map<string, { x: number; y: number }>();\n for (const node of previousResult.nodes) {\n existingPositions.set(node.id, { x: node.x, y: node.y });\n }\n\n // Combine existing nodes (with dimensions) and new nodes\n const existingNodes: LayoutNode[] = previousResult.nodes.map((n) => ({\n id: n.id,\n width: n.width,\n height: n.height,\n }));\n\n const allNodes = [...existingNodes, ...newNodes];\n\n return this.recalculateLayout(allNodes, allEdges, existingPositions, options);\n }\n\n /**\n * Remove nodes from layout and recalculate\n *\n * Removes specified nodes and their connected edges,\n * then recalculates layout for remaining nodes.\n *\n * @param previousResult Previous layout result\n * @param nodeIdsToRemove IDs of nodes to remove\n * @param allEdges All edges (will be filtered to remove orphaned edges)\n * @param options Layout options\n * @param preservePositions If true, remaining nodes keep their positions\n * @returns Promise resolving to updated layout result\n */\n async removeNodesFromLayout(\n previousResult: LayoutResult,\n nodeIdsToRemove: string[],\n allEdges: LayoutEdge[],\n options?: LayoutOptions,\n preservePositions = true\n ): Promise<LayoutResult> {\n const removeSet = new Set(nodeIdsToRemove);\n\n // Filter out removed nodes\n const remainingNodes: LayoutNode[] = previousResult.nodes\n .filter((n) => !removeSet.has(n.id))\n .map((n) => ({\n id: n.id,\n width: n.width,\n height: n.height,\n }));\n\n // Filter out edges connected to removed nodes\n const remainingEdges = allEdges.filter(\n (e) => !removeSet.has(e.sourceId) && !removeSet.has(e.targetId)\n );\n\n if (remainingNodes.length === 0) {\n return { nodes: [], width: 0, height: 0 };\n }\n\n if (preservePositions) {\n const existingPositions = new Map<string, { x: number; y: number }>();\n for (const node of previousResult.nodes) {\n if (!removeSet.has(node.id)) {\n existingPositions.set(node.id, { x: node.x, y: node.y });\n }\n }\n return this.recalculateLayout(\n remainingNodes,\n remainingEdges,\n existingPositions,\n options\n );\n }\n\n return this.calculateLayout({\n nodes: remainingNodes,\n edges: remainingEdges,\n options,\n });\n }\n\n ngOnDestroy(): void {\n this.engine.dispose?.();\n }\n\n /**\n * Validate layout input\n */\n private validateInput(input: LayoutInput): void {\n if (!input.nodes || input.nodes.length === 0) {\n throw new Error('LayoutService: At least one node is required');\n }\n\n // Check for duplicate node IDs\n const nodeIds = new Set<string>();\n for (const node of input.nodes) {\n if (nodeIds.has(node.id)) {\n throw new Error(`LayoutService: Duplicate node ID \"${node.id}\"`);\n }\n nodeIds.add(node.id);\n }\n\n // Validate edges reference existing nodes\n if (input.edges) {\n for (const edge of input.edges) {\n if (!nodeIds.has(edge.sourceId)) {\n throw new Error(\n `LayoutService: Edge \"${edge.id}\" references unknown source node \"${edge.sourceId}\"`\n );\n }\n if (!nodeIds.has(edge.targetId)) {\n throw new Error(\n `LayoutService: Edge \"${edge.id}\" references unknown target node \"${edge.targetId}\"`\n );\n }\n }\n }\n }\n}\n","// Models - Types\nexport type {\n LayoutDirection,\n LayoutAlgorithm,\n EdgeType,\n LayoutNode,\n LayoutEdge,\n LayoutOptions,\n LayoutNodeResult,\n LayoutResult,\n LayoutInput,\n} from './lib/models/layout.models';\n\n// Models - Values\nexport { DEFAULT_LAYOUT_OPTIONS } from './lib/models/layout.models';\n\n// Engine abstraction (for custom engine implementations)\nexport type { LayoutEngine } from './lib/engines/layout-engine';\nexport { ElkLayoutEngine } from './lib/engines/elk-layout-engine';\n\n// Service\nexport {\n LayoutService,\n LAYOUT_ENGINE,\n defaultLayoutEngineFactory,\n} from './lib/services/layout.service';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAAA;;;;;AAKG;AAsHH;;AAEG;AACI,MAAM,sBAAsB,GAA4B;AAC7D,IAAA,SAAS,EAAE,SAAS;AACpB,IAAA,SAAS,EAAE,MAAM;AACjB,IAAA,WAAW,EAAE,EAAE;AACf,IAAA,YAAY,EAAE,EAAE;AAChB,IAAA,OAAO,EAAE,EAAE;AACX,IAAA,aAAa,EAAE,IAAI;;;ACvHrB;;;AAGG;AACH,MAAM,cAAc,GAAoC;AACtD,IAAA,OAAO,EAAE,SAAS;AAClB,IAAA,KAAK,EAAE,OAAO;AACd,IAAA,MAAM,EAAE,QAAQ;AAChB,IAAA,MAAM,EAAE,QAAQ;AAChB,IAAA,IAAI,EAAE,QAAQ;CACf;AAED;;;AAGG;AACH,MAAM,cAAc,GAAoC;AACtD,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,EAAE,EAAE,IAAI;AACR,IAAA,KAAK,EAAE,OAAO;AACd,IAAA,IAAI,EAAE,MAAM;CACb;AAED;;;;;AAKG;MACU,eAAe,CAAA;IACjB,IAAI,GAAG,KAAK;AAEb,IAAA,GAAG;AAEX,IAAA,WAAA,GAAA;AACE,QAAA,IAAI,CAAC,GAAG,GAAG,IAAI,GAAG,EAAE;IACtB;IAEA,MAAM,eAAe,CAAC,KAAkB,EAAA;QACtC,MAAM,OAAO,GAAG,EAAE,GAAG,sBAAsB,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE;;QAG/D,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC;;QAGnD,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC;;AAGrD,QAAA,OAAO,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC;IAC3C;IAEA,OAAO,GAAA;;;IAGP;AAEA;;AAEG;IACK,aAAa,CACnB,KAAkB,EAClB,OAAgC,EAAA;QAEhC,MAAM,UAAU,GAAG,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC;;QAGhD,MAAM,QAAQ,GAAc,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,KAAI;AACnD,YAAA,MAAM,OAAO,GAAY;gBACvB,EAAE,EAAE,IAAI,CAAC,EAAE;gBACX,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,MAAM,EAAE,IAAI,CAAC,MAAM;aACpB;;AAGD,YAAA,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE;AAC1D,gBAAA,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM;AACvB,gBAAA,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM;;gBAEvB,OAAO,CAAC,aAAa,GAAG;oBACtB,cAAc,EAAE,IAAI,IAAI,CAAC,MAAM,CAAA,EAAA,EAAK,IAAI,CAAC,MAAM,CAAA,CAAA,CAAG;iBACnD;YACH;AAEA,YAAA,OAAO,OAAO;AAChB,QAAA,CAAC,CAAC;;AAGF,QAAA,MAAM,KAAK,GAAsB,OAAO,CAAC;cACrC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,KAAK;cAC9B,EAAE;QAEN,OAAO;AACL,YAAA,EAAE,EAAE,MAAM;AACV,YAAA,aAAa,EAAE,UAAU;YACzB,QAAQ;YACR,KAAK;SACN;IACH;AAEA;;AAEG;AACK,IAAA,aAAa,CAAC,KAAmB,EAAA;QACvC,MAAM,QAAQ,GAAsB,EAAE;AAEtC,QAAA,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;;YAExB,QAAQ,CAAC,IAAI,CAAC;gBACZ,EAAE,EAAE,IAAI,CAAC,EAAE;AACX,gBAAA,OAAO,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC;AACxB,gBAAA,OAAO,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC;AACzB,aAAA,CAAC;;AAGF,YAAA,IAAI,IAAI,CAAC,IAAI,KAAK,eAAe,EAAE;gBACjC,QAAQ,CAAC,IAAI,CAAC;AACZ,oBAAA,EAAE,EAAE,CAAA,EAAG,IAAI,CAAC,EAAE,CAAA,QAAA,CAAU;AACxB,oBAAA,OAAO,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC;AACxB,oBAAA,OAAO,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC;AACzB,iBAAA,CAAC;YACJ;QACF;AAEA,QAAA,OAAO,QAAQ;IACjB;AAEA;;AAEG;AACK,IAAA,eAAe,CAAC,OAAgC,EAAA;AACtD,QAAA,MAAM,UAAU,GAAqB;AACnC,YAAA,eAAe,EAAE,cAAc,CAAC,OAAO,CAAC,SAAS,CAAC;AAClD,YAAA,sBAAsB,EAAE,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC;AACnD,YAAA,aAAa,EAAE,CAAA,KAAA,EAAQ,OAAO,CAAC,OAAO,UAAU,OAAO,CAAC,OAAO,CAAA,SAAA,EAAY,OAAO,CAAC,OAAO,WAAW,OAAO,CAAC,OAAO,CAAA,CAAA,CAAG;SACxH;;AAGD,QAAA,QAAQ,OAAO,CAAC,SAAS;AACvB,YAAA,KAAK,SAAS;gBACZ,UAAU,CAAC,eAAe,CAAC,GAAG,cAAc,CAAC,OAAO,CAAC,SAAS,CAAC;gBAC/D,UAAU,CAAC,2CAA2C,CAAC,GAAG,MAAM,CAC9D,OAAO,CAAC,YAAY,CACrB;;AAED,gBAAA,UAAU,CAAC,2CAA2C,CAAC,GAAG,MAAM,CAC9D,OAAO,CAAC,YAAY,GAAG,CAAC,CACzB;gBACD;AAEF,YAAA,KAAK,MAAM;gBACT,UAAU,CAAC,eAAe,CAAC,GAAG,cAAc,CAAC,OAAO,CAAC,SAAS,CAAC;AAC/D,gBAAA,UAAU,CAAC,sBAAsB,CAAC,GAAG,YAAY;gBACjD;AAEF,YAAA,KAAK,OAAO;;AAEV,gBAAA,UAAU,CAAC,sBAAsB,CAAC,GAAG,KAAK;gBAC1C;AAEF,YAAA,KAAK,QAAQ;;AAEX,gBAAA,UAAU,CAAC,8BAA8B,CAAC,GAAG,MAAM,CACjD,OAAO,CAAC,WAAW,GAAG,CAAC,CACxB;gBACD;AAEF,YAAA,KAAK,QAAQ;;gBAEX,UAAU,CAAC,mBAAmB,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC;gBAC9D;;AAGJ,QAAA,OAAO,UAAU;IACnB;AAEA;;AAEG;AACK,IAAA,cAAc,CAAC,QAAiB,EAAA;AACtC,QAAA,MAAM,KAAK,GAAuB,CAAC,QAAQ,CAAC,QAAQ,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC,KAAK,MAAM;YAC1E,EAAE,EAAE,KAAK,CAAC,EAAE;AACZ,YAAA,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC;AACf,YAAA,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC;AACf,YAAA,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,CAAC;AACvB,YAAA,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC;AAC1B,SAAA,CAAC,CAAC;QAEH,OAAO;YACL,KAAK;AACL,YAAA,KAAK,EAAE,QAAQ,CAAC,KAAK,IAAI,CAAC;AAC1B,YAAA,MAAM,EAAE,QAAQ,CAAC,MAAM,IAAI,CAAC;SAC7B;IACH;AACD;;AClMD;;;;;;;;;;AAUG;MACU,aAAa,GAAG,IAAI,cAAc,CAAe,eAAe;AAE7E;;AAEG;SACa,0BAA0B,GAAA;IACxC,OAAO,IAAI,eAAe,EAAE;AAC9B;AAEA;;;;;;;;;;;;;;;;;;;;;AAqBG;MAEU,aAAa,CAAA;AAChB,IAAA,MAAM;AAEd,IAAA,WAAA,GAAA;;AAEE,QAAA,MAAM,cAAc,GAAG,MAAM,CAAC,aAAa,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAChE,QAAA,IAAI,CAAC,MAAM,GAAG,cAAc,IAAI,0BAA0B,EAAE;IAC9D;AAEA;;AAEG;AACH,IAAA,IAAI,UAAU,GAAA;AACZ,QAAA,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI;IACzB;AAEA;;;;;AAKG;IACH,MAAM,eAAe,CAAC,KAAkB,EAAA;;AAEtC,QAAA,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC;QAEzB,OAAO,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC;IAC3C;AAEA;;;;;;;AAOG;AACH,IAAA,MAAM,MAAM,CACV,KAAmB,EACnB,KAAmB,EACnB,OAAuB,EAAA;AAEvB,QAAA,OAAO,IAAI,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IACxD;AAEA;;;;;;AAMG;IACH,MAAM,kBAAkB,CACtB,KAAkB,EAAA;QAElB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC;AAEhD,QAAA,MAAM,WAAW,GAAG,IAAI,GAAG,EAAoC;AAC/D,QAAA,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE;YAC/B,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC;QACpD;AAEA,QAAA,OAAO,WAAW;IACpB;AAEA;;;;;;;;;;;;;;;;;;;;;;;;AAwBG;IACH,MAAM,iBAAiB,CACrB,KAAmB,EACnB,KAAmB,EACnB,iBAAwD,EACxD,OAAuB,EAAA;;QAGvB,MAAM,uBAAuB,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,KAAI;YACjD,MAAM,WAAW,GAAG,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAClD,IAAI,WAAW,EAAE;gBACf,OAAO;AACL,oBAAA,GAAG,IAAI;oBACP,MAAM,EAAE,WAAW,CAAC,CAAC;oBACrB,MAAM,EAAE,WAAW,CAAC,CAAC;iBACtB;YACH;AACA,YAAA,OAAO,IAAI;AACb,QAAA,CAAC,CAAC;QAEF,OAAO,IAAI,CAAC,eAAe,CAAC;AAC1B,YAAA,KAAK,EAAE,uBAAuB;YAC9B,KAAK;YACL,OAAO;AACR,SAAA,CAAC;IACJ;AAEA;;;;;;;;;;;AAWG;IACH,MAAM,gBAAgB,CACpB,cAA4B,EAC5B,QAAsB,EACtB,QAAsB,EACtB,OAAuB,EAAA;;AAGvB,QAAA,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAoC;AACrE,QAAA,KAAK,MAAM,IAAI,IAAI,cAAc,CAAC,KAAK,EAAE;YACvC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC;QAC1D;;AAGA,QAAA,MAAM,aAAa,GAAiB,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM;YACnE,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,MAAM,EAAE,CAAC,CAAC,MAAM;AACjB,SAAA,CAAC,CAAC;QAEH,MAAM,QAAQ,GAAG,CAAC,GAAG,aAAa,EAAE,GAAG,QAAQ,CAAC;AAEhD,QAAA,OAAO,IAAI,CAAC,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,EAAE,iBAAiB,EAAE,OAAO,CAAC;IAC/E;AAEA;;;;;;;;;;;;AAYG;AACH,IAAA,MAAM,qBAAqB,CACzB,cAA4B,EAC5B,eAAyB,EACzB,QAAsB,EACtB,OAAuB,EACvB,iBAAiB,GAAG,IAAI,EAAA;AAExB,QAAA,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,eAAe,CAAC;;AAG1C,QAAA,MAAM,cAAc,GAAiB,cAAc,CAAC;AACjD,aAAA,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;AAClC,aAAA,GAAG,CAAC,CAAC,CAAC,MAAM;YACX,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,MAAM,EAAE,CAAC,CAAC,MAAM;AACjB,SAAA,CAAC,CAAC;;AAGL,QAAA,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CACpC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAChE;AAED,QAAA,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE;AAC/B,YAAA,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;QAC3C;QAEA,IAAI,iBAAiB,EAAE;AACrB,YAAA,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAoC;AACrE,YAAA,KAAK,MAAM,IAAI,IAAI,cAAc,CAAC,KAAK,EAAE;gBACvC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE;oBAC3B,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC;gBAC1D;YACF;AACA,YAAA,OAAO,IAAI,CAAC,iBAAiB,CAC3B,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,OAAO,CACR;QACH;QAEA,OAAO,IAAI,CAAC,eAAe,CAAC;AAC1B,YAAA,KAAK,EAAE,cAAc;AACrB,YAAA,KAAK,EAAE,cAAc;YACrB,OAAO;AACR,SAAA,CAAC;IACJ;IAEA,WAAW,GAAA;AACT,QAAA,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI;IACzB;AAEA;;AAEG;AACK,IAAA,aAAa,CAAC,KAAkB,EAAA;AACtC,QAAA,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE;AAC5C,YAAA,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC;QACjE;;AAGA,QAAA,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU;AACjC,QAAA,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE;YAC9B,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE;gBACxB,MAAM,IAAI,KAAK,CAAC,CAAA,kCAAA,EAAqC,IAAI,CAAC,EAAE,CAAA,CAAA,CAAG,CAAC;YAClE;AACA,YAAA,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB;;AAGA,QAAA,IAAI,KAAK,CAAC,KAAK,EAAE;AACf,YAAA,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE;gBAC9B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE;AAC/B,oBAAA,MAAM,IAAI,KAAK,CACb,CAAA,qBAAA,EAAwB,IAAI,CAAC,EAAE,CAAA,kCAAA,EAAqC,IAAI,CAAC,QAAQ,CAAA,CAAA,CAAG,CACrF;gBACH;gBACA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE;AAC/B,oBAAA,MAAM,IAAI,KAAK,CACb,CAAA,qBAAA,EAAwB,IAAI,CAAC,EAAE,CAAA,kCAAA,EAAqC,IAAI,CAAC,QAAQ,CAAA,CAAA,CAAG,CACrF;gBACH;YACF;QACF;IACF;wGA1PW,aAAa,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;4GAAb,aAAa,EAAA,CAAA;;4FAAb,aAAa,EAAA,UAAA,EAAA,CAAA;kBADzB;;;ACzCD;;ACbA;;AAEG;;;;"}
package/index.d.ts ADDED
@@ -0,0 +1,305 @@
1
+ import * as i0 from '@angular/core';
2
+ import { OnDestroy, InjectionToken } from '@angular/core';
3
+
4
+ /**
5
+ * Layout Library Models
6
+ *
7
+ * These interfaces are engine-agnostic and define the contract
8
+ * for any layout engine implementation (ELK, dagre, etc.)
9
+ */
10
+ /**
11
+ * Direction for hierarchical/layered layouts
12
+ */
13
+ type LayoutDirection = 'DOWN' | 'UP' | 'RIGHT' | 'LEFT';
14
+ /**
15
+ * Available layout algorithm types
16
+ * These are abstract algorithm categories that map to specific engine implementations
17
+ */
18
+ type LayoutAlgorithm = 'layered' | 'force' | 'stress' | 'radial' | 'tree';
19
+ /**
20
+ * Input node definition for layout calculation
21
+ */
22
+ interface LayoutNode {
23
+ /** Unique identifier for the node */
24
+ id: string;
25
+ /** Width of the node in pixels */
26
+ width: number;
27
+ /** Height of the node in pixels */
28
+ height: number;
29
+ /** Optional: Fixed X position (node won't be moved if set) */
30
+ fixedX?: number;
31
+ /** Optional: Fixed Y position (node won't be moved if set) */
32
+ fixedY?: number;
33
+ }
34
+ /**
35
+ * Edge type for layout calculation
36
+ * - 'directed': One-way relationship (source → target)
37
+ * - 'bidirectional': Two-way relationship (source ↔ target)
38
+ */
39
+ type EdgeType = 'directed' | 'bidirectional';
40
+ /**
41
+ * Edge/relationship between nodes for layout calculation
42
+ */
43
+ interface LayoutEdge {
44
+ /** Unique identifier for the edge */
45
+ id: string;
46
+ /** Source node ID */
47
+ sourceId: string;
48
+ /** Target node ID */
49
+ targetId: string;
50
+ /** Edge type (default: 'directed') */
51
+ type?: EdgeType;
52
+ }
53
+ /**
54
+ * Configuration options for layout calculation
55
+ */
56
+ interface LayoutOptions {
57
+ /** Layout algorithm to use (default: 'layered') */
58
+ algorithm?: LayoutAlgorithm;
59
+ /** Direction for hierarchical layouts (default: 'DOWN') */
60
+ direction?: LayoutDirection;
61
+ /** Horizontal spacing between nodes in pixels (default: 50) */
62
+ nodeSpacing?: number;
63
+ /** Vertical spacing between layers in pixels (default: 50) */
64
+ layerSpacing?: number;
65
+ /** Padding around the entire graph in pixels (default: 20) */
66
+ padding?: number;
67
+ /** Whether edges should be considered for layout (default: true) */
68
+ considerEdges?: boolean;
69
+ }
70
+ /**
71
+ * Result of layout calculation for a single node
72
+ */
73
+ interface LayoutNodeResult {
74
+ /** Node ID */
75
+ id: string;
76
+ /** Calculated X position (top-left corner) */
77
+ x: number;
78
+ /** Calculated Y position (top-left corner) */
79
+ y: number;
80
+ /** Node width (same as input) */
81
+ width: number;
82
+ /** Node height (same as input) */
83
+ height: number;
84
+ }
85
+ /**
86
+ * Complete result of layout calculation
87
+ */
88
+ interface LayoutResult {
89
+ /** Positioned nodes */
90
+ nodes: LayoutNodeResult[];
91
+ /** Total width of the laid out graph */
92
+ width: number;
93
+ /** Total height of the laid out graph */
94
+ height: number;
95
+ }
96
+ /**
97
+ * Input data for layout calculation
98
+ */
99
+ interface LayoutInput {
100
+ /** Nodes to layout */
101
+ nodes: LayoutNode[];
102
+ /** Edges/relationships between nodes */
103
+ edges: LayoutEdge[];
104
+ /** Layout options */
105
+ options?: LayoutOptions;
106
+ }
107
+ /**
108
+ * Default layout options
109
+ */
110
+ declare const DEFAULT_LAYOUT_OPTIONS: Required<LayoutOptions>;
111
+
112
+ /**
113
+ * Abstract layout engine interface
114
+ *
115
+ * Implement this interface to create a new layout engine.
116
+ * The layout service uses this abstraction to allow swapping
117
+ * different layout engines (ELK, dagre, etc.) without changing
118
+ * the consumer API.
119
+ */
120
+ interface LayoutEngine {
121
+ /**
122
+ * Name of the layout engine (for debugging/logging)
123
+ */
124
+ readonly name: string;
125
+ /**
126
+ * Calculate layout positions for the given input
127
+ * @param input Nodes, edges, and options for layout calculation
128
+ * @returns Promise resolving to the layout result with node positions
129
+ */
130
+ calculateLayout(input: LayoutInput): Promise<LayoutResult>;
131
+ /**
132
+ * Optional: Clean up any resources used by the engine
133
+ */
134
+ dispose?(): void;
135
+ }
136
+
137
+ /**
138
+ * ELK.js layout engine implementation
139
+ *
140
+ * Uses ELK.js (Eclipse Layout Kernel) for graph layout calculation.
141
+ * This is a high-quality layout library that supports multiple algorithms.
142
+ */
143
+ declare class ElkLayoutEngine implements LayoutEngine {
144
+ readonly name = "ELK";
145
+ private elk;
146
+ constructor();
147
+ calculateLayout(input: LayoutInput): Promise<LayoutResult>;
148
+ dispose(): void;
149
+ /**
150
+ * Convert our abstract input to ELK-specific graph structure
151
+ */
152
+ private buildElkGraph;
153
+ /**
154
+ * Convert layout edges to ELK edges, expanding bidirectional edges
155
+ */
156
+ private buildElkEdges;
157
+ /**
158
+ * Convert our abstract options to ELK-specific layout options
159
+ */
160
+ private buildElkOptions;
161
+ /**
162
+ * Extract layout results from ELK's output
163
+ */
164
+ private extractResults;
165
+ }
166
+
167
+ /**
168
+ * Injection token for providing a custom layout engine
169
+ * Use this to swap the default ELK engine with a different implementation
170
+ *
171
+ * @example
172
+ * ```typescript
173
+ * providers: [
174
+ * { provide: LAYOUT_ENGINE, useClass: CustomLayoutEngine }
175
+ * ]
176
+ * ```
177
+ */
178
+ declare const LAYOUT_ENGINE: InjectionToken<LayoutEngine>;
179
+ /**
180
+ * Factory function to create the default layout engine
181
+ */
182
+ declare function defaultLayoutEngineFactory(): LayoutEngine;
183
+ /**
184
+ * Layout Service
185
+ *
186
+ * Provides graph layout calculation using a pluggable layout engine.
187
+ * By default uses ELK.js, but can be configured to use any engine
188
+ * that implements the LayoutEngine interface.
189
+ *
190
+ * @example
191
+ * ```typescript
192
+ * // Basic usage
193
+ * const result = await layoutService.calculateLayout({
194
+ * nodes: [
195
+ * { id: 'a', width: 100, height: 50 },
196
+ * { id: 'b', width: 100, height: 50 },
197
+ * ],
198
+ * edges: [
199
+ * { id: 'e1', sourceId: 'a', targetId: 'b' }
200
+ * ],
201
+ * options: { algorithm: 'layered', direction: 'DOWN' }
202
+ * });
203
+ * ```
204
+ */
205
+ declare class LayoutService implements OnDestroy {
206
+ private engine;
207
+ constructor();
208
+ /**
209
+ * Get the name of the current layout engine
210
+ */
211
+ get engineName(): string;
212
+ /**
213
+ * Calculate layout positions for nodes based on their relationships
214
+ *
215
+ * @param input Layout input containing nodes, edges, and options
216
+ * @returns Promise resolving to layout result with positioned nodes
217
+ */
218
+ calculateLayout(input: LayoutInput): Promise<LayoutResult>;
219
+ /**
220
+ * Convenience method to calculate layout from separate parameters
221
+ *
222
+ * @param nodes Nodes to layout
223
+ * @param edges Edges/relationships between nodes
224
+ * @param options Layout options
225
+ * @returns Promise resolving to layout result
226
+ */
227
+ layout(nodes: LayoutNode[], edges: LayoutEdge[], options?: LayoutOptions): Promise<LayoutResult>;
228
+ /**
229
+ * Calculate layout and return a map of node positions by ID
230
+ * Useful for quick position lookups
231
+ *
232
+ * @param input Layout input
233
+ * @returns Promise resolving to a Map of node ID to position
234
+ */
235
+ calculateLayoutMap(input: LayoutInput): Promise<Map<string, {
236
+ x: number;
237
+ y: number;
238
+ }>>;
239
+ /**
240
+ * Recalculate layout while preserving positions of existing nodes
241
+ *
242
+ * This is useful when adding new nodes to an existing layout.
243
+ * Existing nodes will keep their positions (as fixed points),
244
+ * and only new nodes will be positioned by the layout algorithm.
245
+ *
246
+ * @param nodes All nodes (existing + new)
247
+ * @param edges All edges
248
+ * @param existingPositions Map of existing node IDs to their positions
249
+ * @param options Layout options
250
+ * @returns Promise resolving to layout result
251
+ *
252
+ * @example
253
+ * ```typescript
254
+ * // Add a new node to existing layout
255
+ * const newNodes = [...existingNodes, { id: 'new', width: 100, height: 50 }];
256
+ * const result = await layoutService.recalculateLayout(
257
+ * newNodes,
258
+ * edges,
259
+ * existingPositions, // Map from previous layout
260
+ * options
261
+ * );
262
+ * ```
263
+ */
264
+ recalculateLayout(nodes: LayoutNode[], edges: LayoutEdge[], existingPositions: Map<string, {
265
+ x: number;
266
+ y: number;
267
+ }>, options?: LayoutOptions): Promise<LayoutResult>;
268
+ /**
269
+ * Calculate incremental layout for new nodes only
270
+ *
271
+ * Takes a previous layout result and adds new nodes to it.
272
+ * Existing nodes keep their positions, new nodes are positioned.
273
+ *
274
+ * @param previousResult Previous layout result
275
+ * @param newNodes New nodes to add
276
+ * @param allEdges All edges (including edges for new nodes)
277
+ * @param options Layout options
278
+ * @returns Promise resolving to updated layout result
279
+ */
280
+ addNodesToLayout(previousResult: LayoutResult, newNodes: LayoutNode[], allEdges: LayoutEdge[], options?: LayoutOptions): Promise<LayoutResult>;
281
+ /**
282
+ * Remove nodes from layout and recalculate
283
+ *
284
+ * Removes specified nodes and their connected edges,
285
+ * then recalculates layout for remaining nodes.
286
+ *
287
+ * @param previousResult Previous layout result
288
+ * @param nodeIdsToRemove IDs of nodes to remove
289
+ * @param allEdges All edges (will be filtered to remove orphaned edges)
290
+ * @param options Layout options
291
+ * @param preservePositions If true, remaining nodes keep their positions
292
+ * @returns Promise resolving to updated layout result
293
+ */
294
+ removeNodesFromLayout(previousResult: LayoutResult, nodeIdsToRemove: string[], allEdges: LayoutEdge[], options?: LayoutOptions, preservePositions?: boolean): Promise<LayoutResult>;
295
+ ngOnDestroy(): void;
296
+ /**
297
+ * Validate layout input
298
+ */
299
+ private validateInput;
300
+ static ɵfac: i0.ɵɵFactoryDeclaration<LayoutService, never>;
301
+ static ɵprov: i0.ɵɵInjectableDeclaration<LayoutService>;
302
+ }
303
+
304
+ export { DEFAULT_LAYOUT_OPTIONS, ElkLayoutEngine, LAYOUT_ENGINE, LayoutService, defaultLayoutEngineFactory };
305
+ export type { EdgeType, LayoutAlgorithm, LayoutDirection, LayoutEdge, LayoutEngine, LayoutInput, LayoutNode, LayoutNodeResult, LayoutOptions, LayoutResult };
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@ngx-km/layout",
3
+ "version": "0.0.1",
4
+ "peerDependencies": {
5
+ "@angular/common": ">=19.0.0",
6
+ "@angular/core": ">=19.0.0"
7
+ },
8
+ "sideEffects": false,
9
+ "module": "fesm2022/ngx-km-layout.mjs",
10
+ "typings": "index.d.ts",
11
+ "exports": {
12
+ "./package.json": {
13
+ "default": "./package.json"
14
+ },
15
+ ".": {
16
+ "types": "./index.d.ts",
17
+ "default": "./fesm2022/ngx-km-layout.mjs"
18
+ }
19
+ },
20
+ "dependencies": {
21
+ "tslib": "^2.3.0"
22
+ }
23
+ }