@shumoku/core 0.2.3 → 0.2.5

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.
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Hierarchical sheet generation utilities
3
+ * Shared logic for building child sheets with export connectors
4
+ */
5
+ import type { LayoutResult, NetworkGraph } from './models/types.js';
6
+ export interface SheetData {
7
+ graph: NetworkGraph;
8
+ layout: LayoutResult;
9
+ }
10
+ export interface LayoutEngine {
11
+ layoutAsync(graph: NetworkGraph): Promise<LayoutResult>;
12
+ }
13
+ /**
14
+ * Check if a node is a virtual export connector
15
+ */
16
+ export declare function isExportNode(nodeId: string): boolean;
17
+ /**
18
+ * Check if a link is a virtual export connector link
19
+ */
20
+ export declare function isExportLink(linkId: string): boolean;
21
+ /**
22
+ * Build hierarchical sheets from a root graph
23
+ *
24
+ * Creates child sheets for each subgraph with:
25
+ * - Filtered nodes (only those belonging to the subgraph)
26
+ * - Internal links (both endpoints in subgraph)
27
+ * - Export connector nodes/links for boundary connections
28
+ *
29
+ * @param graph - Root network graph with subgraphs
30
+ * @param rootLayout - Layout result for the root graph
31
+ * @param layoutEngine - Engine to layout child sheets
32
+ * @returns Map of sheet ID to SheetData (includes 'root')
33
+ */
34
+ export declare function buildHierarchicalSheets(graph: NetworkGraph, rootLayout: LayoutResult, layoutEngine: LayoutEngine): Promise<Map<string, SheetData>>;
35
+ //# sourceMappingURL=hierarchical.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hierarchical.d.ts","sourceRoot":"","sources":["../src/hierarchical.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAQ,YAAY,EAAkB,MAAM,mBAAmB,CAAA;AAazF,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,YAAY,CAAA;IACnB,MAAM,EAAE,YAAY,CAAA;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;CACxD;AAgBD;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEpD;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,YAAY,EACnB,UAAU,EAAE,YAAY,EACxB,YAAY,EAAE,YAAY,GACzB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAsBjC"}
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Hierarchical sheet generation utilities
3
+ * Shared logic for building child sheets with export connectors
4
+ */
5
+ // ============================================
6
+ // Constants
7
+ // ============================================
8
+ const EXPORT_NODE_PREFIX = '__export_';
9
+ const EXPORT_LINK_PREFIX = '__export_link_';
10
+ // ============================================
11
+ // Type Guards
12
+ // ============================================
13
+ /**
14
+ * Check if a node is a virtual export connector
15
+ */
16
+ export function isExportNode(nodeId) {
17
+ return nodeId.startsWith(EXPORT_NODE_PREFIX);
18
+ }
19
+ /**
20
+ * Check if a link is a virtual export connector link
21
+ */
22
+ export function isExportLink(linkId) {
23
+ return linkId.startsWith(EXPORT_LINK_PREFIX);
24
+ }
25
+ // ============================================
26
+ // Main Function
27
+ // ============================================
28
+ /**
29
+ * Build hierarchical sheets from a root graph
30
+ *
31
+ * Creates child sheets for each subgraph with:
32
+ * - Filtered nodes (only those belonging to the subgraph)
33
+ * - Internal links (both endpoints in subgraph)
34
+ * - Export connector nodes/links for boundary connections
35
+ *
36
+ * @param graph - Root network graph with subgraphs
37
+ * @param rootLayout - Layout result for the root graph
38
+ * @param layoutEngine - Engine to layout child sheets
39
+ * @returns Map of sheet ID to SheetData (includes 'root')
40
+ */
41
+ export async function buildHierarchicalSheets(graph, rootLayout, layoutEngine) {
42
+ const sheets = new Map();
43
+ // Add root sheet
44
+ sheets.set('root', { graph, layout: rootLayout });
45
+ if (!graph.subgraphs || graph.subgraphs.length === 0) {
46
+ return sheets;
47
+ }
48
+ // Mark subgraphs as clickable
49
+ for (const sg of graph.subgraphs) {
50
+ sg.file = sg.id;
51
+ }
52
+ // Build child sheets
53
+ for (const sg of graph.subgraphs) {
54
+ const childSheet = await buildChildSheet(graph, sg, layoutEngine);
55
+ sheets.set(sg.id, childSheet);
56
+ }
57
+ return sheets;
58
+ }
59
+ // ============================================
60
+ // Internal Functions
61
+ // ============================================
62
+ async function buildChildSheet(rootGraph, subgraph, layoutEngine) {
63
+ // Get nodes belonging to this subgraph
64
+ const childNodes = rootGraph.nodes.filter((n) => n.parent === subgraph.id);
65
+ const childNodeIds = new Set(childNodes.map((n) => n.id));
66
+ // Get internal links (both endpoints in subgraph)
67
+ const childLinks = rootGraph.links.filter((l) => {
68
+ const fromNode = typeof l.from === 'string' ? l.from : l.from.node;
69
+ const toNode = typeof l.to === 'string' ? l.to : l.to.node;
70
+ return childNodeIds.has(fromNode) && childNodeIds.has(toNode);
71
+ });
72
+ // Generate export connectors for boundary connections
73
+ const { exportNodes, exportLinks } = generateExportConnectors(rootGraph, subgraph.id, childNodeIds);
74
+ // Build child graph
75
+ const childGraph = {
76
+ ...rootGraph,
77
+ name: subgraph.label,
78
+ nodes: [
79
+ ...childNodes.map((n) => ({ ...n, parent: undefined })),
80
+ ...exportNodes,
81
+ ],
82
+ links: [...childLinks, ...exportLinks],
83
+ subgraphs: undefined,
84
+ };
85
+ // Layout child sheet
86
+ const childLayout = await layoutEngine.layoutAsync(childGraph);
87
+ return { graph: childGraph, layout: childLayout };
88
+ }
89
+ function generateExportConnectors(rootGraph, subgraphId, childNodeIds) {
90
+ const exportNodes = [];
91
+ const exportLinks = [];
92
+ const exportPoints = new Map();
93
+ // Find boundary links
94
+ for (const link of rootGraph.links) {
95
+ const fromNode = typeof link.from === 'string' ? link.from : link.from.node;
96
+ const toNode = typeof link.to === 'string' ? link.to : link.to.node;
97
+ const fromPort = typeof link.from === 'object' ? link.from.port : undefined;
98
+ const toPort = typeof link.to === 'object' ? link.to.port : undefined;
99
+ const fromInside = childNodeIds.has(fromNode);
100
+ const toInside = childNodeIds.has(toNode);
101
+ if (fromInside && !toInside) {
102
+ // Outgoing connection
103
+ const key = `${subgraphId}:${fromNode}:${fromPort || ''}`;
104
+ if (!exportPoints.has(key)) {
105
+ const destSubgraph = findNodeSubgraph(rootGraph, toNode);
106
+ exportPoints.set(key, {
107
+ subgraphId,
108
+ device: fromNode,
109
+ port: fromPort,
110
+ destSubgraphLabel: destSubgraph?.label || toNode,
111
+ destDevice: toNode,
112
+ destPort: toPort,
113
+ isSource: true,
114
+ });
115
+ }
116
+ }
117
+ else if (!fromInside && toInside) {
118
+ // Incoming connection
119
+ const key = `${subgraphId}:${toNode}:${toPort || ''}`;
120
+ if (!exportPoints.has(key)) {
121
+ const destSubgraph = findNodeSubgraph(rootGraph, fromNode);
122
+ exportPoints.set(key, {
123
+ subgraphId,
124
+ device: toNode,
125
+ port: toPort,
126
+ destSubgraphLabel: destSubgraph?.label || fromNode,
127
+ destDevice: fromNode,
128
+ destPort: fromPort,
129
+ isSource: false,
130
+ });
131
+ }
132
+ }
133
+ }
134
+ // Create export nodes and links
135
+ for (const [key, exportPoint] of exportPoints) {
136
+ const exportId = key.replace(/:/g, '_');
137
+ // Export node
138
+ exportNodes.push({
139
+ id: `${EXPORT_NODE_PREFIX}${exportId}`,
140
+ label: exportPoint.destSubgraphLabel,
141
+ shape: 'stadium',
142
+ metadata: {
143
+ _isExport: true,
144
+ _destSubgraph: exportPoint.destSubgraphLabel,
145
+ _destDevice: exportPoint.destDevice,
146
+ _destPort: exportPoint.destPort,
147
+ _isSource: exportPoint.isSource,
148
+ },
149
+ });
150
+ // Export link
151
+ const exportNodeId = `${EXPORT_NODE_PREFIX}${exportId}`;
152
+ const deviceEndpoint = exportPoint.port
153
+ ? { node: exportPoint.device, port: exportPoint.port }
154
+ : exportPoint.device;
155
+ exportLinks.push({
156
+ id: `${EXPORT_LINK_PREFIX}${exportId}`,
157
+ from: exportPoint.isSource ? deviceEndpoint : exportNodeId,
158
+ to: exportPoint.isSource ? exportNodeId : deviceEndpoint,
159
+ type: 'dashed',
160
+ arrow: 'forward',
161
+ metadata: {
162
+ _destSubgraphLabel: exportPoint.destSubgraphLabel,
163
+ _destDevice: exportPoint.destDevice,
164
+ _destPort: exportPoint.destPort,
165
+ },
166
+ });
167
+ }
168
+ return { exportNodes, exportLinks };
169
+ }
170
+ function findNodeSubgraph(graph, nodeId) {
171
+ const node = graph.nodes.find((n) => n.id === nodeId);
172
+ if (!node?.parent)
173
+ return undefined;
174
+ return graph.subgraphs?.find((s) => s.id === node.parent);
175
+ }
176
+ //# sourceMappingURL=hierarchical.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hierarchical.js","sourceRoot":"","sources":["../src/hierarchical.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  * @shumoku/core - Network topology visualization core library
3
3
  */
