@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.
- package/dist/hierarchical.d.ts +35 -0
- package/dist/hierarchical.d.ts.map +1 -0
- package/dist/hierarchical.js +176 -0
- package/dist/hierarchical.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/layout/hierarchical.d.ts.map +1 -1
- package/dist/layout/hierarchical.js +126 -24
- package/dist/layout/hierarchical.js.map +1 -1
- package/dist/models/types.d.ts +73 -0
- package/dist/models/types.d.ts.map +1 -1
- package/dist/models/types.js.map +1 -1
- package/package.json +1 -1
- package/src/hierarchical.ts +243 -0
- package/src/index.ts +2 -0
- package/src/layout/hierarchical.ts +1358 -1251
- package/src/models/types.ts +661 -575
|
@@ -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
package/dist/index.d.ts.map
CHANGED
|
@@ -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
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;
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
650
|
-
//
|
|
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
|
-
|
|
666
|
-
|
|
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
|
-
|
|
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
|
|
676
|
-
const
|
|
677
|
-
const y = (
|
|
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:
|
|
680
|
-
{ x:
|
|
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, {
|