@diagrammo/dgmo 0.46.0 → 0.47.0
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/advanced.d.cts +3 -855
- package/dist/advanced.d.ts +3 -855
- package/dist/advanced.js +541 -70107
- package/dist/analyzer-UYKBXAKR.js +15 -0
- package/dist/auto.cjs +1 -1
- package/dist/auto.js +1 -1
- package/dist/auto.mjs +1 -1
- package/dist/block.js +83 -69417
- package/dist/calculator-LDYQCDDY.js +11 -0
- package/dist/charts-d3-QOZHKTCT.js +25 -0
- package/dist/chunk-2DXMZRWJ.js +800 -0
- package/dist/chunk-2LACINWN.js +786 -0
- package/dist/chunk-33LCUH4H.js +893 -0
- package/dist/chunk-3CIOJ5YJ.js +35 -0
- package/dist/chunk-3HDBAXF5.js +86 -0
- package/dist/chunk-3JLKYGBQ.js +290 -0
- package/dist/chunk-3MQQNXPD.js +415 -0
- package/dist/chunk-3R5POCTZ.js +967 -0
- package/dist/chunk-3SMGIJDD.js +112 -0
- package/dist/chunk-44CM72SQ.js +409 -0
- package/dist/chunk-4GRI7MEB.js +1604 -0
- package/dist/chunk-4OHACBGX.js +606 -0
- package/dist/chunk-5DH3KYFH.js +21 -0
- package/dist/chunk-5MLYVTYM.js +76 -0
- package/dist/chunk-5QXL4NYS.js +553 -0
- package/dist/chunk-5WRI5ZAA.js +31 -0
- package/dist/chunk-6EK55ART.js +117 -0
- package/dist/chunk-6FZYCKXB.js +2065 -0
- package/dist/chunk-6HO5ZTXK.js +277 -0
- package/dist/chunk-6KFWC4CO.js +7 -0
- package/dist/chunk-6MTWIQVO.js +121 -0
- package/dist/chunk-6NYWYNBI.js +329 -0
- package/dist/chunk-6SM5NJQH.js +620 -0
- package/dist/chunk-725LDASD.js +422 -0
- package/dist/chunk-76OWZ3BA.js +130 -0
- package/dist/chunk-7JEGTRLD.js +338 -0
- package/dist/chunk-A44USUV7.js +923 -0
- package/dist/chunk-A4GOY3PE.js +25 -0
- package/dist/chunk-A4VFQH5G.js +251 -0
- package/dist/chunk-AELOBFW5.js +375 -0
- package/dist/chunk-AN2MOO5T.js +522 -0
- package/dist/chunk-BMR4CIEO.js +41 -0
- package/dist/chunk-BS7SV7C3.js +51 -0
- package/dist/chunk-BUPDIHML.js +613 -0
- package/dist/chunk-BUVJFRPY.js +887 -0
- package/dist/chunk-C3Z4DX3V.js +112 -0
- package/dist/chunk-CI2G3OEB.js +1818 -0
- package/dist/chunk-CKNVO24D.js +54 -0
- package/dist/chunk-CUVHQHZ7.js +391 -0
- package/dist/chunk-CXK4DZH3.js +114 -0
- package/dist/chunk-DKEPS5LH.js +315 -0
- package/dist/chunk-DU7W62N5.js +487 -0
- package/dist/chunk-DZAQBW35.js +751 -0
- package/dist/chunk-EB6FPQUP.js +316 -0
- package/dist/chunk-EMS2SK26.js +376 -0
- package/dist/chunk-ERLBPDGU.js +510 -0
- package/dist/chunk-EVCVDTXE.js +732 -0
- package/dist/chunk-FCIIHT24.js +384 -0
- package/dist/chunk-FFUN5D66.js +97 -0
- package/dist/chunk-FITOLXSX.js +714 -0
- package/dist/chunk-FMDSF3OV.js +68 -0
- package/dist/chunk-FNB3PNGF.js +410 -0
- package/dist/chunk-G2H74XJZ.js +2104 -0
- package/dist/chunk-GMR5MFHJ.js +339 -0
- package/dist/chunk-H6PI3KJT.js +305 -0
- package/dist/chunk-I46X5VUA.js +59 -0
- package/dist/chunk-I5IGB56C.js +255 -0
- package/dist/chunk-IH6ZMVQ5.js +3950 -0
- package/dist/chunk-IJZFN3HM.js +463 -0
- package/dist/chunk-IPWPWM6R.js +3079 -0
- package/dist/chunk-IYULMRBE.js +23 -0
- package/dist/chunk-IZMRON6C.js +167 -0
- package/dist/chunk-J5FIXQRT.js +813 -0
- package/dist/chunk-JED6ZDEE.js +73 -0
- package/dist/chunk-JEUJOX5X.js +136 -0
- package/dist/chunk-JM4LEPY3.js +436 -0
- package/dist/chunk-L3UHP6TD.js +101 -0
- package/dist/chunk-L66GEFB6.js +524 -0
- package/dist/chunk-LIOHW36Z.js +469 -0
- package/dist/chunk-LP7EC4MI.js +16 -0
- package/dist/chunk-MITI6NOL.js +548 -0
- package/dist/chunk-MO5YHHMQ.js +168 -0
- package/dist/chunk-MZSBFAPJ.js +885 -0
- package/dist/chunk-NNAAJVBU.js +9 -0
- package/dist/chunk-NOTA6TEI.js +47 -0
- package/dist/chunk-NURGB5Z4.js +335 -0
- package/dist/chunk-NX4EQS3U.js +1062 -0
- package/dist/chunk-NXMESY4J.js +1 -0
- package/dist/chunk-OJR3MQYV.js +1458 -0
- package/dist/chunk-OURHMODR.js +454 -0
- package/dist/chunk-P235SVAH.js +1057 -0
- package/dist/chunk-P4RH4OSP.js +1038 -0
- package/dist/chunk-P6T66CCZ.js +226 -0
- package/dist/chunk-PBAHEPLQ.js +223 -0
- package/dist/chunk-PBHE5UG6.js +1530 -0
- package/dist/chunk-PDKUANDZ.js +35 -0
- package/dist/chunk-PWIHZ6NJ.js +296 -0
- package/dist/chunk-QEBUY5NY.js +1685 -0
- package/dist/chunk-QLCCJEJI.js +253 -0
- package/dist/chunk-QOOUC2J5.js +52 -0
- package/dist/chunk-QYTPPDAO.js +402 -0
- package/dist/chunk-R3XI2VC7.js +801 -0
- package/dist/chunk-RCERWOJR.js +154 -0
- package/dist/chunk-RJE5JTVK.js +362 -0
- package/dist/chunk-RTLBSQ3G.js +1155 -0
- package/dist/chunk-RUNNI6VE.js +448 -0
- package/dist/chunk-RVMRZAPB.js +354 -0
- package/dist/chunk-RYEGFMUO.js +955 -0
- package/dist/chunk-RZCF2VVM.js +528 -0
- package/dist/chunk-SML67VPV.js +1025 -0
- package/dist/chunk-SQ7IY53Q.js +44 -0
- package/dist/chunk-SQAG3UXH.js +161 -0
- package/dist/chunk-SRTU6HMQ.js +1851 -0
- package/dist/chunk-T2OE3YQB.js +550 -0
- package/dist/chunk-T3UBLACL.js +299 -0
- package/dist/chunk-TLVIHFRO.js +296 -0
- package/dist/chunk-TYS6CSS5.js +52 -0
- package/dist/chunk-U6EQI3U6.js +130 -0
- package/dist/chunk-UJ4FJLBF.js +86 -0
- package/dist/chunk-ULOEATBZ.js +347 -0
- package/dist/chunk-UNCCZ3NU.js +127 -0
- package/dist/chunk-USZW2HHU.js +197 -0
- package/dist/chunk-UWN33VNX.js +1610 -0
- package/dist/chunk-UZ7EZW3B.js +40 -0
- package/dist/chunk-VOKGH627.js +37 -0
- package/dist/chunk-WBRZJOEC.js +204 -0
- package/dist/chunk-WCSOJFU3.js +380 -0
- package/dist/chunk-WCT3NYCV.js +160 -0
- package/dist/chunk-WVAJFAIA.js +529 -0
- package/dist/chunk-X5LCYCL5.js +394 -0
- package/dist/chunk-XAKEUMEJ.js +1256 -0
- package/dist/chunk-Y2ZE3MSG.js +590 -0
- package/dist/chunk-Y3J7VR52.js +646 -0
- package/dist/chunk-Y5QGVTVC.js +33 -0
- package/dist/chunk-YAA6IID3.js +548 -0
- package/dist/chunk-YGJTDXVR.js +692 -0
- package/dist/chunk-YRYKTYMQ.js +186 -0
- package/dist/chunk-YYVTBZZC.js +61 -0
- package/dist/chunk-Z5CGSFJB.js +647 -0
- package/dist/chunk-Z72XRXD4.js +787 -0
- package/dist/chunk-ZGR7ULFJ.js +978 -0
- package/dist/chunk-ZJEL4H2R.js +851 -0
- package/dist/chunk-ZO27FGJS.js +554 -0
- package/dist/collapse-CLPJD2RL.js +11 -0
- package/dist/collapse-SLINJQW6.js +9 -0
- package/dist/collapse-ZWUFFCK2.js +9 -0
- package/dist/compute-G55RG53N.js +8 -0
- package/dist/dgmo-router-DZCIDUO5.js +73 -0
- package/dist/dimensions-J3FHIMJ6.js +22 -0
- package/dist/element.cjs +1 -1
- package/dist/element.js +1 -1
- package/dist/element.mjs +1 -1
- package/dist/flowchart-parser-ZNSKIBVR.js +21 -0
- package/dist/flowchart-renderer-ZAEOL4DX.js +35 -0
- package/dist/index.cjs +3 -3
- package/dist/index.d.cts +3 -469
- package/dist/index.d.ts +3 -469
- package/dist/index.js +137 -69685
- package/dist/layout-46N4JJYK.js +17 -0
- package/dist/layout-5RA6QWR6.js +26 -0
- package/dist/layout-5T6RH5M6.js +14 -0
- package/dist/layout-7QXCNOVS.js +13 -0
- package/dist/layout-EE6GE46Y.js +17 -0
- package/dist/layout-G23F7LDH.js +9 -0
- package/dist/layout-HRBZPFCN.js +23 -0
- package/dist/layout-HWDSRX5E.js +20 -0
- package/dist/layout-HYZPWAK5.js +18 -0
- package/dist/layout-N76NR4ZO.js +19 -0
- package/dist/layout-OB3RI63S.js +14 -0
- package/dist/layout-S2QBYSK2.js +15 -0
- package/dist/layout-UM5F4DJB.js +15 -0
- package/dist/layout-search-OYV77HHS.js +1655 -0
- package/dist/load-data-EHDDZ5ZS.js +8 -0
- package/dist/palettes-PRJ2RI6D.js +56 -0
- package/dist/parser-2MFP2H2E.js +11 -0
- package/dist/parser-3STE62GB.js +21 -0
- package/dist/parser-4FL2CMDG.js +18 -0
- package/dist/parser-4OGOBJJ4.js +13 -0
- package/dist/parser-6BTRLKRZ.js +15 -0
- package/dist/parser-AQETEVRW.js +21 -0
- package/dist/parser-CFZD5IBL.js +12 -0
- package/dist/parser-FUSKFGZN.js +16 -0
- package/dist/parser-I6KXEYFK.js +13 -0
- package/dist/parser-IXSZZQHY.js +18 -0
- package/dist/parser-JAL2HD4I.js +13 -0
- package/dist/parser-KNSQSKQO.js +20 -0
- package/dist/parser-N5O4IHUN.js +13 -0
- package/dist/parser-NGU3ETO6.js +16 -0
- package/dist/parser-NRAI6O25.js +19 -0
- package/dist/parser-O5J4VBII.js +11 -0
- package/dist/parser-OV7NOBW6.js +21 -0
- package/dist/parser-PQ3POX45.js +23 -0
- package/dist/parser-TFZFBVWN.js +11 -0
- package/dist/parser-TRTHDFPP.js +18 -0
- package/dist/parser-VQP2UJ6W.js +11 -0
- package/dist/parser-XDVELHRK.js +13 -0
- package/dist/parser-XKRP7QKP.js +11 -0
- package/dist/parser-Y6WTBU76.js +14 -0
- package/dist/parser-ZSDPZYQP.js +12 -0
- package/dist/renderer-2XQQQ6QR.js +28 -0
- package/dist/renderer-5NDVNDAZ.js +26 -0
- package/dist/renderer-6EEZHMH4.js +26 -0
- package/dist/renderer-6W5462RB.js +43 -0
- package/dist/renderer-7MVLWWQD.js +22 -0
- package/dist/renderer-AWWAF6XD.js +33 -0
- package/dist/renderer-BYCNMBF7.js +34 -0
- package/dist/renderer-CLJHQPBJ.js +29 -0
- package/dist/renderer-GHPZMFU2.js +15 -0
- package/dist/renderer-JGBKUCZD.js +16 -0
- package/dist/renderer-KQB7UGDS.js +19 -0
- package/dist/renderer-PGX2TKVS.js +24 -0
- package/dist/renderer-QIWVHWZO.js +15 -0
- package/dist/renderer-RPOA5YPZ.js +35 -0
- package/dist/renderer-SMIBCRVB.js +24 -0
- package/dist/renderer-T7YKC5VP.js +28 -0
- package/dist/renderer-TOCOVLHS.js +37 -0
- package/dist/renderer-UWA5XS74.js +39 -0
- package/dist/renderer-VACVQYML.js +25 -0
- package/dist/renderer-W4M6QLIJ.js +37 -0
- package/dist/renderer-WZRREQ2P.js +23 -0
- package/dist/renderer-YY2OSF6E.js +27 -0
- package/dist/renderer-Z7I3W2PP.js +24 -0
- package/dist/renderer-ZGNTUWC4.js +22 -0
- package/dist/renderer-ZTR3DER3.js +19 -0
- package/dist/renderer-radial-C32GPJQ7.js +21 -0
- package/dist/resolver-NOAA7ID7.js +13 -0
- package/dist/state-collapse-HCGD6IVJ.js +8 -0
- package/dist/state-parser-Z2V23ATR.js +18 -0
- package/dist/state-renderer-RFYHSP4K.js +34 -0
- package/dist/themes-1-CuKpeH.d.cts +908 -0
- package/dist/themes-1-CuKpeH.d.ts +908 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1685 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LEGEND_CAPSULE_PAD,
|
|
3
|
+
LEGEND_DOT_R,
|
|
4
|
+
LEGEND_ENTRY_DOT_GAP,
|
|
5
|
+
LEGEND_ENTRY_FONT_SIZE,
|
|
6
|
+
LEGEND_ENTRY_TRAIL,
|
|
7
|
+
LEGEND_HEIGHT,
|
|
8
|
+
LEGEND_PILL_FONT_SIZE,
|
|
9
|
+
LEGEND_PILL_PAD,
|
|
10
|
+
measureLegendText
|
|
11
|
+
} from "./chunk-QOOUC2J5.js";
|
|
12
|
+
import {
|
|
13
|
+
measureText,
|
|
14
|
+
wrapTextToWidth
|
|
15
|
+
} from "./chunk-WCT3NYCV.js";
|
|
16
|
+
import {
|
|
17
|
+
tagAttrKey
|
|
18
|
+
} from "./chunk-QLCCJEJI.js";
|
|
19
|
+
|
|
20
|
+
// src/c4/layout.ts
|
|
21
|
+
import dagre from "@dagrejs/dagre";
|
|
22
|
+
var gNode = (g, name) => g.node(name);
|
|
23
|
+
var gEdge = (g, v, w) => g.edge(v, w);
|
|
24
|
+
var MIN_NODE_WIDTH = 160;
|
|
25
|
+
var MAX_NODE_WIDTH = 260;
|
|
26
|
+
var TYPE_LABEL_HEIGHT = 18;
|
|
27
|
+
var DIVIDER_GAP = 6;
|
|
28
|
+
var NAME_HEIGHT = 20;
|
|
29
|
+
var NAME_FONT_SIZE = 14;
|
|
30
|
+
var DESC_LINE_HEIGHT = 16;
|
|
31
|
+
var DESC_FONT_SIZE = 11;
|
|
32
|
+
var CARD_V_PAD = 14;
|
|
33
|
+
var CARD_H_PAD = 20;
|
|
34
|
+
var META_LINE_HEIGHT = 16;
|
|
35
|
+
var META_FONT_SIZE = 11;
|
|
36
|
+
var MARGIN = 40;
|
|
37
|
+
var BOUNDARY_PAD = 40;
|
|
38
|
+
var GROUP_BOUNDARY_PAD = 24;
|
|
39
|
+
var EDGE_NODE_COLLISION_WEIGHT = 5e3;
|
|
40
|
+
function computeEdgePenalty(edgeList, nodePositions, degrees, nodeGeometry) {
|
|
41
|
+
let penalty = 0;
|
|
42
|
+
for (const edge of edgeList) {
|
|
43
|
+
const sx = nodePositions.get(edge.source);
|
|
44
|
+
const tx = nodePositions.get(edge.target);
|
|
45
|
+
if (sx == null || tx == null) continue;
|
|
46
|
+
const dist = Math.abs(sx - tx);
|
|
47
|
+
const weight = Math.min(
|
|
48
|
+
degrees.get(edge.source) ?? 1,
|
|
49
|
+
degrees.get(edge.target) ?? 1
|
|
50
|
+
);
|
|
51
|
+
penalty += dist * weight;
|
|
52
|
+
}
|
|
53
|
+
if (nodeGeometry) {
|
|
54
|
+
for (const edge of edgeList) {
|
|
55
|
+
const geomA = nodeGeometry.get(edge.source);
|
|
56
|
+
const geomB = nodeGeometry.get(edge.target);
|
|
57
|
+
if (!geomA || !geomB) continue;
|
|
58
|
+
const ax = nodePositions.get(edge.source) ?? 0;
|
|
59
|
+
const bx = nodePositions.get(edge.target) ?? 0;
|
|
60
|
+
const ay = geomA.y;
|
|
61
|
+
const by = geomB.y;
|
|
62
|
+
if (ay === by) continue;
|
|
63
|
+
const edgeMinX = Math.min(ax, bx);
|
|
64
|
+
const edgeMaxX = Math.max(ax, bx);
|
|
65
|
+
const edgeMinY = Math.min(ay, by);
|
|
66
|
+
const edgeMaxY = Math.max(ay, by);
|
|
67
|
+
for (const [name, geomC] of nodeGeometry) {
|
|
68
|
+
if (name === edge.source || name === edge.target) continue;
|
|
69
|
+
const cx = nodePositions.get(name) ?? 0;
|
|
70
|
+
const cy = geomC.y;
|
|
71
|
+
const hw = geomC.width / 2;
|
|
72
|
+
const hh = geomC.height / 2;
|
|
73
|
+
if (cx + hw > edgeMinX && cx - hw < edgeMaxX && cy + hh > edgeMinY && cy - hh < edgeMaxY) {
|
|
74
|
+
penalty += EDGE_NODE_COLLISION_WEIGHT;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return penalty;
|
|
80
|
+
}
|
|
81
|
+
function reduceCrossings(g, edgeList, nodeGroupMap) {
|
|
82
|
+
if (edgeList.length < 2) return;
|
|
83
|
+
const degrees = /* @__PURE__ */ new Map();
|
|
84
|
+
for (const edge of edgeList) {
|
|
85
|
+
degrees.set(edge.source, (degrees.get(edge.source) ?? 0) + 1);
|
|
86
|
+
degrees.set(edge.target, (degrees.get(edge.target) ?? 0) + 1);
|
|
87
|
+
}
|
|
88
|
+
const nodeGeometry = /* @__PURE__ */ new Map();
|
|
89
|
+
for (const name of g.nodes()) {
|
|
90
|
+
const pos = gNode(g, name);
|
|
91
|
+
if (pos)
|
|
92
|
+
nodeGeometry.set(name, {
|
|
93
|
+
y: pos.y,
|
|
94
|
+
width: pos.width,
|
|
95
|
+
height: pos.height
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
const rankMap = /* @__PURE__ */ new Map();
|
|
99
|
+
for (const name of g.nodes()) {
|
|
100
|
+
const pos = gNode(g, name);
|
|
101
|
+
if (!pos) continue;
|
|
102
|
+
const rankY = Math.round(pos.y);
|
|
103
|
+
if (!rankMap.has(rankY)) rankMap.set(rankY, []);
|
|
104
|
+
rankMap.get(rankY).push(name);
|
|
105
|
+
}
|
|
106
|
+
for (const [, rankNodes] of rankMap) {
|
|
107
|
+
rankNodes.sort((a, b) => gNode(g, a).x - gNode(g, b).x);
|
|
108
|
+
}
|
|
109
|
+
let anyMoved = false;
|
|
110
|
+
for (const [, rankNodes] of rankMap) {
|
|
111
|
+
if (rankNodes.length < 2) continue;
|
|
112
|
+
const partitions = [];
|
|
113
|
+
if (nodeGroupMap && nodeGroupMap.size > 0) {
|
|
114
|
+
const groupBuckets = /* @__PURE__ */ new Map();
|
|
115
|
+
const ungrouped = [];
|
|
116
|
+
for (const name of rankNodes) {
|
|
117
|
+
const grp = nodeGroupMap.get(name);
|
|
118
|
+
if (grp) {
|
|
119
|
+
if (!groupBuckets.has(grp)) groupBuckets.set(grp, []);
|
|
120
|
+
groupBuckets.get(grp).push(name);
|
|
121
|
+
} else {
|
|
122
|
+
ungrouped.push(name);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
for (const bucket of groupBuckets.values()) {
|
|
126
|
+
if (bucket.length >= 2) partitions.push(bucket);
|
|
127
|
+
}
|
|
128
|
+
if (ungrouped.length >= 2) partitions.push(ungrouped);
|
|
129
|
+
} else {
|
|
130
|
+
partitions.push(rankNodes);
|
|
131
|
+
}
|
|
132
|
+
for (const partition of partitions) {
|
|
133
|
+
if (partition.length < 2) continue;
|
|
134
|
+
const xSlots = partition.map((name) => gNode(g, name).x).sort((a, b) => a - b);
|
|
135
|
+
const basePositions = /* @__PURE__ */ new Map();
|
|
136
|
+
for (const name of g.nodes()) {
|
|
137
|
+
const pos = gNode(g, name);
|
|
138
|
+
if (pos) basePositions.set(name, pos.x);
|
|
139
|
+
}
|
|
140
|
+
const currentPenalty = computeEdgePenalty(
|
|
141
|
+
edgeList,
|
|
142
|
+
basePositions,
|
|
143
|
+
degrees,
|
|
144
|
+
nodeGeometry
|
|
145
|
+
);
|
|
146
|
+
let bestPerm = [...partition];
|
|
147
|
+
let bestPenalty = currentPenalty;
|
|
148
|
+
if (partition.length <= 8) {
|
|
149
|
+
const perms = permutations(partition);
|
|
150
|
+
for (const perm of perms) {
|
|
151
|
+
const testPositions = new Map(basePositions);
|
|
152
|
+
for (let i = 0; i < perm.length; i++) {
|
|
153
|
+
testPositions.set(perm[i], xSlots[i]);
|
|
154
|
+
}
|
|
155
|
+
const penalty = computeEdgePenalty(
|
|
156
|
+
edgeList,
|
|
157
|
+
testPositions,
|
|
158
|
+
degrees,
|
|
159
|
+
nodeGeometry
|
|
160
|
+
);
|
|
161
|
+
if (penalty < bestPenalty) {
|
|
162
|
+
bestPenalty = penalty;
|
|
163
|
+
bestPerm = [...perm];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
const workingOrder = [...partition];
|
|
168
|
+
let improved = true;
|
|
169
|
+
let passes = 0;
|
|
170
|
+
while (improved && passes < 10) {
|
|
171
|
+
improved = false;
|
|
172
|
+
passes++;
|
|
173
|
+
for (let i = 0; i < workingOrder.length - 1; i++) {
|
|
174
|
+
const testPositions = new Map(basePositions);
|
|
175
|
+
for (let k = 0; k < workingOrder.length; k++) {
|
|
176
|
+
testPositions.set(workingOrder[k], xSlots[k]);
|
|
177
|
+
}
|
|
178
|
+
const before = computeEdgePenalty(
|
|
179
|
+
edgeList,
|
|
180
|
+
testPositions,
|
|
181
|
+
degrees,
|
|
182
|
+
nodeGeometry
|
|
183
|
+
);
|
|
184
|
+
[workingOrder[i], workingOrder[i + 1]] = [
|
|
185
|
+
workingOrder[i + 1],
|
|
186
|
+
workingOrder[i]
|
|
187
|
+
];
|
|
188
|
+
const testPositions2 = new Map(basePositions);
|
|
189
|
+
for (let k = 0; k < workingOrder.length; k++) {
|
|
190
|
+
testPositions2.set(workingOrder[k], xSlots[k]);
|
|
191
|
+
}
|
|
192
|
+
const after = computeEdgePenalty(
|
|
193
|
+
edgeList,
|
|
194
|
+
testPositions2,
|
|
195
|
+
degrees,
|
|
196
|
+
nodeGeometry
|
|
197
|
+
);
|
|
198
|
+
if (after < before) {
|
|
199
|
+
improved = true;
|
|
200
|
+
if (after < bestPenalty) {
|
|
201
|
+
bestPenalty = after;
|
|
202
|
+
bestPerm = [...workingOrder];
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
[workingOrder[i], workingOrder[i + 1]] = [
|
|
206
|
+
workingOrder[i + 1],
|
|
207
|
+
workingOrder[i]
|
|
208
|
+
];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (bestPerm.some((name, i) => name !== partition[i])) {
|
|
214
|
+
for (let i = 0; i < bestPerm.length; i++) {
|
|
215
|
+
gNode(g, bestPerm[i]).x = xSlots[i];
|
|
216
|
+
const rankIdx = rankNodes.indexOf(partition[i]);
|
|
217
|
+
if (rankIdx >= 0) rankNodes[rankIdx] = bestPerm[i];
|
|
218
|
+
}
|
|
219
|
+
anyMoved = true;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (anyMoved) {
|
|
224
|
+
for (const edge of edgeList) {
|
|
225
|
+
const edgeData = gEdge(g, edge.source, edge.target);
|
|
226
|
+
if (!edgeData) continue;
|
|
227
|
+
const srcPos = gNode(g, edge.source);
|
|
228
|
+
const tgtPos = gNode(g, edge.target);
|
|
229
|
+
if (!srcPos || !tgtPos) continue;
|
|
230
|
+
const srcBottom = { x: srcPos.x, y: srcPos.y + srcPos.height / 2 };
|
|
231
|
+
const tgtTop = { x: tgtPos.x, y: tgtPos.y - tgtPos.height / 2 };
|
|
232
|
+
const midY = (srcBottom.y + tgtTop.y) / 2;
|
|
233
|
+
edgeData.points = [
|
|
234
|
+
srcBottom,
|
|
235
|
+
{ x: srcBottom.x, y: midY },
|
|
236
|
+
{ x: tgtTop.x, y: midY },
|
|
237
|
+
tgtTop
|
|
238
|
+
];
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function permutations(arr) {
|
|
243
|
+
const result = [];
|
|
244
|
+
const a = [...arr];
|
|
245
|
+
const n = a.length;
|
|
246
|
+
const c = new Array(n).fill(0);
|
|
247
|
+
result.push([...a]);
|
|
248
|
+
let i = 0;
|
|
249
|
+
while (i < n) {
|
|
250
|
+
if (c[i] < i) {
|
|
251
|
+
if (i % 2 === 0) {
|
|
252
|
+
[a[0], a[i]] = [a[i], a[0]];
|
|
253
|
+
} else {
|
|
254
|
+
[a[c[i]], a[i]] = [a[i], a[c[i]]];
|
|
255
|
+
}
|
|
256
|
+
result.push([...a]);
|
|
257
|
+
c[i]++;
|
|
258
|
+
i = 0;
|
|
259
|
+
} else {
|
|
260
|
+
c[i] = 0;
|
|
261
|
+
i++;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return result;
|
|
265
|
+
}
|
|
266
|
+
function buildOwnershipMap(elements) {
|
|
267
|
+
const map = /* @__PURE__ */ new Map();
|
|
268
|
+
function walk(el, ancestor) {
|
|
269
|
+
map.set(el.name, ancestor);
|
|
270
|
+
for (const child of el.children) {
|
|
271
|
+
walk(child, ancestor);
|
|
272
|
+
}
|
|
273
|
+
for (const group of el.groups) {
|
|
274
|
+
for (const child of group.children) {
|
|
275
|
+
walk(child, ancestor);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
for (const el of elements) {
|
|
280
|
+
walk(el, el.name);
|
|
281
|
+
}
|
|
282
|
+
return map;
|
|
283
|
+
}
|
|
284
|
+
function collectAllRelationships(elements, _ownerMap) {
|
|
285
|
+
const result = [];
|
|
286
|
+
function walk(el) {
|
|
287
|
+
for (const rel of el.relationships) {
|
|
288
|
+
result.push({ sourceName: el.name, rel });
|
|
289
|
+
}
|
|
290
|
+
for (const child of el.children) {
|
|
291
|
+
walk(child);
|
|
292
|
+
}
|
|
293
|
+
for (const group of el.groups) {
|
|
294
|
+
for (const child of group.children) {
|
|
295
|
+
walk(child);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
for (const el of elements) {
|
|
300
|
+
walk(el);
|
|
301
|
+
}
|
|
302
|
+
return result;
|
|
303
|
+
}
|
|
304
|
+
function rollUpContextRelationships(parsed) {
|
|
305
|
+
const ownerMap = buildOwnershipMap(parsed.elements);
|
|
306
|
+
const allRels = collectAllRelationships(parsed.elements, ownerMap);
|
|
307
|
+
const topLevelNames = new Set(parsed.elements.map((e) => e.name));
|
|
308
|
+
const explicitKeys = /* @__PURE__ */ new Set();
|
|
309
|
+
const explicit = [];
|
|
310
|
+
const nested = [];
|
|
311
|
+
for (const { sourceName, rel } of allRels) {
|
|
312
|
+
const sourceAncestor = ownerMap.get(sourceName) ?? sourceName;
|
|
313
|
+
const targetAncestor = ownerMap.get(rel.target) ?? rel.target;
|
|
314
|
+
if (sourceAncestor === targetAncestor) continue;
|
|
315
|
+
const entry = {
|
|
316
|
+
sourceName: sourceAncestor,
|
|
317
|
+
targetName: targetAncestor,
|
|
318
|
+
...rel.label !== void 0 && { label: rel.label },
|
|
319
|
+
...rel.technology !== void 0 && { technology: rel.technology },
|
|
320
|
+
arrowType: rel.arrowType,
|
|
321
|
+
lineNumber: rel.lineNumber
|
|
322
|
+
};
|
|
323
|
+
if (topLevelNames.has(sourceName) && sourceName === sourceAncestor) {
|
|
324
|
+
const key = `${sourceAncestor}\u2192${targetAncestor}`;
|
|
325
|
+
explicitKeys.add(key);
|
|
326
|
+
explicit.push(entry);
|
|
327
|
+
} else {
|
|
328
|
+
nested.push(entry);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const result = [...explicit];
|
|
332
|
+
const seenKeys = new Set(explicitKeys);
|
|
333
|
+
for (const rel of nested) {
|
|
334
|
+
const key = `${rel.sourceName}\u2192${rel.targetName}`;
|
|
335
|
+
if (!seenKeys.has(key)) {
|
|
336
|
+
seenKeys.add(key);
|
|
337
|
+
result.push(rel);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return result;
|
|
341
|
+
}
|
|
342
|
+
function resolveNodeColor(el, tagGroups, activeGroupName, ancestors) {
|
|
343
|
+
const colorMeta = el.metadata["color"];
|
|
344
|
+
if (colorMeta) return colorMeta;
|
|
345
|
+
if (!activeGroupName) return void 0;
|
|
346
|
+
const group = tagGroups.find(
|
|
347
|
+
(g) => g.name.toLowerCase() === activeGroupName.toLowerCase()
|
|
348
|
+
);
|
|
349
|
+
if (!group) return void 0;
|
|
350
|
+
const key = tagAttrKey(group.name);
|
|
351
|
+
let metaValue = el.metadata[key];
|
|
352
|
+
if (!metaValue && ancestors) {
|
|
353
|
+
for (const ancestor of ancestors) {
|
|
354
|
+
metaValue = ancestor.metadata[key];
|
|
355
|
+
if (metaValue) break;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
const resolvedValue = metaValue ?? group.defaultValue;
|
|
359
|
+
if (!resolvedValue) return "#999999";
|
|
360
|
+
return group.entries.find(
|
|
361
|
+
(e) => e.value.toLowerCase() === resolvedValue.toLowerCase()
|
|
362
|
+
)?.color ?? "#999999";
|
|
363
|
+
}
|
|
364
|
+
var META_EXCLUDE_KEYS = /* @__PURE__ */ new Set([
|
|
365
|
+
"description",
|
|
366
|
+
"tech",
|
|
367
|
+
"technology",
|
|
368
|
+
"is a"
|
|
369
|
+
]);
|
|
370
|
+
function collectCardMetadata(metadata) {
|
|
371
|
+
const entries = [];
|
|
372
|
+
const tech = metadata["tech"] ?? metadata["technology"];
|
|
373
|
+
if (tech) entries.push({ key: "Technology", value: tech });
|
|
374
|
+
for (const [k, v] of Object.entries(metadata)) {
|
|
375
|
+
if (META_EXCLUDE_KEYS.has(k.toLowerCase())) continue;
|
|
376
|
+
const displayKey = k.charAt(0).toUpperCase() + k.slice(1);
|
|
377
|
+
entries.push({ key: displayKey, value: v });
|
|
378
|
+
}
|
|
379
|
+
return entries;
|
|
380
|
+
}
|
|
381
|
+
function computeC4NodeDimensions(el, options) {
|
|
382
|
+
const nameWidth = measureText(el.name, NAME_FONT_SIZE) + CARD_H_PAD * 2;
|
|
383
|
+
let width = Math.max(MIN_NODE_WIDTH, Math.min(MAX_NODE_WIDTH, nameWidth));
|
|
384
|
+
if (options?.showTechnology) {
|
|
385
|
+
let height2 = CARD_V_PAD + NAME_HEIGHT;
|
|
386
|
+
const desc2 = el.description?.join("\n");
|
|
387
|
+
if (desc2) {
|
|
388
|
+
const contentWidth = width - CARD_H_PAD * 2;
|
|
389
|
+
const lines = wrapTextToWidth(desc2, DESC_FONT_SIZE, contentWidth);
|
|
390
|
+
height2 += lines.length * DESC_LINE_HEIGHT;
|
|
391
|
+
}
|
|
392
|
+
const metaEntries = collectCardMetadata(el.metadata);
|
|
393
|
+
if (metaEntries.length > 0) {
|
|
394
|
+
height2 += DIVIDER_GAP;
|
|
395
|
+
height2 += metaEntries.length * META_LINE_HEIGHT;
|
|
396
|
+
const maxMetaWidth = Math.max(
|
|
397
|
+
...metaEntries.map(
|
|
398
|
+
(e) => measureText(`${e.key}: ${e.value}`, META_FONT_SIZE) + CARD_H_PAD * 2
|
|
399
|
+
)
|
|
400
|
+
);
|
|
401
|
+
if (maxMetaWidth > width) width = Math.min(MAX_NODE_WIDTH, maxMetaWidth);
|
|
402
|
+
}
|
|
403
|
+
height2 += CARD_V_PAD;
|
|
404
|
+
return { width, height: height2 };
|
|
405
|
+
}
|
|
406
|
+
let height = CARD_V_PAD + TYPE_LABEL_HEIGHT + DIVIDER_GAP + NAME_HEIGHT;
|
|
407
|
+
const desc = el.description?.join("\n");
|
|
408
|
+
if (desc) {
|
|
409
|
+
const contentWidth = width - CARD_H_PAD * 2;
|
|
410
|
+
const lines = wrapTextToWidth(desc, DESC_FONT_SIZE, contentWidth);
|
|
411
|
+
height += lines.length * DESC_LINE_HEIGHT;
|
|
412
|
+
}
|
|
413
|
+
height += CARD_V_PAD;
|
|
414
|
+
return { width, height };
|
|
415
|
+
}
|
|
416
|
+
function computeLegendGroups(tagGroups) {
|
|
417
|
+
const result = [];
|
|
418
|
+
for (const group of tagGroups) {
|
|
419
|
+
const entries = [];
|
|
420
|
+
for (const entry of group.entries) {
|
|
421
|
+
entries.push({ value: entry.value, color: entry.color });
|
|
422
|
+
}
|
|
423
|
+
if (entries.length === 0) continue;
|
|
424
|
+
const nameW = measureLegendText(group.name, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD * 2;
|
|
425
|
+
let capsuleW = LEGEND_CAPSULE_PAD;
|
|
426
|
+
for (const e of entries) {
|
|
427
|
+
capsuleW += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + measureLegendText(e.value, LEGEND_ENTRY_FONT_SIZE) + LEGEND_ENTRY_TRAIL;
|
|
428
|
+
}
|
|
429
|
+
capsuleW += LEGEND_CAPSULE_PAD;
|
|
430
|
+
result.push({
|
|
431
|
+
name: group.name,
|
|
432
|
+
entries,
|
|
433
|
+
x: 0,
|
|
434
|
+
y: 0,
|
|
435
|
+
width: nameW + capsuleW,
|
|
436
|
+
height: LEGEND_HEIGHT
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
return result;
|
|
440
|
+
}
|
|
441
|
+
function computeAdaptiveSpacing(edges) {
|
|
442
|
+
const outDegree = /* @__PURE__ */ new Map();
|
|
443
|
+
for (const edge of edges) {
|
|
444
|
+
if (edge.label || edge.technology) {
|
|
445
|
+
outDegree.set(edge.sourceName, (outDegree.get(edge.sourceName) ?? 0) + 1);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
const maxFanOut = Math.max(0, ...outDegree.values());
|
|
449
|
+
const nodesep = Math.max(80, 60 + maxFanOut * 20);
|
|
450
|
+
const ranksep = Math.max(140, 100 + maxFanOut * 35);
|
|
451
|
+
const edgesep = Math.max(30, 20 + maxFanOut * 8);
|
|
452
|
+
return { nodesep, ranksep, edgesep };
|
|
453
|
+
}
|
|
454
|
+
function layoutC4Context(parsed, activeTagGroup) {
|
|
455
|
+
const contextElements = parsed.elements.filter(
|
|
456
|
+
(el) => el.type === "person" || el.type === "system"
|
|
457
|
+
);
|
|
458
|
+
if (contextElements.length === 0) {
|
|
459
|
+
return {
|
|
460
|
+
nodes: [],
|
|
461
|
+
edges: [],
|
|
462
|
+
legend: [],
|
|
463
|
+
groupBoundaries: [],
|
|
464
|
+
width: 0,
|
|
465
|
+
height: 0
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
const contextRels = rollUpContextRelationships(parsed);
|
|
469
|
+
const spacing = computeAdaptiveSpacing(contextRels);
|
|
470
|
+
const g = new dagre.graphlib.Graph();
|
|
471
|
+
g.setGraph({
|
|
472
|
+
rankdir: "TB",
|
|
473
|
+
nodesep: spacing.nodesep,
|
|
474
|
+
ranksep: spacing.ranksep,
|
|
475
|
+
edgesep: spacing.edgesep
|
|
476
|
+
});
|
|
477
|
+
g.setDefaultEdgeLabel(() => ({}));
|
|
478
|
+
const nameToElement = /* @__PURE__ */ new Map();
|
|
479
|
+
for (const el of contextElements) {
|
|
480
|
+
nameToElement.set(el.name, el);
|
|
481
|
+
const dims = computeC4NodeDimensions(el);
|
|
482
|
+
g.setNode(el.name, { width: dims.width, height: dims.height });
|
|
483
|
+
}
|
|
484
|
+
const validRels = [];
|
|
485
|
+
for (const rel of contextRels) {
|
|
486
|
+
if (nameToElement.has(rel.sourceName) && nameToElement.has(rel.targetName)) {
|
|
487
|
+
validRels.push(rel);
|
|
488
|
+
g.setEdge(rel.sourceName, rel.targetName, { label: rel.label ?? "" });
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
dagre.layout(g);
|
|
492
|
+
reduceCrossings(
|
|
493
|
+
g,
|
|
494
|
+
validRels.map((r) => ({ source: r.sourceName, target: r.targetName }))
|
|
495
|
+
);
|
|
496
|
+
const nodes = contextElements.map(
|
|
497
|
+
(el) => {
|
|
498
|
+
const pos = g.node(el.name);
|
|
499
|
+
const color = resolveNodeColor(
|
|
500
|
+
el,
|
|
501
|
+
parsed.tagGroups,
|
|
502
|
+
activeTagGroup ?? null
|
|
503
|
+
);
|
|
504
|
+
const hasContainers = el.children.some((c) => c.type === "container") || el.groups.some((g2) => g2.children.some((c) => c.type === "container"));
|
|
505
|
+
const description = el.description?.join("\n");
|
|
506
|
+
const drillable = hasContainers || el.importPath ? true : void 0;
|
|
507
|
+
return {
|
|
508
|
+
id: el.name,
|
|
509
|
+
name: el.name,
|
|
510
|
+
type: el.type,
|
|
511
|
+
...description !== void 0 && { description },
|
|
512
|
+
metadata: el.metadata,
|
|
513
|
+
lineNumber: el.lineNumber,
|
|
514
|
+
...color !== void 0 && { color },
|
|
515
|
+
...drillable !== void 0 && { drillable },
|
|
516
|
+
...el.importPath !== void 0 && { importPath: el.importPath },
|
|
517
|
+
x: pos.x,
|
|
518
|
+
y: pos.y,
|
|
519
|
+
width: pos.width,
|
|
520
|
+
height: pos.height
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
);
|
|
524
|
+
const edges = validRels.map(
|
|
525
|
+
(rel) => {
|
|
526
|
+
const edgeData = g.edge(rel.sourceName, rel.targetName);
|
|
527
|
+
return {
|
|
528
|
+
source: rel.sourceName,
|
|
529
|
+
target: rel.targetName,
|
|
530
|
+
arrowType: rel.arrowType,
|
|
531
|
+
...rel.label !== void 0 && { label: rel.label },
|
|
532
|
+
...rel.technology !== void 0 && { technology: rel.technology },
|
|
533
|
+
lineNumber: rel.lineNumber,
|
|
534
|
+
points: edgeData?.points ?? []
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
);
|
|
538
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
539
|
+
for (const node of nodes) {
|
|
540
|
+
const left = node.x - node.width / 2;
|
|
541
|
+
const top = node.y - node.height / 2;
|
|
542
|
+
const right = node.x + node.width / 2;
|
|
543
|
+
const bottom = node.y + node.height / 2;
|
|
544
|
+
if (left < minX) minX = left;
|
|
545
|
+
if (top < minY) minY = top;
|
|
546
|
+
if (right > maxX) maxX = right;
|
|
547
|
+
if (bottom > maxY) maxY = bottom;
|
|
548
|
+
}
|
|
549
|
+
for (const edge of edges) {
|
|
550
|
+
for (const pt of edge.points) {
|
|
551
|
+
if (pt.x < minX) minX = pt.x;
|
|
552
|
+
if (pt.y < minY) minY = pt.y;
|
|
553
|
+
if (pt.x > maxX) maxX = pt.x;
|
|
554
|
+
if (pt.y > maxY) maxY = pt.y;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
if (nodes.length > 0) {
|
|
558
|
+
const shiftX = MARGIN - minX;
|
|
559
|
+
const shiftY = MARGIN - minY;
|
|
560
|
+
for (const node of nodes) {
|
|
561
|
+
node.x += shiftX;
|
|
562
|
+
node.y += shiftY;
|
|
563
|
+
}
|
|
564
|
+
for (const edge of edges) {
|
|
565
|
+
for (const pt of edge.points) {
|
|
566
|
+
pt.x += shiftX;
|
|
567
|
+
pt.y += shiftY;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
let totalWidth = nodes.length > 0 ? maxX - minX + MARGIN * 2 : 0;
|
|
572
|
+
let totalHeight = nodes.length > 0 ? maxY - minY + MARGIN * 2 : 0;
|
|
573
|
+
const legendGroups = computeLegendGroups(parsed.tagGroups);
|
|
574
|
+
if (legendGroups.length > 0) {
|
|
575
|
+
const legendY = totalHeight + MARGIN;
|
|
576
|
+
let legendX = MARGIN;
|
|
577
|
+
for (const lg of legendGroups) {
|
|
578
|
+
lg.x = legendX;
|
|
579
|
+
lg.y = legendY;
|
|
580
|
+
legendX += lg.width + 12;
|
|
581
|
+
}
|
|
582
|
+
const legendRight = legendX;
|
|
583
|
+
const legendBottom = legendY + LEGEND_HEIGHT;
|
|
584
|
+
if (legendRight > totalWidth) totalWidth = legendRight;
|
|
585
|
+
if (legendBottom > totalHeight) totalHeight = legendBottom;
|
|
586
|
+
}
|
|
587
|
+
return {
|
|
588
|
+
nodes,
|
|
589
|
+
edges,
|
|
590
|
+
legend: legendGroups,
|
|
591
|
+
groupBoundaries: [],
|
|
592
|
+
width: totalWidth,
|
|
593
|
+
height: totalHeight
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
function layoutC4Containers(parsed, systemName, activeTagGroup) {
|
|
597
|
+
const system = parsed.elements.find(
|
|
598
|
+
(el) => el.name.toLowerCase() === systemName.toLowerCase()
|
|
599
|
+
);
|
|
600
|
+
if (!system) {
|
|
601
|
+
return {
|
|
602
|
+
nodes: [],
|
|
603
|
+
edges: [],
|
|
604
|
+
legend: [],
|
|
605
|
+
groupBoundaries: [],
|
|
606
|
+
width: 0,
|
|
607
|
+
height: 0
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
const containers = [];
|
|
611
|
+
for (const child of system.children) {
|
|
612
|
+
if (child.type === "container") containers.push(child);
|
|
613
|
+
}
|
|
614
|
+
for (const group of system.groups) {
|
|
615
|
+
for (const child of group.children) {
|
|
616
|
+
if (child.type === "container") containers.push(child);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
if (containers.length === 0) {
|
|
620
|
+
return {
|
|
621
|
+
nodes: [],
|
|
622
|
+
edges: [],
|
|
623
|
+
legend: [],
|
|
624
|
+
groupBoundaries: [],
|
|
625
|
+
width: 0,
|
|
626
|
+
height: 0
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
const containerNames = new Set(containers.map((c) => c.name.toLowerCase()));
|
|
630
|
+
const topElementsByName = /* @__PURE__ */ new Map();
|
|
631
|
+
for (const el of parsed.elements) {
|
|
632
|
+
topElementsByName.set(el.name.toLowerCase(), el);
|
|
633
|
+
}
|
|
634
|
+
const externalNames = /* @__PURE__ */ new Set();
|
|
635
|
+
for (const container of containers) {
|
|
636
|
+
for (const rel of container.relationships) {
|
|
637
|
+
const targetLower = rel.target.toLowerCase();
|
|
638
|
+
if (!containerNames.has(targetLower)) {
|
|
639
|
+
externalNames.add(targetLower);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
const ownerMap = buildOwnershipMap(parsed.elements);
|
|
644
|
+
const allRels = collectAllRelationships(parsed.elements, ownerMap);
|
|
645
|
+
for (const { sourceName, rel } of allRels) {
|
|
646
|
+
const sourceAncestor = ownerMap.get(sourceName) ?? sourceName;
|
|
647
|
+
if (sourceAncestor.toLowerCase() === system.name.toLowerCase()) continue;
|
|
648
|
+
if (containerNames.has(rel.target.toLowerCase())) {
|
|
649
|
+
externalNames.add(sourceAncestor.toLowerCase());
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
const externals = [];
|
|
653
|
+
for (const name of externalNames) {
|
|
654
|
+
const el = topElementsByName.get(name);
|
|
655
|
+
if (el) externals.push(el);
|
|
656
|
+
}
|
|
657
|
+
const elementToGroup = /* @__PURE__ */ new Map();
|
|
658
|
+
for (const group of system.groups) {
|
|
659
|
+
for (const child of group.children) {
|
|
660
|
+
if (child.type === "container") {
|
|
661
|
+
elementToGroup.set(child.name, {
|
|
662
|
+
name: group.name,
|
|
663
|
+
lineNumber: group.lineNumber
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
const hasGroups = elementToGroup.size > 0;
|
|
669
|
+
const g = hasGroups ? new dagre.graphlib.Graph({ compound: true }) : new dagre.graphlib.Graph();
|
|
670
|
+
g.setDefaultEdgeLabel(() => ({}));
|
|
671
|
+
if (hasGroups) {
|
|
672
|
+
const seenGroups = /* @__PURE__ */ new Set();
|
|
673
|
+
for (const { name } of elementToGroup.values()) {
|
|
674
|
+
if (!seenGroups.has(name)) {
|
|
675
|
+
seenGroups.add(name);
|
|
676
|
+
g.setNode("__group_" + name, {});
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
const nameToElement = /* @__PURE__ */ new Map();
|
|
681
|
+
for (const el of containers) {
|
|
682
|
+
nameToElement.set(el.name, el);
|
|
683
|
+
const dims = computeC4NodeDimensions(el, { showTechnology: true });
|
|
684
|
+
g.setNode(el.name, { width: dims.width, height: dims.height });
|
|
685
|
+
const grp = elementToGroup.get(el.name);
|
|
686
|
+
if (grp) {
|
|
687
|
+
g.setParent(el.name, "__group_" + grp.name);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
for (const el of externals) {
|
|
691
|
+
nameToElement.set(el.name, el);
|
|
692
|
+
const dims = computeC4NodeDimensions(el);
|
|
693
|
+
g.setNode(el.name, { width: dims.width, height: dims.height });
|
|
694
|
+
}
|
|
695
|
+
const containerRels = [];
|
|
696
|
+
const seenEdgeKeys = /* @__PURE__ */ new Set();
|
|
697
|
+
for (const container of containers) {
|
|
698
|
+
for (const rel of container.relationships) {
|
|
699
|
+
const targetEl = nameToElement.get(rel.target);
|
|
700
|
+
if (!targetEl) continue;
|
|
701
|
+
const key = `${container.name}\u2192${rel.target}`;
|
|
702
|
+
if (!seenEdgeKeys.has(key)) {
|
|
703
|
+
seenEdgeKeys.add(key);
|
|
704
|
+
containerRels.push({
|
|
705
|
+
sourceName: container.name,
|
|
706
|
+
targetName: rel.target,
|
|
707
|
+
...rel.label !== void 0 && { label: rel.label },
|
|
708
|
+
...rel.technology !== void 0 && { technology: rel.technology },
|
|
709
|
+
arrowType: rel.arrowType,
|
|
710
|
+
lineNumber: rel.lineNumber
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
for (const { sourceName, rel } of allRels) {
|
|
716
|
+
const sourceAncestor = ownerMap.get(sourceName) ?? sourceName;
|
|
717
|
+
if (sourceAncestor.toLowerCase() === system.name.toLowerCase()) continue;
|
|
718
|
+
if (containerNames.has(rel.target.toLowerCase())) {
|
|
719
|
+
const externalEl = nameToElement.get(sourceAncestor);
|
|
720
|
+
if (!externalEl) continue;
|
|
721
|
+
const key = `${sourceAncestor}\u2192${rel.target}`;
|
|
722
|
+
if (!seenEdgeKeys.has(key)) {
|
|
723
|
+
seenEdgeKeys.add(key);
|
|
724
|
+
containerRels.push({
|
|
725
|
+
sourceName: sourceAncestor,
|
|
726
|
+
targetName: rel.target,
|
|
727
|
+
...rel.label !== void 0 && { label: rel.label },
|
|
728
|
+
...rel.technology !== void 0 && { technology: rel.technology },
|
|
729
|
+
arrowType: rel.arrowType,
|
|
730
|
+
lineNumber: rel.lineNumber
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
const spacing = computeAdaptiveSpacing(containerRels);
|
|
736
|
+
g.setGraph({
|
|
737
|
+
rankdir: "TB",
|
|
738
|
+
nodesep: spacing.nodesep,
|
|
739
|
+
ranksep: spacing.ranksep,
|
|
740
|
+
edgesep: spacing.edgesep
|
|
741
|
+
});
|
|
742
|
+
for (const rel of containerRels) {
|
|
743
|
+
if (nameToElement.has(rel.sourceName) && nameToElement.has(rel.targetName)) {
|
|
744
|
+
g.setEdge(rel.sourceName, rel.targetName, { label: rel.label ?? "" });
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
dagre.layout(g);
|
|
748
|
+
const nodeGroupMap = hasGroups ? new Map([...elementToGroup.entries()].map(([k, v]) => [k, v.name])) : void 0;
|
|
749
|
+
reduceCrossings(
|
|
750
|
+
g,
|
|
751
|
+
containerRels.filter(
|
|
752
|
+
(r) => nameToElement.has(r.sourceName) && nameToElement.has(r.targetName)
|
|
753
|
+
).map((r) => ({ source: r.sourceName, target: r.targetName })),
|
|
754
|
+
nodeGroupMap
|
|
755
|
+
);
|
|
756
|
+
const nodes = [];
|
|
757
|
+
for (const el of containers) {
|
|
758
|
+
const pos = g.node(el.name);
|
|
759
|
+
const color = resolveNodeColor(
|
|
760
|
+
el,
|
|
761
|
+
parsed.tagGroups,
|
|
762
|
+
activeTagGroup ?? null
|
|
763
|
+
);
|
|
764
|
+
const tech = el.metadata["tech"] ?? el.metadata["technology"];
|
|
765
|
+
const hasComponents = el.children.some((c) => c.type === "component") || el.groups.some((grp) => grp.children.some((c) => c.type === "component"));
|
|
766
|
+
const description = el.description?.join("\n");
|
|
767
|
+
const drillable = hasComponents || el.importPath ? true : void 0;
|
|
768
|
+
nodes.push({
|
|
769
|
+
id: el.name,
|
|
770
|
+
name: el.name,
|
|
771
|
+
type: "container",
|
|
772
|
+
...description !== void 0 && { description },
|
|
773
|
+
metadata: el.metadata,
|
|
774
|
+
lineNumber: el.lineNumber,
|
|
775
|
+
...color !== void 0 && { color },
|
|
776
|
+
shape: el.shape,
|
|
777
|
+
...tech !== void 0 && { technology: tech },
|
|
778
|
+
...drillable !== void 0 && { drillable },
|
|
779
|
+
...el.importPath !== void 0 && { importPath: el.importPath },
|
|
780
|
+
x: pos.x,
|
|
781
|
+
y: pos.y,
|
|
782
|
+
width: pos.width,
|
|
783
|
+
height: pos.height
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
for (const el of externals) {
|
|
787
|
+
const pos = g.node(el.name);
|
|
788
|
+
const color = resolveNodeColor(
|
|
789
|
+
el,
|
|
790
|
+
parsed.tagGroups,
|
|
791
|
+
activeTagGroup ?? null
|
|
792
|
+
);
|
|
793
|
+
const description = el.description?.join("\n");
|
|
794
|
+
nodes.push({
|
|
795
|
+
id: el.name,
|
|
796
|
+
name: el.name,
|
|
797
|
+
type: el.type,
|
|
798
|
+
...description !== void 0 && { description },
|
|
799
|
+
metadata: el.metadata,
|
|
800
|
+
lineNumber: el.lineNumber,
|
|
801
|
+
...color !== void 0 && { color },
|
|
802
|
+
x: pos.x,
|
|
803
|
+
y: pos.y,
|
|
804
|
+
width: pos.width,
|
|
805
|
+
height: pos.height
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
const edges = containerRels.filter(
|
|
809
|
+
(rel) => nameToElement.has(rel.sourceName) && nameToElement.has(rel.targetName)
|
|
810
|
+
).map((rel) => {
|
|
811
|
+
const edgeData = g.edge(rel.sourceName, rel.targetName);
|
|
812
|
+
return {
|
|
813
|
+
source: rel.sourceName,
|
|
814
|
+
target: rel.targetName,
|
|
815
|
+
arrowType: rel.arrowType,
|
|
816
|
+
...rel.label !== void 0 && { label: rel.label },
|
|
817
|
+
...rel.technology !== void 0 && { technology: rel.technology },
|
|
818
|
+
lineNumber: rel.lineNumber,
|
|
819
|
+
points: edgeData?.points ?? []
|
|
820
|
+
};
|
|
821
|
+
});
|
|
822
|
+
const containerNodes = nodes.filter((n) => n.type === "container");
|
|
823
|
+
let bMinX = Infinity, bMinY = Infinity, bMaxX = -Infinity, bMaxY = -Infinity;
|
|
824
|
+
for (const n of containerNodes) {
|
|
825
|
+
const left = n.x - n.width / 2;
|
|
826
|
+
const top = n.y - n.height / 2;
|
|
827
|
+
const right = n.x + n.width / 2;
|
|
828
|
+
const bottom = n.y + n.height / 2;
|
|
829
|
+
if (left < bMinX) bMinX = left;
|
|
830
|
+
if (top < bMinY) bMinY = top;
|
|
831
|
+
if (right > bMaxX) bMaxX = right;
|
|
832
|
+
if (bottom > bMaxY) bMaxY = bottom;
|
|
833
|
+
}
|
|
834
|
+
const boundary = {
|
|
835
|
+
label: system.name,
|
|
836
|
+
typeLabel: "system",
|
|
837
|
+
lineNumber: system.lineNumber,
|
|
838
|
+
x: bMinX - BOUNDARY_PAD,
|
|
839
|
+
y: bMinY - BOUNDARY_PAD,
|
|
840
|
+
width: bMaxX - bMinX + BOUNDARY_PAD * 2,
|
|
841
|
+
height: bMaxY - bMinY + BOUNDARY_PAD * 2
|
|
842
|
+
};
|
|
843
|
+
const groupBoundaries = [];
|
|
844
|
+
if (hasGroups) {
|
|
845
|
+
const nodeMap = new Map(containerNodes.map((n) => [n.name, n]));
|
|
846
|
+
const seenGroups = /* @__PURE__ */ new Map();
|
|
847
|
+
for (const [elName, grp] of elementToGroup) {
|
|
848
|
+
const node = nodeMap.get(elName);
|
|
849
|
+
if (!node) continue;
|
|
850
|
+
if (!seenGroups.has(grp.name)) {
|
|
851
|
+
seenGroups.set(grp.name, { lineNumber: grp.lineNumber, members: [] });
|
|
852
|
+
}
|
|
853
|
+
seenGroups.get(grp.name).members.push(node);
|
|
854
|
+
}
|
|
855
|
+
for (const [groupName, { lineNumber, members }] of seenGroups) {
|
|
856
|
+
if (members.length === 0) continue;
|
|
857
|
+
let gMinX = Infinity, gMinY = Infinity, gMaxX = -Infinity, gMaxY = -Infinity;
|
|
858
|
+
for (const m of members) {
|
|
859
|
+
const left = m.x - m.width / 2;
|
|
860
|
+
const top = m.y - m.height / 2;
|
|
861
|
+
const right = m.x + m.width / 2;
|
|
862
|
+
const bottom = m.y + m.height / 2;
|
|
863
|
+
if (left < gMinX) gMinX = left;
|
|
864
|
+
if (top < gMinY) gMinY = top;
|
|
865
|
+
if (right > gMaxX) gMaxX = right;
|
|
866
|
+
if (bottom > gMaxY) gMaxY = bottom;
|
|
867
|
+
}
|
|
868
|
+
groupBoundaries.push({
|
|
869
|
+
label: groupName,
|
|
870
|
+
typeLabel: "group",
|
|
871
|
+
lineNumber,
|
|
872
|
+
x: gMinX - GROUP_BOUNDARY_PAD,
|
|
873
|
+
y: gMinY - GROUP_BOUNDARY_PAD,
|
|
874
|
+
width: gMaxX - gMinX + GROUP_BOUNDARY_PAD * 2,
|
|
875
|
+
height: gMaxY - gMinY + GROUP_BOUNDARY_PAD * 2
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
880
|
+
for (const node of nodes) {
|
|
881
|
+
const left = node.x - node.width / 2;
|
|
882
|
+
const top = node.y - node.height / 2;
|
|
883
|
+
const right = node.x + node.width / 2;
|
|
884
|
+
const bottom = node.y + node.height / 2;
|
|
885
|
+
if (left < minX) minX = left;
|
|
886
|
+
if (top < minY) minY = top;
|
|
887
|
+
if (right > maxX) maxX = right;
|
|
888
|
+
if (bottom > maxY) maxY = bottom;
|
|
889
|
+
}
|
|
890
|
+
if (boundary.x < minX) minX = boundary.x;
|
|
891
|
+
if (boundary.y < minY) minY = boundary.y;
|
|
892
|
+
if (boundary.x + boundary.width > maxX) maxX = boundary.x + boundary.width;
|
|
893
|
+
if (boundary.y + boundary.height > maxY) maxY = boundary.y + boundary.height;
|
|
894
|
+
for (const gb of groupBoundaries) {
|
|
895
|
+
if (gb.x < minX) minX = gb.x;
|
|
896
|
+
if (gb.y < minY) minY = gb.y;
|
|
897
|
+
if (gb.x + gb.width > maxX) maxX = gb.x + gb.width;
|
|
898
|
+
if (gb.y + gb.height > maxY) maxY = gb.y + gb.height;
|
|
899
|
+
}
|
|
900
|
+
for (const edge of edges) {
|
|
901
|
+
for (const pt of edge.points) {
|
|
902
|
+
if (pt.x < minX) minX = pt.x;
|
|
903
|
+
if (pt.y < minY) minY = pt.y;
|
|
904
|
+
if (pt.x > maxX) maxX = pt.x;
|
|
905
|
+
if (pt.y > maxY) maxY = pt.y;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
const shiftX = MARGIN - minX;
|
|
909
|
+
const shiftY = MARGIN - minY;
|
|
910
|
+
for (const node of nodes) {
|
|
911
|
+
node.x += shiftX;
|
|
912
|
+
node.y += shiftY;
|
|
913
|
+
}
|
|
914
|
+
boundary.x += shiftX;
|
|
915
|
+
boundary.y += shiftY;
|
|
916
|
+
for (const gb of groupBoundaries) {
|
|
917
|
+
gb.x += shiftX;
|
|
918
|
+
gb.y += shiftY;
|
|
919
|
+
}
|
|
920
|
+
for (const edge of edges) {
|
|
921
|
+
for (const pt of edge.points) {
|
|
922
|
+
pt.x += shiftX;
|
|
923
|
+
pt.y += shiftY;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
let totalWidth = maxX - minX + MARGIN * 2;
|
|
927
|
+
let totalHeight = maxY - minY + MARGIN * 2;
|
|
928
|
+
const legendGroups = computeLegendGroups(parsed.tagGroups);
|
|
929
|
+
if (legendGroups.length > 0) {
|
|
930
|
+
const legendY = totalHeight + MARGIN;
|
|
931
|
+
let legendX = MARGIN;
|
|
932
|
+
for (const lg of legendGroups) {
|
|
933
|
+
lg.x = legendX;
|
|
934
|
+
lg.y = legendY;
|
|
935
|
+
legendX += lg.width + 12;
|
|
936
|
+
}
|
|
937
|
+
const legendRight = legendX;
|
|
938
|
+
const legendBottom = legendY + LEGEND_HEIGHT;
|
|
939
|
+
if (legendRight > totalWidth) totalWidth = legendRight;
|
|
940
|
+
if (legendBottom > totalHeight) totalHeight = legendBottom;
|
|
941
|
+
}
|
|
942
|
+
return {
|
|
943
|
+
nodes,
|
|
944
|
+
edges,
|
|
945
|
+
legend: legendGroups,
|
|
946
|
+
boundary,
|
|
947
|
+
groupBoundaries,
|
|
948
|
+
width: totalWidth,
|
|
949
|
+
height: totalHeight
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
function layoutC4Components(parsed, systemName, containerName, activeTagGroup) {
|
|
953
|
+
const system = parsed.elements.find(
|
|
954
|
+
(el) => el.name.toLowerCase() === systemName.toLowerCase()
|
|
955
|
+
);
|
|
956
|
+
if (!system) {
|
|
957
|
+
return {
|
|
958
|
+
nodes: [],
|
|
959
|
+
edges: [],
|
|
960
|
+
legend: [],
|
|
961
|
+
groupBoundaries: [],
|
|
962
|
+
width: 0,
|
|
963
|
+
height: 0
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
let targetContainer;
|
|
967
|
+
for (const child of system.children) {
|
|
968
|
+
if (child.type === "container" && child.name.toLowerCase() === containerName.toLowerCase()) {
|
|
969
|
+
targetContainer = child;
|
|
970
|
+
break;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
if (!targetContainer) {
|
|
974
|
+
for (const group of system.groups) {
|
|
975
|
+
for (const child of group.children) {
|
|
976
|
+
if (child.type === "container" && child.name.toLowerCase() === containerName.toLowerCase()) {
|
|
977
|
+
targetContainer = child;
|
|
978
|
+
break;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
if (targetContainer) break;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
if (!targetContainer) {
|
|
985
|
+
return {
|
|
986
|
+
nodes: [],
|
|
987
|
+
edges: [],
|
|
988
|
+
legend: [],
|
|
989
|
+
groupBoundaries: [],
|
|
990
|
+
width: 0,
|
|
991
|
+
height: 0
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
const components = [];
|
|
995
|
+
for (const child of targetContainer.children) {
|
|
996
|
+
if (child.type === "component") components.push(child);
|
|
997
|
+
}
|
|
998
|
+
for (const group of targetContainer.groups) {
|
|
999
|
+
for (const child of group.children) {
|
|
1000
|
+
if (child.type === "component") components.push(child);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
if (components.length === 0) {
|
|
1004
|
+
return {
|
|
1005
|
+
nodes: [],
|
|
1006
|
+
edges: [],
|
|
1007
|
+
legend: [],
|
|
1008
|
+
groupBoundaries: [],
|
|
1009
|
+
width: 0,
|
|
1010
|
+
height: 0
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
const componentNames = new Set(components.map((c) => c.name.toLowerCase()));
|
|
1014
|
+
const topElementsByName = /* @__PURE__ */ new Map();
|
|
1015
|
+
for (const el of parsed.elements) {
|
|
1016
|
+
topElementsByName.set(el.name.toLowerCase(), el);
|
|
1017
|
+
for (const child of el.children) {
|
|
1018
|
+
if (child.type === "container") {
|
|
1019
|
+
topElementsByName.set(child.name.toLowerCase(), child);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
for (const group of el.groups) {
|
|
1023
|
+
for (const child of group.children) {
|
|
1024
|
+
if (child.type === "container") {
|
|
1025
|
+
topElementsByName.set(child.name.toLowerCase(), child);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
const externalNames = /* @__PURE__ */ new Set();
|
|
1031
|
+
for (const component of components) {
|
|
1032
|
+
for (const rel of component.relationships) {
|
|
1033
|
+
const targetLower = rel.target.toLowerCase();
|
|
1034
|
+
if (!componentNames.has(targetLower)) {
|
|
1035
|
+
externalNames.add(targetLower);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
const ownerMap = buildOwnershipMap(parsed.elements);
|
|
1040
|
+
const allRels = collectAllRelationships(parsed.elements, ownerMap);
|
|
1041
|
+
for (const { sourceName, rel } of allRels) {
|
|
1042
|
+
if (componentNames.has(sourceName.toLowerCase())) continue;
|
|
1043
|
+
if (componentNames.has(rel.target.toLowerCase())) {
|
|
1044
|
+
const sourceAncestor = ownerMap.get(sourceName) ?? sourceName;
|
|
1045
|
+
if (sourceAncestor.toLowerCase() === targetContainer.name.toLowerCase())
|
|
1046
|
+
continue;
|
|
1047
|
+
if (sourceAncestor.toLowerCase() === system.name.toLowerCase()) {
|
|
1048
|
+
const sourceLower = sourceName.toLowerCase();
|
|
1049
|
+
if (topElementsByName.has(sourceLower)) {
|
|
1050
|
+
externalNames.add(sourceLower);
|
|
1051
|
+
} else {
|
|
1052
|
+
externalNames.add(sourceAncestor.toLowerCase());
|
|
1053
|
+
}
|
|
1054
|
+
} else {
|
|
1055
|
+
externalNames.add(sourceAncestor.toLowerCase());
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
const externals = [];
|
|
1060
|
+
for (const name of externalNames) {
|
|
1061
|
+
const el = topElementsByName.get(name);
|
|
1062
|
+
if (el) externals.push(el);
|
|
1063
|
+
}
|
|
1064
|
+
const elementToGroup = /* @__PURE__ */ new Map();
|
|
1065
|
+
for (const group of targetContainer.groups) {
|
|
1066
|
+
for (const child of group.children) {
|
|
1067
|
+
if (child.type === "component") {
|
|
1068
|
+
elementToGroup.set(child.name, {
|
|
1069
|
+
name: group.name,
|
|
1070
|
+
lineNumber: group.lineNumber
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
const hasGroups = elementToGroup.size > 0;
|
|
1076
|
+
const g = hasGroups ? new dagre.graphlib.Graph({ compound: true }) : new dagre.graphlib.Graph();
|
|
1077
|
+
g.setDefaultEdgeLabel(() => ({}));
|
|
1078
|
+
if (hasGroups) {
|
|
1079
|
+
const seenGroups = /* @__PURE__ */ new Set();
|
|
1080
|
+
for (const { name } of elementToGroup.values()) {
|
|
1081
|
+
if (!seenGroups.has(name)) {
|
|
1082
|
+
seenGroups.add(name);
|
|
1083
|
+
g.setNode("__group_" + name, {});
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
const nameToElement = /* @__PURE__ */ new Map();
|
|
1088
|
+
for (const el of components) {
|
|
1089
|
+
nameToElement.set(el.name, el);
|
|
1090
|
+
const dims = computeC4NodeDimensions(el, { showTechnology: true });
|
|
1091
|
+
g.setNode(el.name, { width: dims.width, height: dims.height });
|
|
1092
|
+
const grp = elementToGroup.get(el.name);
|
|
1093
|
+
if (grp) {
|
|
1094
|
+
g.setParent(el.name, "__group_" + grp.name);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
for (const el of externals) {
|
|
1098
|
+
nameToElement.set(el.name, el);
|
|
1099
|
+
const dims = computeC4NodeDimensions(el);
|
|
1100
|
+
g.setNode(el.name, { width: dims.width, height: dims.height });
|
|
1101
|
+
}
|
|
1102
|
+
const componentRels = [];
|
|
1103
|
+
const seenEdgeKeys = /* @__PURE__ */ new Set();
|
|
1104
|
+
for (const component of components) {
|
|
1105
|
+
for (const rel of component.relationships) {
|
|
1106
|
+
const targetEl = nameToElement.get(rel.target);
|
|
1107
|
+
if (!targetEl) continue;
|
|
1108
|
+
const key = `${component.name}\u2192${rel.target}`;
|
|
1109
|
+
if (!seenEdgeKeys.has(key)) {
|
|
1110
|
+
seenEdgeKeys.add(key);
|
|
1111
|
+
componentRels.push({
|
|
1112
|
+
sourceName: component.name,
|
|
1113
|
+
targetName: rel.target,
|
|
1114
|
+
...rel.label !== void 0 && { label: rel.label },
|
|
1115
|
+
...rel.technology !== void 0 && { technology: rel.technology },
|
|
1116
|
+
arrowType: rel.arrowType,
|
|
1117
|
+
lineNumber: rel.lineNumber
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
for (const { sourceName, rel } of allRels) {
|
|
1123
|
+
if (componentNames.has(sourceName.toLowerCase())) continue;
|
|
1124
|
+
if (!componentNames.has(rel.target.toLowerCase())) continue;
|
|
1125
|
+
const sourceAncestor = ownerMap.get(sourceName) ?? sourceName;
|
|
1126
|
+
if (sourceAncestor.toLowerCase() === targetContainer.name.toLowerCase())
|
|
1127
|
+
continue;
|
|
1128
|
+
let resolvedSource;
|
|
1129
|
+
if (nameToElement.has(sourceName)) {
|
|
1130
|
+
resolvedSource = sourceName;
|
|
1131
|
+
} else if (nameToElement.has(sourceAncestor)) {
|
|
1132
|
+
resolvedSource = sourceAncestor;
|
|
1133
|
+
}
|
|
1134
|
+
if (!resolvedSource) continue;
|
|
1135
|
+
const key = `${resolvedSource}\u2192${rel.target}`;
|
|
1136
|
+
if (!seenEdgeKeys.has(key)) {
|
|
1137
|
+
seenEdgeKeys.add(key);
|
|
1138
|
+
componentRels.push({
|
|
1139
|
+
sourceName: resolvedSource,
|
|
1140
|
+
targetName: rel.target,
|
|
1141
|
+
...rel.label !== void 0 && { label: rel.label },
|
|
1142
|
+
...rel.technology !== void 0 && { technology: rel.technology },
|
|
1143
|
+
arrowType: rel.arrowType,
|
|
1144
|
+
lineNumber: rel.lineNumber
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
const spacing = computeAdaptiveSpacing(componentRels);
|
|
1149
|
+
g.setGraph({
|
|
1150
|
+
rankdir: "TB",
|
|
1151
|
+
nodesep: spacing.nodesep,
|
|
1152
|
+
ranksep: spacing.ranksep,
|
|
1153
|
+
edgesep: spacing.edgesep
|
|
1154
|
+
});
|
|
1155
|
+
for (const rel of componentRels) {
|
|
1156
|
+
if (nameToElement.has(rel.sourceName) && nameToElement.has(rel.targetName)) {
|
|
1157
|
+
g.setEdge(rel.sourceName, rel.targetName, { label: rel.label ?? "" });
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
dagre.layout(g);
|
|
1161
|
+
const nodeGroupMap = hasGroups ? new Map([...elementToGroup.entries()].map(([k, v]) => [k, v.name])) : void 0;
|
|
1162
|
+
reduceCrossings(
|
|
1163
|
+
g,
|
|
1164
|
+
componentRels.filter(
|
|
1165
|
+
(r) => nameToElement.has(r.sourceName) && nameToElement.has(r.targetName)
|
|
1166
|
+
).map((r) => ({ source: r.sourceName, target: r.targetName })),
|
|
1167
|
+
nodeGroupMap
|
|
1168
|
+
);
|
|
1169
|
+
const ancestors = [targetContainer, system];
|
|
1170
|
+
const nodes = [];
|
|
1171
|
+
for (const el of components) {
|
|
1172
|
+
const pos = g.node(el.name);
|
|
1173
|
+
const color = resolveNodeColor(
|
|
1174
|
+
el,
|
|
1175
|
+
parsed.tagGroups,
|
|
1176
|
+
activeTagGroup ?? null,
|
|
1177
|
+
ancestors
|
|
1178
|
+
);
|
|
1179
|
+
const tech = el.metadata["tech"] ?? el.metadata["technology"];
|
|
1180
|
+
const hasComponents = el.children.some((c) => c.type === "component") || el.groups.some((grp) => grp.children.some((c) => c.type === "component"));
|
|
1181
|
+
const description = el.description?.join("\n");
|
|
1182
|
+
const drillable = hasComponents || el.importPath ? true : void 0;
|
|
1183
|
+
nodes.push({
|
|
1184
|
+
id: el.name,
|
|
1185
|
+
name: el.name,
|
|
1186
|
+
type: "component",
|
|
1187
|
+
...description !== void 0 && { description },
|
|
1188
|
+
metadata: el.metadata,
|
|
1189
|
+
lineNumber: el.lineNumber,
|
|
1190
|
+
...color !== void 0 && { color },
|
|
1191
|
+
shape: el.shape,
|
|
1192
|
+
...tech !== void 0 && { technology: tech },
|
|
1193
|
+
...drillable !== void 0 && { drillable },
|
|
1194
|
+
...el.importPath !== void 0 && { importPath: el.importPath },
|
|
1195
|
+
x: pos.x,
|
|
1196
|
+
y: pos.y,
|
|
1197
|
+
width: pos.width,
|
|
1198
|
+
height: pos.height
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
for (const el of externals) {
|
|
1202
|
+
const pos = g.node(el.name);
|
|
1203
|
+
const color = resolveNodeColor(
|
|
1204
|
+
el,
|
|
1205
|
+
parsed.tagGroups,
|
|
1206
|
+
activeTagGroup ?? null
|
|
1207
|
+
);
|
|
1208
|
+
const description = el.description?.join("\n");
|
|
1209
|
+
nodes.push({
|
|
1210
|
+
id: el.name,
|
|
1211
|
+
name: el.name,
|
|
1212
|
+
type: el.type,
|
|
1213
|
+
...description !== void 0 && { description },
|
|
1214
|
+
metadata: el.metadata,
|
|
1215
|
+
lineNumber: el.lineNumber,
|
|
1216
|
+
...color !== void 0 && { color },
|
|
1217
|
+
x: pos.x,
|
|
1218
|
+
y: pos.y,
|
|
1219
|
+
width: pos.width,
|
|
1220
|
+
height: pos.height
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
const edges = componentRels.filter(
|
|
1224
|
+
(rel) => nameToElement.has(rel.sourceName) && nameToElement.has(rel.targetName)
|
|
1225
|
+
).map((rel) => {
|
|
1226
|
+
const edgeData = g.edge(rel.sourceName, rel.targetName);
|
|
1227
|
+
return {
|
|
1228
|
+
source: rel.sourceName,
|
|
1229
|
+
target: rel.targetName,
|
|
1230
|
+
arrowType: rel.arrowType,
|
|
1231
|
+
...rel.label !== void 0 && { label: rel.label },
|
|
1232
|
+
...rel.technology !== void 0 && { technology: rel.technology },
|
|
1233
|
+
lineNumber: rel.lineNumber,
|
|
1234
|
+
points: edgeData?.points ?? []
|
|
1235
|
+
};
|
|
1236
|
+
});
|
|
1237
|
+
const componentNodes = nodes.filter((n) => n.type === "component");
|
|
1238
|
+
let bMinX = Infinity, bMinY = Infinity, bMaxX = -Infinity, bMaxY = -Infinity;
|
|
1239
|
+
for (const n of componentNodes) {
|
|
1240
|
+
const left = n.x - n.width / 2;
|
|
1241
|
+
const top = n.y - n.height / 2;
|
|
1242
|
+
const right = n.x + n.width / 2;
|
|
1243
|
+
const bottom = n.y + n.height / 2;
|
|
1244
|
+
if (left < bMinX) bMinX = left;
|
|
1245
|
+
if (top < bMinY) bMinY = top;
|
|
1246
|
+
if (right > bMaxX) bMaxX = right;
|
|
1247
|
+
if (bottom > bMaxY) bMaxY = bottom;
|
|
1248
|
+
}
|
|
1249
|
+
const boundary = {
|
|
1250
|
+
label: targetContainer.name,
|
|
1251
|
+
typeLabel: "container",
|
|
1252
|
+
lineNumber: targetContainer.lineNumber,
|
|
1253
|
+
x: bMinX - BOUNDARY_PAD,
|
|
1254
|
+
y: bMinY - BOUNDARY_PAD,
|
|
1255
|
+
width: bMaxX - bMinX + BOUNDARY_PAD * 2,
|
|
1256
|
+
height: bMaxY - bMinY + BOUNDARY_PAD * 2
|
|
1257
|
+
};
|
|
1258
|
+
const groupBoundaries = [];
|
|
1259
|
+
if (hasGroups) {
|
|
1260
|
+
const nodeMap = new Map(componentNodes.map((n) => [n.name, n]));
|
|
1261
|
+
const seenGroups = /* @__PURE__ */ new Map();
|
|
1262
|
+
for (const [elName, grp] of elementToGroup) {
|
|
1263
|
+
const node = nodeMap.get(elName);
|
|
1264
|
+
if (!node) continue;
|
|
1265
|
+
if (!seenGroups.has(grp.name)) {
|
|
1266
|
+
seenGroups.set(grp.name, { lineNumber: grp.lineNumber, members: [] });
|
|
1267
|
+
}
|
|
1268
|
+
seenGroups.get(grp.name).members.push(node);
|
|
1269
|
+
}
|
|
1270
|
+
for (const [groupName, { lineNumber, members }] of seenGroups) {
|
|
1271
|
+
if (members.length === 0) continue;
|
|
1272
|
+
let gMinX = Infinity, gMinY = Infinity, gMaxX = -Infinity, gMaxY = -Infinity;
|
|
1273
|
+
for (const m of members) {
|
|
1274
|
+
const left = m.x - m.width / 2;
|
|
1275
|
+
const top = m.y - m.height / 2;
|
|
1276
|
+
const right = m.x + m.width / 2;
|
|
1277
|
+
const bottom = m.y + m.height / 2;
|
|
1278
|
+
if (left < gMinX) gMinX = left;
|
|
1279
|
+
if (top < gMinY) gMinY = top;
|
|
1280
|
+
if (right > gMaxX) gMaxX = right;
|
|
1281
|
+
if (bottom > gMaxY) gMaxY = bottom;
|
|
1282
|
+
}
|
|
1283
|
+
groupBoundaries.push({
|
|
1284
|
+
label: groupName,
|
|
1285
|
+
typeLabel: "group",
|
|
1286
|
+
lineNumber,
|
|
1287
|
+
x: gMinX - GROUP_BOUNDARY_PAD,
|
|
1288
|
+
y: gMinY - GROUP_BOUNDARY_PAD,
|
|
1289
|
+
width: gMaxX - gMinX + GROUP_BOUNDARY_PAD * 2,
|
|
1290
|
+
height: gMaxY - gMinY + GROUP_BOUNDARY_PAD * 2
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
1295
|
+
for (const node of nodes) {
|
|
1296
|
+
const left = node.x - node.width / 2;
|
|
1297
|
+
const top = node.y - node.height / 2;
|
|
1298
|
+
const right = node.x + node.width / 2;
|
|
1299
|
+
const bottom = node.y + node.height / 2;
|
|
1300
|
+
if (left < minX) minX = left;
|
|
1301
|
+
if (top < minY) minY = top;
|
|
1302
|
+
if (right > maxX) maxX = right;
|
|
1303
|
+
if (bottom > maxY) maxY = bottom;
|
|
1304
|
+
}
|
|
1305
|
+
if (boundary.x < minX) minX = boundary.x;
|
|
1306
|
+
if (boundary.y < minY) minY = boundary.y;
|
|
1307
|
+
if (boundary.x + boundary.width > maxX) maxX = boundary.x + boundary.width;
|
|
1308
|
+
if (boundary.y + boundary.height > maxY) maxY = boundary.y + boundary.height;
|
|
1309
|
+
for (const gb of groupBoundaries) {
|
|
1310
|
+
if (gb.x < minX) minX = gb.x;
|
|
1311
|
+
if (gb.y < minY) minY = gb.y;
|
|
1312
|
+
if (gb.x + gb.width > maxX) maxX = gb.x + gb.width;
|
|
1313
|
+
if (gb.y + gb.height > maxY) maxY = gb.y + gb.height;
|
|
1314
|
+
}
|
|
1315
|
+
for (const edge of edges) {
|
|
1316
|
+
for (const pt of edge.points) {
|
|
1317
|
+
if (pt.x < minX) minX = pt.x;
|
|
1318
|
+
if (pt.y < minY) minY = pt.y;
|
|
1319
|
+
if (pt.x > maxX) maxX = pt.x;
|
|
1320
|
+
if (pt.y > maxY) maxY = pt.y;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
const shiftX = MARGIN - minX;
|
|
1324
|
+
const shiftY = MARGIN - minY;
|
|
1325
|
+
for (const node of nodes) {
|
|
1326
|
+
node.x += shiftX;
|
|
1327
|
+
node.y += shiftY;
|
|
1328
|
+
}
|
|
1329
|
+
boundary.x += shiftX;
|
|
1330
|
+
boundary.y += shiftY;
|
|
1331
|
+
for (const gb of groupBoundaries) {
|
|
1332
|
+
gb.x += shiftX;
|
|
1333
|
+
gb.y += shiftY;
|
|
1334
|
+
}
|
|
1335
|
+
for (const edge of edges) {
|
|
1336
|
+
for (const pt of edge.points) {
|
|
1337
|
+
pt.x += shiftX;
|
|
1338
|
+
pt.y += shiftY;
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
let totalWidth = maxX - minX + MARGIN * 2;
|
|
1342
|
+
let totalHeight = maxY - minY + MARGIN * 2;
|
|
1343
|
+
const legendGroups = computeLegendGroups(parsed.tagGroups);
|
|
1344
|
+
if (legendGroups.length > 0) {
|
|
1345
|
+
const legendY = totalHeight + MARGIN;
|
|
1346
|
+
let legendX = MARGIN;
|
|
1347
|
+
for (const lg of legendGroups) {
|
|
1348
|
+
lg.x = legendX;
|
|
1349
|
+
lg.y = legendY;
|
|
1350
|
+
legendX += lg.width + 12;
|
|
1351
|
+
}
|
|
1352
|
+
const legendRight = legendX;
|
|
1353
|
+
const legendBottom = legendY + LEGEND_HEIGHT;
|
|
1354
|
+
if (legendRight > totalWidth) totalWidth = legendRight;
|
|
1355
|
+
if (legendBottom > totalHeight) totalHeight = legendBottom;
|
|
1356
|
+
}
|
|
1357
|
+
return {
|
|
1358
|
+
nodes,
|
|
1359
|
+
edges,
|
|
1360
|
+
legend: legendGroups,
|
|
1361
|
+
boundary,
|
|
1362
|
+
groupBoundaries,
|
|
1363
|
+
width: totalWidth,
|
|
1364
|
+
height: totalHeight
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
function resolveContainerRef(parsed, refName) {
|
|
1368
|
+
const lower = refName.toLowerCase();
|
|
1369
|
+
for (const el of parsed.elements) {
|
|
1370
|
+
for (const child of el.children) {
|
|
1371
|
+
if (child.type === "container" && child.name.toLowerCase() === lower)
|
|
1372
|
+
return child;
|
|
1373
|
+
}
|
|
1374
|
+
for (const group of el.groups) {
|
|
1375
|
+
for (const child of group.children) {
|
|
1376
|
+
if (child.type === "container" && child.name.toLowerCase() === lower)
|
|
1377
|
+
return child;
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
return void 0;
|
|
1382
|
+
}
|
|
1383
|
+
function collectDeploymentRefs(nodes, parsed, parentId, refs, infraIds, infraParents) {
|
|
1384
|
+
for (const node of nodes) {
|
|
1385
|
+
const infraId = `__infra_${node.name}`;
|
|
1386
|
+
infraIds.set(infraId, node);
|
|
1387
|
+
infraParents.set(infraId, parentId);
|
|
1388
|
+
for (const ref of node.containerRefs) {
|
|
1389
|
+
const el = resolveContainerRef(parsed, ref);
|
|
1390
|
+
if (el) {
|
|
1391
|
+
refs.push({
|
|
1392
|
+
refName: ref,
|
|
1393
|
+
element: el,
|
|
1394
|
+
infraId,
|
|
1395
|
+
deployLineNumber: node.lineNumber
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
collectDeploymentRefs(
|
|
1400
|
+
node.children,
|
|
1401
|
+
parsed,
|
|
1402
|
+
infraId,
|
|
1403
|
+
refs,
|
|
1404
|
+
infraIds,
|
|
1405
|
+
infraParents
|
|
1406
|
+
);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
function layoutC4Deployment(parsed, activeTagGroup) {
|
|
1410
|
+
if (parsed.deployment.length === 0) {
|
|
1411
|
+
return {
|
|
1412
|
+
nodes: [],
|
|
1413
|
+
edges: [],
|
|
1414
|
+
legend: [],
|
|
1415
|
+
groupBoundaries: [],
|
|
1416
|
+
width: 0,
|
|
1417
|
+
height: 0
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
const refs = [];
|
|
1421
|
+
const infraIds = /* @__PURE__ */ new Map();
|
|
1422
|
+
const infraParents = /* @__PURE__ */ new Map();
|
|
1423
|
+
collectDeploymentRefs(
|
|
1424
|
+
parsed.deployment,
|
|
1425
|
+
parsed,
|
|
1426
|
+
null,
|
|
1427
|
+
refs,
|
|
1428
|
+
infraIds,
|
|
1429
|
+
infraParents
|
|
1430
|
+
);
|
|
1431
|
+
if (refs.length === 0) {
|
|
1432
|
+
return {
|
|
1433
|
+
nodes: [],
|
|
1434
|
+
edges: [],
|
|
1435
|
+
legend: [],
|
|
1436
|
+
groupBoundaries: [],
|
|
1437
|
+
width: 0,
|
|
1438
|
+
height: 0
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
const seenRefs = /* @__PURE__ */ new Map();
|
|
1442
|
+
for (const ref of refs) {
|
|
1443
|
+
const key = ref.element.name.toLowerCase();
|
|
1444
|
+
if (!seenRefs.has(key)) seenRefs.set(key, ref);
|
|
1445
|
+
}
|
|
1446
|
+
const refEntries = [...seenRefs.values()];
|
|
1447
|
+
const refNames = new Set(refEntries.map((r) => r.element.name.toLowerCase()));
|
|
1448
|
+
const nameToElement = /* @__PURE__ */ new Map();
|
|
1449
|
+
for (const r of refEntries) {
|
|
1450
|
+
nameToElement.set(r.element.name, r.element);
|
|
1451
|
+
}
|
|
1452
|
+
const g = new dagre.graphlib.Graph({ compound: true });
|
|
1453
|
+
g.setDefaultEdgeLabel(() => ({}));
|
|
1454
|
+
for (const [infraId] of infraIds) {
|
|
1455
|
+
g.setNode(infraId, {});
|
|
1456
|
+
const parentId = infraParents.get(infraId);
|
|
1457
|
+
if (parentId) g.setParent(infraId, parentId);
|
|
1458
|
+
}
|
|
1459
|
+
for (const r of refEntries) {
|
|
1460
|
+
const dims = computeC4NodeDimensions(r.element, { showTechnology: true });
|
|
1461
|
+
g.setNode(r.element.name, { width: dims.width, height: dims.height });
|
|
1462
|
+
g.setParent(r.element.name, r.infraId);
|
|
1463
|
+
}
|
|
1464
|
+
const deployRels = [];
|
|
1465
|
+
const seenEdgeKeys = /* @__PURE__ */ new Set();
|
|
1466
|
+
for (const r of refEntries) {
|
|
1467
|
+
for (const rel of r.element.relationships) {
|
|
1468
|
+
if (refNames.has(rel.target.toLowerCase())) {
|
|
1469
|
+
const key = `${r.element.name}\u2192${rel.target}`;
|
|
1470
|
+
if (!seenEdgeKeys.has(key)) {
|
|
1471
|
+
seenEdgeKeys.add(key);
|
|
1472
|
+
deployRels.push({
|
|
1473
|
+
sourceName: r.element.name,
|
|
1474
|
+
targetName: rel.target,
|
|
1475
|
+
...rel.label !== void 0 && { label: rel.label },
|
|
1476
|
+
...rel.technology !== void 0 && { technology: rel.technology },
|
|
1477
|
+
arrowType: rel.arrowType,
|
|
1478
|
+
lineNumber: rel.lineNumber
|
|
1479
|
+
});
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
const spacing = computeAdaptiveSpacing(deployRels);
|
|
1485
|
+
g.setGraph({
|
|
1486
|
+
rankdir: "TB",
|
|
1487
|
+
nodesep: spacing.nodesep,
|
|
1488
|
+
ranksep: spacing.ranksep,
|
|
1489
|
+
edgesep: spacing.edgesep
|
|
1490
|
+
});
|
|
1491
|
+
for (const rel of deployRels) {
|
|
1492
|
+
if (nameToElement.has(rel.sourceName) && nameToElement.has(rel.targetName)) {
|
|
1493
|
+
g.setEdge(rel.sourceName, rel.targetName, { label: rel.label ?? "" });
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
dagre.layout(g);
|
|
1497
|
+
const nodeInfraMap = /* @__PURE__ */ new Map();
|
|
1498
|
+
for (const r of refEntries) nodeInfraMap.set(r.element.name, r.infraId);
|
|
1499
|
+
reduceCrossings(
|
|
1500
|
+
g,
|
|
1501
|
+
deployRels.filter(
|
|
1502
|
+
(r) => nameToElement.has(r.sourceName) && nameToElement.has(r.targetName)
|
|
1503
|
+
).map((r) => ({ source: r.sourceName, target: r.targetName })),
|
|
1504
|
+
nodeInfraMap
|
|
1505
|
+
);
|
|
1506
|
+
const nodes = [];
|
|
1507
|
+
for (const r of refEntries) {
|
|
1508
|
+
const pos = g.node(r.element.name);
|
|
1509
|
+
const color = resolveNodeColor(
|
|
1510
|
+
r.element,
|
|
1511
|
+
parsed.tagGroups,
|
|
1512
|
+
activeTagGroup ?? null
|
|
1513
|
+
);
|
|
1514
|
+
const tech = r.element.metadata["tech"] ?? r.element.metadata["technology"];
|
|
1515
|
+
const description = r.element.description?.join("\n");
|
|
1516
|
+
nodes.push({
|
|
1517
|
+
id: r.element.name,
|
|
1518
|
+
name: r.element.name,
|
|
1519
|
+
type: "container",
|
|
1520
|
+
...description !== void 0 && { description },
|
|
1521
|
+
metadata: r.element.metadata,
|
|
1522
|
+
lineNumber: r.element.lineNumber,
|
|
1523
|
+
...color !== void 0 && { color },
|
|
1524
|
+
shape: r.element.shape,
|
|
1525
|
+
...tech !== void 0 && { technology: tech },
|
|
1526
|
+
x: pos.x,
|
|
1527
|
+
y: pos.y,
|
|
1528
|
+
width: pos.width,
|
|
1529
|
+
height: pos.height
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
const edges = deployRels.filter(
|
|
1533
|
+
(rel) => nameToElement.has(rel.sourceName) && nameToElement.has(rel.targetName)
|
|
1534
|
+
).map((rel) => {
|
|
1535
|
+
const edgeData = g.edge(rel.sourceName, rel.targetName);
|
|
1536
|
+
return {
|
|
1537
|
+
source: rel.sourceName,
|
|
1538
|
+
target: rel.targetName,
|
|
1539
|
+
arrowType: rel.arrowType,
|
|
1540
|
+
...rel.label !== void 0 && { label: rel.label },
|
|
1541
|
+
...rel.technology !== void 0 && { technology: rel.technology },
|
|
1542
|
+
lineNumber: rel.lineNumber,
|
|
1543
|
+
points: edgeData?.points ?? []
|
|
1544
|
+
};
|
|
1545
|
+
});
|
|
1546
|
+
const groupBoundaries = [];
|
|
1547
|
+
const nodeMap = new Map(nodes.map((n) => [n.name, n]));
|
|
1548
|
+
const infraMembers = /* @__PURE__ */ new Map();
|
|
1549
|
+
for (const r of refEntries) {
|
|
1550
|
+
const members = infraMembers.get(r.infraId) ?? [];
|
|
1551
|
+
const node = nodeMap.get(r.element.name);
|
|
1552
|
+
if (node) members.push(node);
|
|
1553
|
+
infraMembers.set(r.infraId, members);
|
|
1554
|
+
}
|
|
1555
|
+
const infraBounds = /* @__PURE__ */ new Map();
|
|
1556
|
+
function computeInfraBounds(infraId) {
|
|
1557
|
+
if (infraBounds.has(infraId)) return infraBounds.get(infraId);
|
|
1558
|
+
let bMinX = Infinity, bMinY = Infinity, bMaxX = -Infinity, bMaxY = -Infinity;
|
|
1559
|
+
let hasContent = false;
|
|
1560
|
+
const members = infraMembers.get(infraId) ?? [];
|
|
1561
|
+
for (const m of members) {
|
|
1562
|
+
hasContent = true;
|
|
1563
|
+
const left = m.x - m.width / 2;
|
|
1564
|
+
const top = m.y - m.height / 2;
|
|
1565
|
+
const right = m.x + m.width / 2;
|
|
1566
|
+
const bottom = m.y + m.height / 2;
|
|
1567
|
+
if (left < bMinX) bMinX = left;
|
|
1568
|
+
if (top < bMinY) bMinY = top;
|
|
1569
|
+
if (right > bMaxX) bMaxX = right;
|
|
1570
|
+
if (bottom > bMaxY) bMaxY = bottom;
|
|
1571
|
+
}
|
|
1572
|
+
for (const [childId, parentId] of infraParents) {
|
|
1573
|
+
if (parentId === infraId) {
|
|
1574
|
+
const childBounds = computeInfraBounds(childId);
|
|
1575
|
+
if (childBounds) {
|
|
1576
|
+
hasContent = true;
|
|
1577
|
+
if (childBounds.x < bMinX) bMinX = childBounds.x;
|
|
1578
|
+
if (childBounds.y < bMinY) bMinY = childBounds.y;
|
|
1579
|
+
if (childBounds.x + childBounds.width > bMaxX)
|
|
1580
|
+
bMaxX = childBounds.x + childBounds.width;
|
|
1581
|
+
if (childBounds.y + childBounds.height > bMaxY)
|
|
1582
|
+
bMaxY = childBounds.y + childBounds.height;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
if (!hasContent) return null;
|
|
1587
|
+
const bounds = {
|
|
1588
|
+
x: bMinX - BOUNDARY_PAD,
|
|
1589
|
+
y: bMinY - BOUNDARY_PAD,
|
|
1590
|
+
width: bMaxX - bMinX + BOUNDARY_PAD * 2,
|
|
1591
|
+
height: bMaxY - bMinY + BOUNDARY_PAD * 2
|
|
1592
|
+
};
|
|
1593
|
+
infraBounds.set(infraId, bounds);
|
|
1594
|
+
return bounds;
|
|
1595
|
+
}
|
|
1596
|
+
for (const [infraId, node] of infraIds) {
|
|
1597
|
+
const bounds = computeInfraBounds(infraId);
|
|
1598
|
+
if (bounds) {
|
|
1599
|
+
const shapeLabel = node.shape !== "default" ? node.shape : "node";
|
|
1600
|
+
groupBoundaries.push({
|
|
1601
|
+
label: node.name,
|
|
1602
|
+
typeLabel: shapeLabel,
|
|
1603
|
+
lineNumber: node.lineNumber,
|
|
1604
|
+
...bounds
|
|
1605
|
+
});
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
groupBoundaries.sort((a, b) => b.width * b.height - a.width * a.height);
|
|
1609
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
1610
|
+
for (const node of nodes) {
|
|
1611
|
+
const left = node.x - node.width / 2;
|
|
1612
|
+
const top = node.y - node.height / 2;
|
|
1613
|
+
const right = node.x + node.width / 2;
|
|
1614
|
+
const bottom = node.y + node.height / 2;
|
|
1615
|
+
if (left < minX) minX = left;
|
|
1616
|
+
if (top < minY) minY = top;
|
|
1617
|
+
if (right > maxX) maxX = right;
|
|
1618
|
+
if (bottom > maxY) maxY = bottom;
|
|
1619
|
+
}
|
|
1620
|
+
for (const gb of groupBoundaries) {
|
|
1621
|
+
if (gb.x < minX) minX = gb.x;
|
|
1622
|
+
if (gb.y < minY) minY = gb.y;
|
|
1623
|
+
if (gb.x + gb.width > maxX) maxX = gb.x + gb.width;
|
|
1624
|
+
if (gb.y + gb.height > maxY) maxY = gb.y + gb.height;
|
|
1625
|
+
}
|
|
1626
|
+
for (const edge of edges) {
|
|
1627
|
+
for (const pt of edge.points) {
|
|
1628
|
+
if (pt.x < minX) minX = pt.x;
|
|
1629
|
+
if (pt.y < minY) minY = pt.y;
|
|
1630
|
+
if (pt.x > maxX) maxX = pt.x;
|
|
1631
|
+
if (pt.y > maxY) maxY = pt.y;
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
const shiftX = MARGIN - minX;
|
|
1635
|
+
const shiftY = MARGIN - minY;
|
|
1636
|
+
for (const node of nodes) {
|
|
1637
|
+
node.x += shiftX;
|
|
1638
|
+
node.y += shiftY;
|
|
1639
|
+
}
|
|
1640
|
+
for (const gb of groupBoundaries) {
|
|
1641
|
+
gb.x += shiftX;
|
|
1642
|
+
gb.y += shiftY;
|
|
1643
|
+
}
|
|
1644
|
+
for (const edge of edges) {
|
|
1645
|
+
for (const pt of edge.points) {
|
|
1646
|
+
pt.x += shiftX;
|
|
1647
|
+
pt.y += shiftY;
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
let totalWidth = maxX - minX + MARGIN * 2;
|
|
1651
|
+
let totalHeight = maxY - minY + MARGIN * 2;
|
|
1652
|
+
const legendGroups = computeLegendGroups(parsed.tagGroups);
|
|
1653
|
+
if (legendGroups.length > 0) {
|
|
1654
|
+
const legendY = totalHeight + MARGIN;
|
|
1655
|
+
let legendX = MARGIN;
|
|
1656
|
+
for (const lg of legendGroups) {
|
|
1657
|
+
lg.x = legendX;
|
|
1658
|
+
lg.y = legendY;
|
|
1659
|
+
legendX += lg.width + 12;
|
|
1660
|
+
}
|
|
1661
|
+
const legendRight = legendX;
|
|
1662
|
+
const legendBottom = legendY + LEGEND_HEIGHT;
|
|
1663
|
+
if (legendRight > totalWidth) totalWidth = legendRight;
|
|
1664
|
+
if (legendBottom > totalHeight) totalHeight = legendBottom;
|
|
1665
|
+
}
|
|
1666
|
+
return {
|
|
1667
|
+
nodes,
|
|
1668
|
+
edges,
|
|
1669
|
+
legend: legendGroups,
|
|
1670
|
+
groupBoundaries,
|
|
1671
|
+
width: totalWidth,
|
|
1672
|
+
height: totalHeight
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
export {
|
|
1677
|
+
rollUpContextRelationships,
|
|
1678
|
+
collectCardMetadata,
|
|
1679
|
+
computeC4NodeDimensions,
|
|
1680
|
+
layoutC4Context,
|
|
1681
|
+
layoutC4Containers,
|
|
1682
|
+
layoutC4Components,
|
|
1683
|
+
layoutC4Deployment
|
|
1684
|
+
};
|
|
1685
|
+
//# sourceMappingURL=chunk-QEBUY5NY.js.map
|