4
4
  export * from './constants.js';
5
+ export * from './hierarchical.js';
5
6
  export * from './icons/index.js';
6
7
  export * from './layout/index.js';
7
8
  export * from './models/index.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,cAAc,gBAAgB,CAAA;AAE9B,cAAc,kBAAkB,CAAA;AAEhC,cAAc,mBAAmB,CAAA;AAEjC,cAAc,mBAAmB,CAAA;AAEjC,cAAc,mBAAmB,CAAA;AAGjC,eAAO,MAAM,OAAO,UAAU,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,cAAc,gBAAgB,CAAA;AAE9B,cAAc,mBAAmB,CAAA;AAEjC,cAAc,kBAAkB,CAAA;AAEhC,cAAc,mBAAmB,CAAA;AAEjC,cAAc,mBAAmB,CAAA;AAEjC,cAAc,mBAAmB,CAAA;AAGjC,eAAO,MAAM,OAAO,UAAU,CAAA"}
package/dist/index.js CHANGED
@@ -3,6 +3,8 @@
3
3
  */
4
4
  // Constants
5
5
  export * from './constants.js';
6
+ // Hierarchical
7
+ export * from './hierarchical.js';
6
8
  // Icons
7
9
  export * from './icons/index.js';
8
10
  // Layout
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,YAAY;AACZ,cAAc,gBAAgB,CAAA;AAC9B,QAAQ;AACR,cAAc,kBAAkB,CAAA;AAChC,SAAS;AACT,cAAc,mBAAmB,CAAA;AACjC,SAAS;AACT,cAAc,mBAAmB,CAAA;AACjC,SAAS;AACT,cAAc,mBAAmB,CAAA;AAEjC,UAAU;AACV,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,YAAY;AACZ,cAAc,gBAAgB,CAAA;AAC9B,eAAe;AACf,cAAc,mBAAmB,CAAA;AACjC,QAAQ;AACR,cAAc,kBAAkB,CAAA;AAChC,SAAS;AACT,cAAc,mBAAmB,CAAA;AACjC,SAAS;AACT,cAAc,mBAAmB,CAAA;AACjC,SAAS;AACT,cAAc,mBAAmB,CAAA;AAEjC,UAAU;AACV,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"hierarchical.d.ts","sourceRoot":"","sources":["../../src/layout/hierarchical.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAqBH,OAAO,EAGL,KAAK,eAAe,EAGpB,KAAK,YAAY,EAGjB,KAAK,YAAY,EAIlB,MAAM,oBAAoB,CAAA;AAwG3B,MAAM,WAAW,yBAAyB;IACxC,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B;AAgBD,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,GAAG,CAA0B;gBAEzB,OAAO,CAAC,EAAE,yBAAyB;IAK/C;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAwC/B,OAAO,CAAC,mBAAmB;IAarB,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;IA8B7D;;OAEG;IACH,OAAO,CAAC,aAAa;IA+XrB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAuR3B,MAAM,CAAC,KAAK,EAAE,YAAY,GAAG,YAAY;IAczC,OAAO,CAAC,cAAc;IAetB,OAAO,CAAC,mBAAmB;IAgE3B,OAAO,CAAC,oBAAoB;IAc5B,OAAO,CAAC,kBAAkB;IAiD1B,OAAO,CAAC,oBAAoB;IAqD5B,OAAO,CAAC,uBAAuB;IAwE/B,4CAA4C;IAC5C,OAAO,CAAC,aAAa;IAmBrB,kDAAkD;IAClD,OAAO,CAAC,sBAAsB;CAiB/B;AAGD,eAAO,MAAM,kBAAkB,oBAA2B,CAAA"}
1
+ {"version":3,"file":"hierarchical.d.ts","sourceRoot":"","sources":["../../src/layout/hierarchical.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAqBH,OAAO,EAGL,KAAK,eAAe,EAGpB,KAAK,YAAY,EAGjB,KAAK,YAAY,EAIlB,MAAM,oBAAoB,CAAA;AAgH3B,MAAM,WAAW,yBAAyB;IACxC,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B;AAgBD,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,GAAG,CAA0B;gBAEzB,OAAO,CAAC,EAAE,yBAAyB;IAK/C;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAwC/B,OAAO,CAAC,mBAAmB;IAarB,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;IA8B7D;;OAEG;IACH,OAAO,CAAC,aAAa;IAoarB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAqV3B,MAAM,CAAC,KAAK,EAAE,YAAY,GAAG,YAAY;IAczC,OAAO,CAAC,cAAc;IAetB,OAAO,CAAC,mBAAmB;IAgE3B,OAAO,CAAC,oBAAoB;IAc5B,OAAO,CAAC,kBAAkB;IAiD1B,OAAO,CAAC,oBAAoB;IAqD5B,OAAO,CAAC,uBAAuB;IAwE/B,4CAA4C;IAC5C,OAAO,CAAC,aAAa;IAmBrB,kDAAkD;IAClD,OAAO,CAAC,sBAAsB;CAiB/B;AAGD,eAAO,MAAM,kBAAkB,oBAA2B,CAAA"}
@@ -13,6 +13,14 @@ function toEndpoint(endpoint) {
13
13
  if (typeof endpoint === 'string') {
14
14
  return { node: endpoint };
15
15
  }
16
+ // Convert pin to port for subgraph boundary connections
17
+ if ('pin' in endpoint && endpoint.pin) {
18
+ return {
19
+ node: endpoint.node,
20
+ port: endpoint.pin, // Use pin as port
21
+ ip: endpoint.ip,
22
+ };
23
+ }
16
24
  return endpoint;
17
25
  }
18
26
  /** Collect ports for each node from links */
@@ -343,15 +351,39 @@ export class HierarchicalLayout {
343
351
  }
344
352
  const sgPadding = subgraph.style?.padding ?? options.subgraphPadding;
345
353
  const sgEdges = edgesByContainer.get(subgraph.id) || [];
346
- return {
354
+ // Set minimum size for empty subgraphs (e.g., those with file references)
355
+ const hasFileRef = !!subgraph.file;
356
+ const minWidth = hasFileRef && childNodes.length === 0 ? 200 : undefined;
357
+ const minHeight = hasFileRef && childNodes.length === 0 ? 100 : undefined;
358
+ // Subgraph-specific direction (can override parent)
359
+ const sgDirection = subgraph.direction
360
+ ? this.toElkDirection(subgraph.direction)
361
+ : elkDirection;
362
+ const elkNode = {
347
363
  id: subgraph.id,
348
364
  labels: [{ text: subgraph.label }],
349
365
  children: childNodes,
350
366
  edges: sgEdges,
351
367
  layoutOptions: {
368
+ 'elk.algorithm': 'layered',
369
+ 'elk.direction': sgDirection,
370
+ 'elk.hierarchyHandling': 'INCLUDE_CHILDREN',
352
371
  'elk.padding': `[top=${sgPadding + options.subgraphLabelHeight},left=${sgPadding},bottom=${sgPadding},right=${sgPadding}]`,
372
+ 'elk.spacing.nodeNode': String(options.nodeSpacing),
373
+ 'elk.layered.spacing.nodeNodeBetweenLayers': String(options.rankSpacing),
374
+ 'elk.edgeRouting': 'ORTHOGONAL',
375
+ // Use ROOT coordinate system for consistent edge/shape positioning
376
+ 'org.eclipse.elk.json.edgeCoords': 'ROOT',
377
+ 'org.eclipse.elk.json.shapeCoords': 'ROOT',
353
378
  },
354
379
  };
380
+ // Note: Subgraph pins are resolved to device:port in parser
381
+ // ELK handles cross-hierarchy edges directly with INCLUDE_CHILDREN
382
+ if (minWidth)
383
+ elkNode.width = minWidth;
384
+ if (minHeight)
385
+ elkNode.height = minHeight;
386
+ return elkNode;
355
387
  };
356
388
  // Build root children
357
389
  const buildRootChildren = (edgesByContainer) => {
@@ -382,11 +414,15 @@ export class HierarchicalLayout {
382
414
  }
383
415
  return children;
384
416
  };
385
- // Build node to parent map
417
+ // Build node to parent map (includes both nodes and subgraphs)
386
418
  const nodeParentMap = new Map();
387
419
  for (const node of graph.nodes) {
388
420
  nodeParentMap.set(node.id, node.parent);
389
421
  }
422
+ // Add subgraphs to parent map for LCA calculation
423
+ for (const sg of subgraphMap.values()) {
424
+ nodeParentMap.set(sg.id, sg.parent);
425
+ }
390
426
  // Find LCA (Lowest Common Ancestor) of two nodes
391
427
  const findLCA = (nodeA, nodeB) => {
392
428
  const ancestorsA = new Set();
@@ -427,6 +463,8 @@ export class HierarchicalLayout {
427
463
  if (link.redundancy && isHALink(from.node, to.node)) {
428
464
  continue;
429
465
  }
466
+ // ELK port reference format: nodeId:portId in sources/targets
467
+ // Note: Pin references are already resolved to device:port in parser
430
468
  const sourceId = from.port ? `${from.node}:${from.port}` : from.node;
431
469
  const targetId = to.port ? `${to.node}:${to.port}` : to.node;
432
470
  const edge = {
@@ -451,13 +489,17 @@ export class HierarchicalLayout {
451
489
  },
452
490
  ];
453
491
  }
454
- // Find LCA and place edge in appropriate container
492
+ // Determine edge container using LCA (Lowest Common Ancestor)
493
+ // All pin references are already resolved to actual devices in parser
455
494
  const lca = findLCA(from.node, to.node);
456
495
  let container = lca;
457
496
  if (container === from.node || container === to.node) {
458
497
  container = nodeParentMap.get(container);
459
498
  }
460
499
  const containerId = container && subgraphMap.has(container) ? container : 'root';
500
+ if (!edgesByContainer.has(containerId)) {
501
+ edgesByContainer.set(containerId, []);
502
+ }
461
503
  edgesByContainer.get(containerId).push(edge);
462
504
  }
463
505
  // Dynamic edge spacing
@@ -519,11 +561,12 @@ export class HierarchicalLayout {
519
561
  if (subgraphMap.has(elkNode.id)) {
520
562
  // Subgraph
521
563
  const sg = subgraphMap.get(elkNode.id);
522
- layoutSubgraphs.set(elkNode.id, {
564
+ const layoutSg = {
523
565
  id: elkNode.id,
524
566
  bounds: { x, y, width, height },
525
567
  subgraph: sg,
526
- });
568
+ };
569
+ layoutSubgraphs.set(elkNode.id, layoutSg);
527
570
  if (elkNode.children) {
528
571
  for (const child of elkNode.children) {
529
572
  processElkNode(child);
@@ -630,11 +673,32 @@ export class HierarchicalLayout {
630
673
  const id = link.id || `link-${index}`;
631
674
  const fromEndpoint = toEndpoint(link.from);
632
675
  const toEndpoint_ = toEndpoint(link.to);
676
+ // Get layout info for endpoints (can be nodes or subgraphs)
633
677
  const fromNode = layoutNodes.get(fromEndpoint.node);
634
678
  const toNode = layoutNodes.get(toEndpoint_.node);
635
- if (!fromNode || !toNode)
679
+ const fromSubgraph = layoutSubgraphs.get(fromEndpoint.node);
680
+ const toSubgraph = layoutSubgraphs.get(toEndpoint_.node);
681
+ // Get position and size for from/to (either node or subgraph)
682
+ // LayoutSubgraph uses bounds {x, y, width, height}, convert to position/size format
683
+ const fromLayout = fromNode || (fromSubgraph ? {
684
+ position: {
685
+ x: fromSubgraph.bounds.x + fromSubgraph.bounds.width / 2,
686
+ y: fromSubgraph.bounds.y + fromSubgraph.bounds.height / 2,
687
+ },
688
+ size: { width: fromSubgraph.bounds.width, height: fromSubgraph.bounds.height },
689
+ } : null);
690
+ const toLayout = toNode || (toSubgraph ? {
691
+ position: {
692
+ x: toSubgraph.bounds.x + toSubgraph.bounds.width / 2,
693
+ y: toSubgraph.bounds.y + toSubgraph.bounds.height / 2,
694
+ },
695
+ size: { width: toSubgraph.bounds.width, height: toSubgraph.bounds.height },
696
+ } : null);
697
+ if (!fromLayout || !toLayout)
636
698
  continue;
637
699
  let points = [];
700
+ // Check if this is a subgraph-to-subgraph edge
701
+ const isSubgraphEdge = fromSubgraph || toSubgraph;
638
702
  // HA edges inside HA containers: use ELK's edge routing directly
639
703
  if (isHAContainer(container.id) && elkEdge.sections && elkEdge.sections.length > 0) {
640
704
  const section = elkEdge.sections[0];
@@ -646,38 +710,76 @@ export class HierarchicalLayout {
646
710
  }
647
711
  points.push({ x: section.endPoint.x, y: section.endPoint.y });
648
712
  }
649
- else if (!isHAContainer(container.id)) {
650
- // Normal vertical edges
651
- const fromBottomY = fromNode.position.y + fromNode.size.height / 2;
652
- const toTopY = toNode.position.y - toNode.size.height / 2;
713
+ else if (isSubgraphEdge) {
714
+ // Subgraph edges: use ELK's coordinates directly
653
715
  if (elkEdge.sections && elkEdge.sections.length > 0) {
654
716
  const section = elkEdge.sections[0];
655
- points.push({
656
- x: section.startPoint.x,
657
- y: fromBottomY,
658
- });
717
+ points.push({ x: section.startPoint.x, y: section.startPoint.y });
659
718
  if (section.bendPoints) {
660
719
  for (const bp of section.bendPoints) {
661
720
  points.push({ x: bp.x, y: bp.y });
662
721
  }
663
722
  }
664
- points.push({
665
- x: section.endPoint.x,
666
- y: toTopY,
667
- });
723
+ points.push({ x: section.endPoint.x, y: section.endPoint.y });
724
+ }
725
+ else {
726
+ // Fallback: simple line between centers
727
+ points = [
728
+ { x: fromLayout.position.x, y: fromLayout.position.y + fromLayout.size.height / 2 },
729
+ { x: toLayout.position.x, y: toLayout.position.y - toLayout.size.height / 2 },
730
+ ];
731
+ }
732
+ }
733
+ else if (!isHAContainer(container.id)) {
734
+ // Check if this is a cross-subgraph edge
735
+ const fromParent = graph.nodes.find((n) => n.id === fromEndpoint.node)?.parent;
736
+ const toParent = graph.nodes.find((n) => n.id === toEndpoint_.node)?.parent;
737
+ const isCrossSubgraph = fromParent !== toParent;
738
+ if (elkEdge.sections && elkEdge.sections.length > 0) {
739
+ const section = elkEdge.sections[0];
740
+ if (isCrossSubgraph) {
741
+ // Cross-subgraph edges: use ELK's coordinates directly
742
+ points.push({ x: section.startPoint.x, y: section.startPoint.y });
743
+ if (section.bendPoints) {
744
+ for (const bp of section.bendPoints) {
745
+ points.push({ x: bp.x, y: bp.y });
746
+ }
747
+ }
748
+ points.push({ x: section.endPoint.x, y: section.endPoint.y });
749
+ }
750
+ else {
751
+ // Same-subgraph edges: snap to node boundaries for cleaner look
752
+ const fromBottomY = fromLayout.position.y + fromLayout.size.height / 2;
753
+ const toTopY = toLayout.position.y - toLayout.size.height / 2;
754
+ points.push({
755
+ x: section.startPoint.x,
756
+ y: fromBottomY,
757
+ });
758
+ if (section.bendPoints) {
759
+ for (const bp of section.bendPoints) {
760
+ points.push({ x: bp.x, y: bp.y });
761
+ }
762
+ }
763
+ points.push({
764
+ x: section.endPoint.x,
765
+ y: toTopY,
766
+ });
767
+ }
668
768
  }
669
769
  else {
670
- points = this.generateOrthogonalPath({ x: fromNode.position.x, y: fromBottomY }, { x: toNode.position.x, y: toTopY });
770
+ const fromBottomY = fromLayout.position.y + fromLayout.size.height / 2;
771
+ const toTopY = toLayout.position.y - toLayout.size.height / 2;
772
+ points = this.generateOrthogonalPath({ x: fromLayout.position.x, y: fromBottomY }, { x: toLayout.position.x, y: toTopY });
671
773
  }
672
774
  }
673
775
  else {
674
776
  // HA edge fallback: simple horizontal line
675
- const leftNode = fromNode.position.x < toNode.position.x ? fromNode : toNode;
676
- const rightNode = fromNode.position.x < toNode.position.x ? toNode : fromNode;
677
- const y = (leftNode.position.y + rightNode.position.y) / 2;
777
+ const leftLayout = fromLayout.position.x < toLayout.position.x ? fromLayout : toLayout;
778
+ const rightLayout = fromLayout.position.x < toLayout.position.x ? toLayout : fromLayout;
779
+ const y = (leftLayout.position.y + rightLayout.position.y) / 2;
678
780
  points = [
679
- { x: leftNode.position.x + leftNode.size.width / 2, y },
680
- { x: rightNode.position.x - rightNode.size.width / 2, y },
781
+ { x: leftLayout.position.x + leftLayout.size.width / 2, y },
782
+ { x: rightLayout.position.x - rightLayout.size.width / 2, y },
681
783
  ];
682
784
  }
683
785
  layoutLinks.set(id, {