@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.
Files changed (232) hide show
  1. package/dist/advanced.d.cts +3 -855
  2. package/dist/advanced.d.ts +3 -855
  3. package/dist/advanced.js +541 -70107
  4. package/dist/analyzer-UYKBXAKR.js +15 -0
  5. package/dist/auto.cjs +1 -1
  6. package/dist/auto.js +1 -1
  7. package/dist/auto.mjs +1 -1
  8. package/dist/block.js +83 -69417
  9. package/dist/calculator-LDYQCDDY.js +11 -0
  10. package/dist/charts-d3-QOZHKTCT.js +25 -0
  11. package/dist/chunk-2DXMZRWJ.js +800 -0
  12. package/dist/chunk-2LACINWN.js +786 -0
  13. package/dist/chunk-33LCUH4H.js +893 -0
  14. package/dist/chunk-3CIOJ5YJ.js +35 -0
  15. package/dist/chunk-3HDBAXF5.js +86 -0
  16. package/dist/chunk-3JLKYGBQ.js +290 -0
  17. package/dist/chunk-3MQQNXPD.js +415 -0
  18. package/dist/chunk-3R5POCTZ.js +967 -0
  19. package/dist/chunk-3SMGIJDD.js +112 -0
  20. package/dist/chunk-44CM72SQ.js +409 -0
  21. package/dist/chunk-4GRI7MEB.js +1604 -0
  22. package/dist/chunk-4OHACBGX.js +606 -0
  23. package/dist/chunk-5DH3KYFH.js +21 -0
  24. package/dist/chunk-5MLYVTYM.js +76 -0
  25. package/dist/chunk-5QXL4NYS.js +553 -0
  26. package/dist/chunk-5WRI5ZAA.js +31 -0
  27. package/dist/chunk-6EK55ART.js +117 -0
  28. package/dist/chunk-6FZYCKXB.js +2065 -0
  29. package/dist/chunk-6HO5ZTXK.js +277 -0
  30. package/dist/chunk-6KFWC4CO.js +7 -0
  31. package/dist/chunk-6MTWIQVO.js +121 -0
  32. package/dist/chunk-6NYWYNBI.js +329 -0
  33. package/dist/chunk-6SM5NJQH.js +620 -0
  34. package/dist/chunk-725LDASD.js +422 -0
  35. package/dist/chunk-76OWZ3BA.js +130 -0
  36. package/dist/chunk-7JEGTRLD.js +338 -0
  37. package/dist/chunk-A44USUV7.js +923 -0
  38. package/dist/chunk-A4GOY3PE.js +25 -0
  39. package/dist/chunk-A4VFQH5G.js +251 -0
  40. package/dist/chunk-AELOBFW5.js +375 -0
  41. package/dist/chunk-AN2MOO5T.js +522 -0
  42. package/dist/chunk-BMR4CIEO.js +41 -0
  43. package/dist/chunk-BS7SV7C3.js +51 -0
  44. package/dist/chunk-BUPDIHML.js +613 -0
  45. package/dist/chunk-BUVJFRPY.js +887 -0
  46. package/dist/chunk-C3Z4DX3V.js +112 -0
  47. package/dist/chunk-CI2G3OEB.js +1818 -0
  48. package/dist/chunk-CKNVO24D.js +54 -0
  49. package/dist/chunk-CUVHQHZ7.js +391 -0
  50. package/dist/chunk-CXK4DZH3.js +114 -0
  51. package/dist/chunk-DKEPS5LH.js +315 -0
  52. package/dist/chunk-DU7W62N5.js +487 -0
  53. package/dist/chunk-DZAQBW35.js +751 -0
  54. package/dist/chunk-EB6FPQUP.js +316 -0
  55. package/dist/chunk-EMS2SK26.js +376 -0
  56. package/dist/chunk-ERLBPDGU.js +510 -0
  57. package/dist/chunk-EVCVDTXE.js +732 -0
  58. package/dist/chunk-FCIIHT24.js +384 -0
  59. package/dist/chunk-FFUN5D66.js +97 -0
  60. package/dist/chunk-FITOLXSX.js +714 -0
  61. package/dist/chunk-FMDSF3OV.js +68 -0
  62. package/dist/chunk-FNB3PNGF.js +410 -0
  63. package/dist/chunk-G2H74XJZ.js +2104 -0
  64. package/dist/chunk-GMR5MFHJ.js +339 -0
  65. package/dist/chunk-H6PI3KJT.js +305 -0
  66. package/dist/chunk-I46X5VUA.js +59 -0
  67. package/dist/chunk-I5IGB56C.js +255 -0
  68. package/dist/chunk-IH6ZMVQ5.js +3950 -0
  69. package/dist/chunk-IJZFN3HM.js +463 -0
  70. package/dist/chunk-IPWPWM6R.js +3079 -0
  71. package/dist/chunk-IYULMRBE.js +23 -0
  72. package/dist/chunk-IZMRON6C.js +167 -0
  73. package/dist/chunk-J5FIXQRT.js +813 -0
  74. package/dist/chunk-JED6ZDEE.js +73 -0
  75. package/dist/chunk-JEUJOX5X.js +136 -0
  76. package/dist/chunk-JM4LEPY3.js +436 -0
  77. package/dist/chunk-L3UHP6TD.js +101 -0
  78. package/dist/chunk-L66GEFB6.js +524 -0
  79. package/dist/chunk-LIOHW36Z.js +469 -0
  80. package/dist/chunk-LP7EC4MI.js +16 -0
  81. package/dist/chunk-MITI6NOL.js +548 -0
  82. package/dist/chunk-MO5YHHMQ.js +168 -0
  83. package/dist/chunk-MZSBFAPJ.js +885 -0
  84. package/dist/chunk-NNAAJVBU.js +9 -0
  85. package/dist/chunk-NOTA6TEI.js +47 -0
  86. package/dist/chunk-NURGB5Z4.js +335 -0
  87. package/dist/chunk-NX4EQS3U.js +1062 -0
  88. package/dist/chunk-NXMESY4J.js +1 -0
  89. package/dist/chunk-OJR3MQYV.js +1458 -0
  90. package/dist/chunk-OURHMODR.js +454 -0
  91. package/dist/chunk-P235SVAH.js +1057 -0
  92. package/dist/chunk-P4RH4OSP.js +1038 -0
  93. package/dist/chunk-P6T66CCZ.js +226 -0
  94. package/dist/chunk-PBAHEPLQ.js +223 -0
  95. package/dist/chunk-PBHE5UG6.js +1530 -0
  96. package/dist/chunk-PDKUANDZ.js +35 -0
  97. package/dist/chunk-PWIHZ6NJ.js +296 -0
  98. package/dist/chunk-QEBUY5NY.js +1685 -0
  99. package/dist/chunk-QLCCJEJI.js +253 -0
  100. package/dist/chunk-QOOUC2J5.js +52 -0
  101. package/dist/chunk-QYTPPDAO.js +402 -0
  102. package/dist/chunk-R3XI2VC7.js +801 -0
  103. package/dist/chunk-RCERWOJR.js +154 -0
  104. package/dist/chunk-RJE5JTVK.js +362 -0
  105. package/dist/chunk-RTLBSQ3G.js +1155 -0
  106. package/dist/chunk-RUNNI6VE.js +448 -0
  107. package/dist/chunk-RVMRZAPB.js +354 -0
  108. package/dist/chunk-RYEGFMUO.js +955 -0
  109. package/dist/chunk-RZCF2VVM.js +528 -0
  110. package/dist/chunk-SML67VPV.js +1025 -0
  111. package/dist/chunk-SQ7IY53Q.js +44 -0
  112. package/dist/chunk-SQAG3UXH.js +161 -0
  113. package/dist/chunk-SRTU6HMQ.js +1851 -0
  114. package/dist/chunk-T2OE3YQB.js +550 -0
  115. package/dist/chunk-T3UBLACL.js +299 -0
  116. package/dist/chunk-TLVIHFRO.js +296 -0
  117. package/dist/chunk-TYS6CSS5.js +52 -0
  118. package/dist/chunk-U6EQI3U6.js +130 -0
  119. package/dist/chunk-UJ4FJLBF.js +86 -0
  120. package/dist/chunk-ULOEATBZ.js +347 -0
  121. package/dist/chunk-UNCCZ3NU.js +127 -0
  122. package/dist/chunk-USZW2HHU.js +197 -0
  123. package/dist/chunk-UWN33VNX.js +1610 -0
  124. package/dist/chunk-UZ7EZW3B.js +40 -0
  125. package/dist/chunk-VOKGH627.js +37 -0
  126. package/dist/chunk-WBRZJOEC.js +204 -0
  127. package/dist/chunk-WCSOJFU3.js +380 -0
  128. package/dist/chunk-WCT3NYCV.js +160 -0
  129. package/dist/chunk-WVAJFAIA.js +529 -0
  130. package/dist/chunk-X5LCYCL5.js +394 -0
  131. package/dist/chunk-XAKEUMEJ.js +1256 -0
  132. package/dist/chunk-Y2ZE3MSG.js +590 -0
  133. package/dist/chunk-Y3J7VR52.js +646 -0
  134. package/dist/chunk-Y5QGVTVC.js +33 -0
  135. package/dist/chunk-YAA6IID3.js +548 -0
  136. package/dist/chunk-YGJTDXVR.js +692 -0
  137. package/dist/chunk-YRYKTYMQ.js +186 -0
  138. package/dist/chunk-YYVTBZZC.js +61 -0
  139. package/dist/chunk-Z5CGSFJB.js +647 -0
  140. package/dist/chunk-Z72XRXD4.js +787 -0
  141. package/dist/chunk-ZGR7ULFJ.js +978 -0
  142. package/dist/chunk-ZJEL4H2R.js +851 -0
  143. package/dist/chunk-ZO27FGJS.js +554 -0
  144. package/dist/collapse-CLPJD2RL.js +11 -0
  145. package/dist/collapse-SLINJQW6.js +9 -0
  146. package/dist/collapse-ZWUFFCK2.js +9 -0
  147. package/dist/compute-G55RG53N.js +8 -0
  148. package/dist/dgmo-router-DZCIDUO5.js +73 -0
  149. package/dist/dimensions-J3FHIMJ6.js +22 -0
  150. package/dist/element.cjs +1 -1
  151. package/dist/element.js +1 -1
  152. package/dist/element.mjs +1 -1
  153. package/dist/flowchart-parser-ZNSKIBVR.js +21 -0
  154. package/dist/flowchart-renderer-ZAEOL4DX.js +35 -0
  155. package/dist/index.cjs +3 -3
  156. package/dist/index.d.cts +3 -469
  157. package/dist/index.d.ts +3 -469
  158. package/dist/index.js +137 -69685
  159. package/dist/layout-46N4JJYK.js +17 -0
  160. package/dist/layout-5RA6QWR6.js +26 -0
  161. package/dist/layout-5T6RH5M6.js +14 -0
  162. package/dist/layout-7QXCNOVS.js +13 -0
  163. package/dist/layout-EE6GE46Y.js +17 -0
  164. package/dist/layout-G23F7LDH.js +9 -0
  165. package/dist/layout-HRBZPFCN.js +23 -0
  166. package/dist/layout-HWDSRX5E.js +20 -0
  167. package/dist/layout-HYZPWAK5.js +18 -0
  168. package/dist/layout-N76NR4ZO.js +19 -0
  169. package/dist/layout-OB3RI63S.js +14 -0
  170. package/dist/layout-S2QBYSK2.js +15 -0
  171. package/dist/layout-UM5F4DJB.js +15 -0
  172. package/dist/layout-search-OYV77HHS.js +1655 -0
  173. package/dist/load-data-EHDDZ5ZS.js +8 -0
  174. package/dist/palettes-PRJ2RI6D.js +56 -0
  175. package/dist/parser-2MFP2H2E.js +11 -0
  176. package/dist/parser-3STE62GB.js +21 -0
  177. package/dist/parser-4FL2CMDG.js +18 -0
  178. package/dist/parser-4OGOBJJ4.js +13 -0
  179. package/dist/parser-6BTRLKRZ.js +15 -0
  180. package/dist/parser-AQETEVRW.js +21 -0
  181. package/dist/parser-CFZD5IBL.js +12 -0
  182. package/dist/parser-FUSKFGZN.js +16 -0
  183. package/dist/parser-I6KXEYFK.js +13 -0
  184. package/dist/parser-IXSZZQHY.js +18 -0
  185. package/dist/parser-JAL2HD4I.js +13 -0
  186. package/dist/parser-KNSQSKQO.js +20 -0
  187. package/dist/parser-N5O4IHUN.js +13 -0
  188. package/dist/parser-NGU3ETO6.js +16 -0
  189. package/dist/parser-NRAI6O25.js +19 -0
  190. package/dist/parser-O5J4VBII.js +11 -0
  191. package/dist/parser-OV7NOBW6.js +21 -0
  192. package/dist/parser-PQ3POX45.js +23 -0
  193. package/dist/parser-TFZFBVWN.js +11 -0
  194. package/dist/parser-TRTHDFPP.js +18 -0
  195. package/dist/parser-VQP2UJ6W.js +11 -0
  196. package/dist/parser-XDVELHRK.js +13 -0
  197. package/dist/parser-XKRP7QKP.js +11 -0
  198. package/dist/parser-Y6WTBU76.js +14 -0
  199. package/dist/parser-ZSDPZYQP.js +12 -0
  200. package/dist/renderer-2XQQQ6QR.js +28 -0
  201. package/dist/renderer-5NDVNDAZ.js +26 -0
  202. package/dist/renderer-6EEZHMH4.js +26 -0
  203. package/dist/renderer-6W5462RB.js +43 -0
  204. package/dist/renderer-7MVLWWQD.js +22 -0
  205. package/dist/renderer-AWWAF6XD.js +33 -0
  206. package/dist/renderer-BYCNMBF7.js +34 -0
  207. package/dist/renderer-CLJHQPBJ.js +29 -0
  208. package/dist/renderer-GHPZMFU2.js +15 -0
  209. package/dist/renderer-JGBKUCZD.js +16 -0
  210. package/dist/renderer-KQB7UGDS.js +19 -0
  211. package/dist/renderer-PGX2TKVS.js +24 -0
  212. package/dist/renderer-QIWVHWZO.js +15 -0
  213. package/dist/renderer-RPOA5YPZ.js +35 -0
  214. package/dist/renderer-SMIBCRVB.js +24 -0
  215. package/dist/renderer-T7YKC5VP.js +28 -0
  216. package/dist/renderer-TOCOVLHS.js +37 -0
  217. package/dist/renderer-UWA5XS74.js +39 -0
  218. package/dist/renderer-VACVQYML.js +25 -0
  219. package/dist/renderer-W4M6QLIJ.js +37 -0
  220. package/dist/renderer-WZRREQ2P.js +23 -0
  221. package/dist/renderer-YY2OSF6E.js +27 -0
  222. package/dist/renderer-Z7I3W2PP.js +24 -0
  223. package/dist/renderer-ZGNTUWC4.js +22 -0
  224. package/dist/renderer-ZTR3DER3.js +19 -0
  225. package/dist/renderer-radial-C32GPJQ7.js +21 -0
  226. package/dist/resolver-NOAA7ID7.js +13 -0
  227. package/dist/state-collapse-HCGD6IVJ.js +8 -0
  228. package/dist/state-parser-Z2V23ATR.js +18 -0
  229. package/dist/state-renderer-RFYHSP4K.js +34 -0
  230. package/dist/themes-1-CuKpeH.d.cts +908 -0
  231. package/dist/themes-1-CuKpeH.d.ts +908 -0
  232. package/package.json +1 -1
@@ -0,0 +1,1038 @@
1
+ import {
2
+ collectCardMetadata,
3
+ layoutC4Components,
4
+ layoutC4Containers,
5
+ layoutC4Context,
6
+ layoutC4Deployment
7
+ } from "./chunk-QEBUY5NY.js";
8
+ import {
9
+ renderInlineText
10
+ } from "./chunk-3HDBAXF5.js";
11
+ import {
12
+ CARD_RADIUS,
13
+ EDGE_STROKE_WIDTH,
14
+ META_FONT_SIZE,
15
+ META_LINE_HEIGHT,
16
+ NODE_STROKE_WIDTH
17
+ } from "./chunk-3CIOJ5YJ.js";
18
+ import {
19
+ renderIntegratedLegend
20
+ } from "./chunk-PWIHZ6NJ.js";
21
+ import {
22
+ TITLE_FONT_SIZE,
23
+ TITLE_FONT_WEIGHT
24
+ } from "./chunk-A4GOY3PE.js";
25
+ import {
26
+ FONT_FAMILY
27
+ } from "./chunk-6KFWC4CO.js";
28
+ import {
29
+ LEGEND_HEIGHT
30
+ } from "./chunk-QOOUC2J5.js";
31
+ import {
32
+ measureText,
33
+ wrapTextToWidth
34
+ } from "./chunk-WCT3NYCV.js";
35
+ import {
36
+ parseC4
37
+ } from "./chunk-2DXMZRWJ.js";
38
+ import {
39
+ preprocessDescriptionLine
40
+ } from "./chunk-IYULMRBE.js";
41
+ import {
42
+ contrastText,
43
+ mix,
44
+ shapeFill
45
+ } from "./chunk-P6T66CCZ.js";
46
+ import {
47
+ tagAttrKey
48
+ } from "./chunk-QLCCJEJI.js";
49
+
50
+ // src/c4/renderer.ts
51
+ import * as d3Selection from "d3-selection";
52
+ import * as d3Shape from "d3-shape";
53
+ var DIAGRAM_PADDING = 20;
54
+ var MAX_SCALE = 3;
55
+ var TITLE_HEIGHT = 30;
56
+ var TYPE_FONT_SIZE = 10;
57
+ var NAME_FONT_SIZE = 14;
58
+ var DESC_FONT_SIZE = 11;
59
+ var DESC_LINE_HEIGHT = 16;
60
+ var EDGE_LABEL_FONT_SIZE = 11;
61
+ var TECH_FONT_SIZE = 10;
62
+ var CARD_H_PAD = 20;
63
+ var CARD_V_PAD = 14;
64
+ var TYPE_LABEL_HEIGHT = 18;
65
+ var DIVIDER_GAP = 6;
66
+ var NAME_HEIGHT = 20;
67
+ var BOUNDARY_LABEL_FONT_SIZE = 12;
68
+ var BOUNDARY_STROKE_WIDTH = 1.5;
69
+ var BOUNDARY_RADIUS = 8;
70
+ var DRILL_BAR_HEIGHT = 6;
71
+ var CYLINDER_RY = 8;
72
+ var PERSON_HEAD_R = 4;
73
+ var PERSON_ARM_SPAN = 10;
74
+ var PERSON_LEG_SPAN = 7;
75
+ var PERSON_ICON_W = PERSON_ARM_SPAN * 2;
76
+ var PERSON_SW = 1.5;
77
+ function typeColor(type, palette, nodeColor) {
78
+ if (nodeColor) return nodeColor;
79
+ switch (type) {
80
+ case "person":
81
+ return palette.colors.blue;
82
+ case "container":
83
+ return palette.colors.purple;
84
+ case "component":
85
+ return palette.colors.green;
86
+ default:
87
+ return palette.colors.teal;
88
+ }
89
+ }
90
+ function nodeFill(palette, isDark, type, nodeColor, solid) {
91
+ const color = typeColor(type, palette, nodeColor);
92
+ return shapeFill(palette, color, isDark, {
93
+ ...solid !== void 0 && { solid }
94
+ });
95
+ }
96
+ function nodeStroke(palette, type, nodeColor) {
97
+ return typeColor(type, palette, nodeColor);
98
+ }
99
+ var lineGenerator = d3Shape.line().x((d) => d.x).y((d) => d.y).curve(d3Shape.curveBasis);
100
+ function isDashedEdge(arrowType) {
101
+ return arrowType === "async" || arrowType === "bidirectional-async";
102
+ }
103
+ function hasBidirectionalMarkers(arrowType) {
104
+ return arrowType === "bidirectional" || arrowType === "bidirectional-async";
105
+ }
106
+ function drawPersonIcon(g, cx, cy, color) {
107
+ const headY = cy - 7;
108
+ const bodyTopY = headY + PERSON_HEAD_R + 1;
109
+ const bodyBottomY = cy + 4;
110
+ const legY = cy + 10;
111
+ g.append("circle").attr("cx", cx).attr("cy", headY).attr("r", PERSON_HEAD_R).attr("fill", "none").attr("stroke", color).attr("stroke-width", PERSON_SW);
112
+ g.append("line").attr("x1", cx).attr("y1", bodyTopY).attr("x2", cx).attr("y2", bodyBottomY).attr("stroke", color).attr("stroke-width", PERSON_SW);
113
+ g.append("line").attr("x1", cx - PERSON_ARM_SPAN).attr("y1", bodyTopY + 3).attr("x2", cx + PERSON_ARM_SPAN).attr("y2", bodyTopY + 3).attr("stroke", color).attr("stroke-width", PERSON_SW);
114
+ g.append("line").attr("x1", cx).attr("y1", bodyBottomY).attr("x2", cx - PERSON_LEG_SPAN).attr("y2", legY).attr("stroke", color).attr("stroke-width", PERSON_SW);
115
+ g.append("line").attr("x1", cx).attr("y1", bodyBottomY).attr("x2", cx + PERSON_LEG_SPAN).attr("y2", legY).attr("stroke", color).attr("stroke-width", PERSON_SW);
116
+ }
117
+ function renderC4Context(container, parsed, layout, palette, isDark, onClickItem, exportDims, activeTagGroup, exportMode) {
118
+ d3Selection.select(container).selectAll(":not([data-d3-tooltip])").remove();
119
+ const width = exportDims?.width ?? container.clientWidth;
120
+ const height = exportDims?.height ?? container.clientHeight;
121
+ if (width <= 0 || height <= 0) return;
122
+ const titleHeight = parsed.title && parsed.options["no-title"] !== "on" ? TITLE_HEIGHT + 10 : 0;
123
+ const diagramW = layout.width;
124
+ const hasLegend = layout.legend.length > 0;
125
+ const C4_LAYOUT_MARGIN = 40;
126
+ const LEGEND_FIXED_GAP = 8;
127
+ const fixedLegend = !exportDims && hasLegend;
128
+ const legendLayoutSpace = C4_LAYOUT_MARGIN + LEGEND_HEIGHT;
129
+ const legendReserveH = fixedLegend ? LEGEND_HEIGHT + LEGEND_FIXED_GAP : 0;
130
+ const diagramH = fixedLegend ? layout.height - legendLayoutSpace : layout.height;
131
+ const availH = height - titleHeight - legendReserveH;
132
+ const scaleX = (width - DIAGRAM_PADDING * 2) / diagramW;
133
+ const scaleY = (availH - DIAGRAM_PADDING * 2) / diagramH;
134
+ const scale = Math.min(MAX_SCALE, scaleX, scaleY);
135
+ const scaledW = diagramW * scale;
136
+ const offsetX = (width - scaledW) / 2;
137
+ const offsetY = titleHeight + DIAGRAM_PADDING + legendReserveH;
138
+ const svg = d3Selection.select(container).append("svg").attr("width", width).attr("height", height).attr("viewBox", `0 0 ${width} ${height}`).attr("preserveAspectRatio", "xMidYMid meet").style("font-family", FONT_FAMILY);
139
+ const defs = svg.append("defs");
140
+ const AW = 10;
141
+ const AH = 7;
142
+ defs.append("marker").attr("id", "c4-arrow-end").attr("viewBox", `0 0 ${AW} ${AH}`).attr("refX", AW).attr("refY", AH / 2).attr("markerWidth", AW).attr("markerHeight", AH).attr("orient", "auto").append("polygon").attr("points", `0,0 ${AW},${AH / 2} 0,${AH}`).attr("fill", palette.textMuted);
143
+ defs.append("marker").attr("id", "c4-arrow-start").attr("viewBox", `0 0 ${AW} ${AH}`).attr("refX", 0).attr("refY", AH / 2).attr("markerWidth", AW).attr("markerHeight", AH).attr("orient", "auto").append("polygon").attr("points", `${AW},0 0,${AH / 2} ${AW},${AH}`).attr("fill", palette.textMuted);
144
+ if (parsed.title && parsed.options["no-title"] !== "on") {
145
+ const titleEl = svg.append("text").attr("class", "chart-title").attr("x", width / 2).attr("y", 30).attr("text-anchor", "middle").attr("fill", palette.text).attr("font-size", TITLE_FONT_SIZE).attr("font-weight", TITLE_FONT_WEIGHT).style(
146
+ "cursor",
147
+ onClickItem && parsed.titleLineNumber ? "pointer" : "default"
148
+ ).text(parsed.title);
149
+ if (parsed.titleLineNumber) {
150
+ titleEl.attr("data-line-number", parsed.titleLineNumber);
151
+ if (onClickItem) {
152
+ titleEl.on("click", () => onClickItem(parsed.titleLineNumber)).on("mouseenter", function() {
153
+ d3Selection.select(this).attr("opacity", 0.7);
154
+ }).on("mouseleave", function() {
155
+ d3Selection.select(this).attr("opacity", 1);
156
+ });
157
+ }
158
+ }
159
+ }
160
+ const contentG = svg.append("g").attr("transform", `translate(${offsetX}, ${offsetY}) scale(${scale})`);
161
+ for (const edge of layout.edges) {
162
+ if (edge.points.length < 2) continue;
163
+ const edgeG = contentG.append("g").attr("class", "c4-edge-group").attr("data-line-number", String(edge.lineNumber)).attr("data-source", edge.source).attr("data-target", edge.target);
164
+ if (onClickItem) {
165
+ edgeG.style("cursor", "pointer").on("click", () => {
166
+ onClickItem(edge.lineNumber);
167
+ });
168
+ }
169
+ const edgeColor = palette.textMuted;
170
+ const dashed = isDashedEdge(edge.arrowType);
171
+ const bidir = hasBidirectionalMarkers(edge.arrowType);
172
+ const pathD = lineGenerator(edge.points);
173
+ if (pathD) {
174
+ const pathEl = edgeG.append("path").attr("d", pathD).attr("fill", "none").attr("stroke", edgeColor).attr("stroke-width", EDGE_STROKE_WIDTH).attr("class", "c4-edge").attr("marker-end", "url(#c4-arrow-end)");
175
+ if (dashed) {
176
+ pathEl.attr("stroke-dasharray", "6 3");
177
+ }
178
+ if (bidir) {
179
+ pathEl.attr("marker-start", "url(#c4-arrow-start)");
180
+ }
181
+ }
182
+ if (edge.label || edge.technology) {
183
+ const midIdx = Math.floor(edge.points.length / 2);
184
+ const midPt = edge.points[midIdx];
185
+ const labelText = edge.label ?? "";
186
+ const techText = edge.technology ? `[${edge.technology}]` : "";
187
+ const textW = Math.max(
188
+ measureText(labelText, EDGE_LABEL_FONT_SIZE),
189
+ measureText(techText, TECH_FONT_SIZE)
190
+ );
191
+ const bgW = textW + 12;
192
+ const bgH = (labelText ? 16 : 0) + (techText ? 14 : 0) + 4;
193
+ edgeG.append("rect").attr("x", midPt.x - bgW / 2).attr("y", midPt.y - bgH / 2).attr("width", bgW).attr("height", bgH).attr("rx", 3).attr("fill", palette.bg).attr("opacity", 0.9).attr("class", "c4-edge-label-bg");
194
+ let textY = midPt.y;
195
+ if (labelText && techText) {
196
+ textY = midPt.y - 4;
197
+ }
198
+ if (labelText) {
199
+ edgeG.append("text").attr("x", midPt.x).attr("y", textY + 4).attr("text-anchor", "middle").attr("fill", edgeColor).attr("font-size", EDGE_LABEL_FONT_SIZE).attr("class", "c4-edge-label").text(labelText);
200
+ }
201
+ if (techText) {
202
+ edgeG.append("text").attr("x", midPt.x).attr("y", labelText ? textY + 18 : textY + 4).attr("text-anchor", "middle").attr("fill", edgeColor).attr("font-size", TECH_FONT_SIZE).attr("font-style", "italic").attr("class", "c4-edge-tech").text(techText);
203
+ }
204
+ }
205
+ }
206
+ for (const node of layout.nodes) {
207
+ const nodeG = contentG.append("g").attr("transform", `translate(${node.x}, ${node.y})`).attr("class", "c4-card").attr("data-line-number", String(node.lineNumber)).attr("data-node-id", node.id);
208
+ if (activeTagGroup) {
209
+ const tagKey = tagAttrKey(activeTagGroup);
210
+ const tagValue = node.metadata[tagKey];
211
+ if (tagValue) {
212
+ nodeG.attr(`data-tag-${tagKey}`, tagValue.toLowerCase());
213
+ } else {
214
+ const tagGroup = parsed.tagGroups.find(
215
+ (g) => g.name.toLowerCase() === tagKey || g.alias?.toLowerCase() === tagKey
216
+ );
217
+ if (tagGroup?.defaultValue) {
218
+ nodeG.attr(`data-tag-${tagKey}`, tagGroup.defaultValue.toLowerCase());
219
+ }
220
+ }
221
+ }
222
+ if (node.importPath) {
223
+ nodeG.attr("data-import-path", node.importPath);
224
+ }
225
+ if (onClickItem) {
226
+ nodeG.style("cursor", "pointer").on("click", () => {
227
+ onClickItem(node.lineNumber);
228
+ });
229
+ }
230
+ const w = node.width;
231
+ const h = node.height;
232
+ const solid = parsed.options["solid-fill"] === "on";
233
+ const fill = nodeFill(palette, isDark, node.type, node.color, solid);
234
+ const stroke = nodeStroke(palette, node.type, node.color);
235
+ const onFillText = contrastText(
236
+ fill,
237
+ palette.textOnFillLight,
238
+ palette.textOnFillDark
239
+ );
240
+ nodeG.append("rect").attr("x", -w / 2).attr("y", -h / 2).attr("width", w).attr("height", h).attr("rx", CARD_RADIUS).attr("ry", CARD_RADIUS).attr("fill", fill).attr("stroke", stroke).attr("stroke-width", NODE_STROKE_WIDTH);
241
+ let yPos = -h / 2 + CARD_V_PAD;
242
+ const typeLabel = `\xAB${node.type}\xBB`;
243
+ nodeG.append("text").attr("x", 0).attr("y", yPos + TYPE_FONT_SIZE / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("fill", onFillText).attr("font-size", TYPE_FONT_SIZE).attr("font-style", "italic").text(typeLabel);
244
+ yPos += TYPE_LABEL_HEIGHT;
245
+ if (node.type === "person") {
246
+ const textWidth = measureText(node.name, NAME_FONT_SIZE);
247
+ const gap = 6;
248
+ const totalWidth = PERSON_ICON_W + gap + textWidth;
249
+ const iconCx = -totalWidth / 2 + PERSON_ICON_W / 2;
250
+ const textX = iconCx + PERSON_ICON_W / 2 + gap;
251
+ drawPersonIcon(
252
+ nodeG,
253
+ iconCx,
254
+ yPos + NAME_FONT_SIZE / 2 - 2,
255
+ stroke
256
+ );
257
+ nodeG.append("text").attr("x", textX).attr("y", yPos + NAME_FONT_SIZE / 2).attr("text-anchor", "start").attr("dominant-baseline", "central").attr("fill", onFillText).attr("font-size", NAME_FONT_SIZE).attr("font-weight", "bold").text(node.name);
258
+ } else {
259
+ nodeG.append("text").attr("x", 0).attr("y", yPos + NAME_FONT_SIZE / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("fill", onFillText).attr("font-size", NAME_FONT_SIZE).attr("font-weight", "bold").text(node.name);
260
+ }
261
+ yPos += NAME_HEIGHT;
262
+ nodeG.append("line").attr("x1", -w / 2 + CARD_H_PAD / 2).attr("y1", yPos).attr("x2", w / 2 - CARD_H_PAD / 2).attr("y2", yPos).attr("stroke", solid ? onFillText : stroke).attr("stroke-width", 0.5).attr("stroke-opacity", 0.4);
263
+ yPos += DIVIDER_GAP;
264
+ if (node.description) {
265
+ const contentWidth = w - CARD_H_PAD * 2;
266
+ const lines = wrapTextToWidth(
267
+ node.description,
268
+ DESC_FONT_SIZE,
269
+ contentWidth
270
+ );
271
+ for (const line2 of lines) {
272
+ const textEl = nodeG.append("text").attr("x", 0).attr("y", yPos + DESC_FONT_SIZE / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("fill", onFillText).attr("font-size", DESC_FONT_SIZE);
273
+ renderInlineText(
274
+ textEl,
275
+ preprocessDescriptionLine(line2),
276
+ palette,
277
+ DESC_FONT_SIZE
278
+ );
279
+ yPos += DESC_LINE_HEIGHT;
280
+ }
281
+ }
282
+ if (node.drillable) {
283
+ const clipId = `clip-drill-${node.id.replace(/\s+/g, "-")}`;
284
+ nodeG.append("clipPath").attr("id", clipId).append("rect").attr("x", -w / 2).attr("y", -h / 2).attr("width", w).attr("height", h).attr("rx", CARD_RADIUS);
285
+ nodeG.append("rect").attr("x", -w / 2).attr("y", h / 2 - DRILL_BAR_HEIGHT).attr("width", w).attr("height", DRILL_BAR_HEIGHT).attr("fill", solid ? onFillText : stroke).attr("clip-path", `url(#${clipId})`).attr("class", "c4-drill-bar");
286
+ }
287
+ }
288
+ if (hasLegend) {
289
+ const legendParent = fixedLegend ? svg.append("g").attr("class", "c4-legend-fixed").attr("transform", `translate(0, ${DIAGRAM_PADDING + titleHeight})`) : contentG.append("g").attr("class", "c4-legend");
290
+ if (activeTagGroup) {
291
+ legendParent.attr("data-legend-active", tagAttrKey(activeTagGroup));
292
+ }
293
+ renderLegend(
294
+ legendParent,
295
+ layout,
296
+ palette,
297
+ isDark,
298
+ activeTagGroup,
299
+ fixedLegend ? width : null,
300
+ exportMode
301
+ );
302
+ }
303
+ }
304
+ function renderC4ContextForExport(content, theme, palette) {
305
+ const parsed = parseC4(content, palette);
306
+ if (parsed.error || parsed.elements.length === 0) return "";
307
+ const layout = layoutC4Context(parsed);
308
+ const isDark = theme === "dark";
309
+ const container = document.createElement("div");
310
+ const titleOffset = parsed.title && parsed.options["no-title"] !== "on" ? TITLE_HEIGHT + 10 : 0;
311
+ const exportWidth = layout.width + DIAGRAM_PADDING * 2;
312
+ const exportHeight = layout.height + DIAGRAM_PADDING * 2 + titleOffset;
313
+ container.style.width = `${exportWidth}px`;
314
+ container.style.height = `${exportHeight}px`;
315
+ container.style.position = "absolute";
316
+ container.style.left = "-9999px";
317
+ document.body.appendChild(container);
318
+ try {
319
+ renderC4Context(container, parsed, layout, palette, isDark, void 0, {
320
+ width: exportWidth,
321
+ height: exportHeight
322
+ });
323
+ const svgEl = container.querySelector("svg");
324
+ if (!svgEl) return "";
325
+ if (theme === "transparent") {
326
+ svgEl.style.background = "none";
327
+ }
328
+ svgEl.setAttribute("xmlns", "http://www.w3.org/2000/svg");
329
+ svgEl.style.fontFamily = FONT_FAMILY;
330
+ return svgEl.outerHTML;
331
+ } finally {
332
+ document.body.removeChild(container);
333
+ }
334
+ }
335
+ function drawCylinderCard(nodeG, w, h, fill, stroke, dashed) {
336
+ const ry = CYLINDER_RY;
337
+ const path = [
338
+ `M ${-w / 2} ${-h / 2 + ry}`,
339
+ `A ${w / 2} ${ry} 0 0 1 ${w / 2} ${-h / 2 + ry}`,
340
+ `L ${w / 2} ${h / 2 - ry}`,
341
+ `A ${w / 2} ${ry} 0 0 1 ${-w / 2} ${h / 2 - ry}`,
342
+ "Z"
343
+ ].join(" ");
344
+ const el = nodeG.append("path").attr("d", path).attr("fill", fill).attr("stroke", stroke).attr("stroke-width", NODE_STROKE_WIDTH);
345
+ if (dashed) {
346
+ el.attr("stroke-dasharray", "6 3");
347
+ }
348
+ nodeG.append("ellipse").attr("cx", 0).attr("cy", -h / 2 + ry).attr("rx", w / 2).attr("ry", ry).attr("fill", fill).attr("stroke", stroke).attr("stroke-width", NODE_STROKE_WIDTH);
349
+ }
350
+ function drawCardRect(nodeG, w, h, fill, stroke, dashed) {
351
+ const el = nodeG.append("rect").attr("x", -w / 2).attr("y", -h / 2).attr("width", w).attr("height", h).attr("rx", CARD_RADIUS).attr("ry", CARD_RADIUS).attr("fill", fill).attr("stroke", stroke).attr("stroke-width", NODE_STROKE_WIDTH);
352
+ if (dashed) {
353
+ el.attr("stroke-dasharray", "6 3");
354
+ }
355
+ }
356
+ function renderEdges(contentG, edges, palette, onClickItem, obstacleRects) {
357
+ const pendingLabels = [];
358
+ for (const edge of edges) {
359
+ if (edge.points.length < 2) continue;
360
+ const edgeG = contentG.append("g").attr("class", "c4-edge-group").attr("data-line-number", String(edge.lineNumber)).attr("data-source", edge.source).attr("data-target", edge.target);
361
+ if (onClickItem) {
362
+ edgeG.style("cursor", "pointer").on("click", () => {
363
+ onClickItem(edge.lineNumber);
364
+ });
365
+ }
366
+ const edgeColor = palette.textMuted;
367
+ const dashed = isDashedEdge(edge.arrowType);
368
+ const bidir = hasBidirectionalMarkers(edge.arrowType);
369
+ const pathD = lineGenerator(edge.points);
370
+ if (pathD) {
371
+ const pathEl = edgeG.append("path").attr("d", pathD).attr("fill", "none").attr("stroke", edgeColor).attr("stroke-width", EDGE_STROKE_WIDTH).attr("class", "c4-edge").attr("marker-end", "url(#c4-arrow-end)");
372
+ if (dashed) {
373
+ pathEl.attr("stroke-dasharray", "6 3");
374
+ }
375
+ if (bidir) {
376
+ pathEl.attr("marker-start", "url(#c4-arrow-start)");
377
+ }
378
+ }
379
+ if (edge.label || edge.technology) {
380
+ const labelText = edge.label ?? "";
381
+ const techText = edge.technology ? `[${edge.technology}]` : "";
382
+ const textW = Math.max(
383
+ measureText(labelText, EDGE_LABEL_FONT_SIZE),
384
+ measureText(techText, TECH_FONT_SIZE)
385
+ );
386
+ const bgW = textW + 12;
387
+ const bgH = (labelText ? 16 : 0) + (techText ? 14 : 0) + 4;
388
+ pendingLabels.push({
389
+ edgeG,
390
+ labelText,
391
+ techText,
392
+ bgW,
393
+ bgH,
394
+ x: 0,
395
+ y: 0,
396
+ edgeColor,
397
+ edgeIdx: edges.indexOf(edge)
398
+ });
399
+ }
400
+ }
401
+ placeEdgeLabels(pendingLabels, edges, obstacleRects);
402
+ for (const lbl of pendingLabels) {
403
+ lbl.edgeG.append("rect").attr("x", lbl.x - lbl.bgW / 2).attr("y", lbl.y - lbl.bgH / 2).attr("width", lbl.bgW).attr("height", lbl.bgH).attr("rx", 3).attr("fill", palette.bg).attr("opacity", 0.9).attr("class", "c4-edge-label-bg");
404
+ let textY = lbl.y;
405
+ if (lbl.labelText && lbl.techText) {
406
+ textY = lbl.y - 4;
407
+ }
408
+ if (lbl.labelText) {
409
+ lbl.edgeG.append("text").attr("x", lbl.x).attr("y", textY + 4).attr("text-anchor", "middle").attr("fill", lbl.edgeColor).attr("font-size", EDGE_LABEL_FONT_SIZE).attr("class", "c4-edge-label").text(lbl.labelText);
410
+ }
411
+ if (lbl.techText) {
412
+ lbl.edgeG.append("text").attr("x", lbl.x).attr("y", lbl.labelText ? textY + 18 : textY + 4).attr("text-anchor", "middle").attr("fill", lbl.edgeColor).attr("font-size", TECH_FONT_SIZE).attr("font-style", "italic").attr("class", "c4-edge-tech").text(lbl.techText);
413
+ }
414
+ }
415
+ }
416
+ function interpolateAlongPath(points, t) {
417
+ if (points.length < 2) return points[0];
418
+ let totalLen = 0;
419
+ for (let i = 1; i < points.length; i++) {
420
+ const dx = points[i].x - points[i - 1].x;
421
+ const dy = points[i].y - points[i - 1].y;
422
+ totalLen += Math.sqrt(dx * dx + dy * dy);
423
+ }
424
+ const targetLen = t * totalLen;
425
+ let accumulated = 0;
426
+ for (let i = 1; i < points.length; i++) {
427
+ const dx = points[i].x - points[i - 1].x;
428
+ const dy = points[i].y - points[i - 1].y;
429
+ const segLen = Math.sqrt(dx * dx + dy * dy);
430
+ if (accumulated + segLen >= targetLen) {
431
+ const segT = segLen > 0 ? (targetLen - accumulated) / segLen : 0;
432
+ return {
433
+ x: points[i - 1].x + dx * segT,
434
+ y: points[i - 1].y + dy * segT
435
+ };
436
+ }
437
+ accumulated += segLen;
438
+ }
439
+ return points[points.length - 1];
440
+ }
441
+ function pointToSegmentDist(p, a, b) {
442
+ const dx = b.x - a.x;
443
+ const dy = b.y - a.y;
444
+ const lenSq = dx * dx + dy * dy;
445
+ if (lenSq === 0) {
446
+ const ex2 = p.x - a.x;
447
+ const ey2 = p.y - a.y;
448
+ return Math.sqrt(ex2 * ex2 + ey2 * ey2);
449
+ }
450
+ let t = ((p.x - a.x) * dx + (p.y - a.y) * dy) / lenSq;
451
+ t = Math.max(0, Math.min(1, t));
452
+ const projX = a.x + t * dx;
453
+ const projY = a.y + t * dy;
454
+ const ex = p.x - projX;
455
+ const ey = p.y - projY;
456
+ return Math.sqrt(ex * ex + ey * ey);
457
+ }
458
+ function pointToPolylineDist(p, points) {
459
+ let minDist = Infinity;
460
+ for (let i = 1; i < points.length; i++) {
461
+ const d = pointToSegmentDist(p, points[i - 1], points[i]);
462
+ if (d < minDist) minDist = d;
463
+ }
464
+ return minDist;
465
+ }
466
+ function rectsOverlap(ax, ay, aw, ah, bx, by, bw, bh, pad) {
467
+ return !(ax + aw / 2 + pad < bx - bw / 2 - pad || bx + bw / 2 + pad < ax - aw / 2 - pad || ay + ah / 2 + pad < by - bh / 2 - pad || by + bh / 2 + pad < ay - ah / 2 - pad);
468
+ }
469
+ function tangentAt(points, t) {
470
+ if (points.length < 2) return { x: 0, y: 1 };
471
+ let totalLen = 0;
472
+ for (let i = 1; i < points.length; i++) {
473
+ const dx = points[i].x - points[i - 1].x;
474
+ const dy = points[i].y - points[i - 1].y;
475
+ totalLen += Math.sqrt(dx * dx + dy * dy);
476
+ }
477
+ const targetLen = t * totalLen;
478
+ let accumulated = 0;
479
+ for (let i = 1; i < points.length; i++) {
480
+ const dx = points[i].x - points[i - 1].x;
481
+ const dy = points[i].y - points[i - 1].y;
482
+ const segLen = Math.sqrt(dx * dx + dy * dy);
483
+ if (accumulated + segLen >= targetLen || i === points.length - 1) {
484
+ return { x: dx, y: dy };
485
+ }
486
+ accumulated += segLen;
487
+ }
488
+ return { x: 0, y: 1 };
489
+ }
490
+ function placeEdgeLabels(labels, edges, obstacleRects) {
491
+ if (labels.length === 0) return;
492
+ const allPaths = edges.map((e) => e.points);
493
+ const placedRects = [];
494
+ const SAMPLES = [0.4, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9];
495
+ const candidates = labels.map((lbl) => {
496
+ const ownPath = allPaths[lbl.edgeIdx];
497
+ return SAMPLES.map((t) => ({ pt: interpolateAlongPath(ownPath, t), t }));
498
+ });
499
+ const order = labels.map((_, i) => i);
500
+ order.sort((a, b) => {
501
+ const midA = interpolateAlongPath(allPaths[labels[a].edgeIdx], 0.5);
502
+ const midB = interpolateAlongPath(allPaths[labels[b].edgeIdx], 0.5);
503
+ let nearA = 0, nearB = 0;
504
+ for (let e = 0; e < allPaths.length; e++) {
505
+ if (e === labels[a].edgeIdx) continue;
506
+ if (pointToPolylineDist(midA, allPaths[e]) < 100) nearA++;
507
+ }
508
+ for (let e = 0; e < allPaths.length; e++) {
509
+ if (e === labels[b].edgeIdx) continue;
510
+ if (pointToPolylineDist(midB, allPaths[e]) < 100) nearB++;
511
+ }
512
+ return nearB - nearA;
513
+ });
514
+ for (const idx of order) {
515
+ const lbl = labels[idx];
516
+ const ownEdgeIdx = lbl.edgeIdx;
517
+ const ownPath = allPaths[ownEdgeIdx];
518
+ const cands = candidates[idx];
519
+ let bestScore = -Infinity;
520
+ let bestPt = cands[Math.floor(cands.length / 2)].pt;
521
+ let bestT = 0.5;
522
+ for (const { pt, t } of cands) {
523
+ let minEdgeDist = Infinity;
524
+ for (let e = 0; e < allPaths.length; e++) {
525
+ if (e === ownEdgeIdx) continue;
526
+ const d = pointToPolylineDist(pt, allPaths[e]);
527
+ if (d < minEdgeDist) minEdgeDist = d;
528
+ }
529
+ let labelOverlapPenalty = 0;
530
+ for (const placed of placedRects) {
531
+ if (rectsOverlap(
532
+ pt.x,
533
+ pt.y,
534
+ lbl.bgW,
535
+ lbl.bgH,
536
+ placed.x,
537
+ placed.y,
538
+ placed.w,
539
+ placed.h,
540
+ 6
541
+ )) {
542
+ labelOverlapPenalty += 200;
543
+ }
544
+ }
545
+ if (obstacleRects) {
546
+ for (const obs of obstacleRects) {
547
+ if (rectsOverlap(
548
+ pt.x,
549
+ pt.y,
550
+ lbl.bgW,
551
+ lbl.bgH,
552
+ obs.x + obs.w / 2,
553
+ obs.y + obs.h / 2,
554
+ obs.w,
555
+ obs.h,
556
+ 6
557
+ )) {
558
+ labelOverlapPenalty += 200;
559
+ }
560
+ }
561
+ }
562
+ const score = minEdgeDist - labelOverlapPenalty;
563
+ if (score > bestScore) {
564
+ bestScore = score;
565
+ bestPt = pt;
566
+ bestT = t;
567
+ }
568
+ }
569
+ const tan = tangentAt(ownPath, bestT);
570
+ const tLen = Math.sqrt(tan.x * tan.x + tan.y * tan.y);
571
+ if (tLen > 0) {
572
+ const nx = -tan.y / tLen;
573
+ const ny = tan.x / tLen;
574
+ const offsetDist = lbl.bgH / 2 + 4;
575
+ const sideA = {
576
+ x: bestPt.x + nx * offsetDist,
577
+ y: bestPt.y + ny * offsetDist
578
+ };
579
+ const sideB = {
580
+ x: bestPt.x - nx * offsetDist,
581
+ y: bestPt.y - ny * offsetDist
582
+ };
583
+ let scoreA = Infinity, scoreB = Infinity;
584
+ for (let e = 0; e < allPaths.length; e++) {
585
+ if (e === ownEdgeIdx) continue;
586
+ scoreA = Math.min(scoreA, pointToPolylineDist(sideA, allPaths[e]));
587
+ scoreB = Math.min(scoreB, pointToPolylineDist(sideB, allPaths[e]));
588
+ }
589
+ for (const placed of placedRects) {
590
+ if (rectsOverlap(
591
+ sideA.x,
592
+ sideA.y,
593
+ lbl.bgW,
594
+ lbl.bgH,
595
+ placed.x,
596
+ placed.y,
597
+ placed.w,
598
+ placed.h,
599
+ 6
600
+ )) {
601
+ scoreA -= 200;
602
+ }
603
+ if (rectsOverlap(
604
+ sideB.x,
605
+ sideB.y,
606
+ lbl.bgW,
607
+ lbl.bgH,
608
+ placed.x,
609
+ placed.y,
610
+ placed.w,
611
+ placed.h,
612
+ 6
613
+ )) {
614
+ scoreB -= 200;
615
+ }
616
+ }
617
+ if (obstacleRects) {
618
+ for (const obs of obstacleRects) {
619
+ const cx = obs.x + obs.w / 2, cy = obs.y + obs.h / 2;
620
+ if (rectsOverlap(
621
+ sideA.x,
622
+ sideA.y,
623
+ lbl.bgW,
624
+ lbl.bgH,
625
+ cx,
626
+ cy,
627
+ obs.w,
628
+ obs.h,
629
+ 6
630
+ )) {
631
+ scoreA -= 200;
632
+ }
633
+ if (rectsOverlap(
634
+ sideB.x,
635
+ sideB.y,
636
+ lbl.bgW,
637
+ lbl.bgH,
638
+ cx,
639
+ cy,
640
+ obs.w,
641
+ obs.h,
642
+ 6
643
+ )) {
644
+ scoreB -= 200;
645
+ }
646
+ }
647
+ }
648
+ const finalPt = scoreA >= scoreB ? sideA : sideB;
649
+ lbl.x = finalPt.x;
650
+ lbl.y = finalPt.y;
651
+ } else {
652
+ lbl.x = bestPt.x;
653
+ lbl.y = bestPt.y;
654
+ }
655
+ placedRects.push({ x: lbl.x, y: lbl.y, w: lbl.bgW, h: lbl.bgH });
656
+ }
657
+ }
658
+ function renderLegend(parent, layout, palette, isDark, activeTagGroup, fixedWidth, exportMode) {
659
+ const groups = layout.legend.map((g) => ({
660
+ name: g.name,
661
+ entries: g.entries.map((e) => ({ value: e.value, color: e.color }))
662
+ }));
663
+ const containerWidth = fixedWidth ?? layout.width;
664
+ renderIntegratedLegend(parent, {
665
+ groups,
666
+ activeGroup: activeTagGroup ?? null,
667
+ mode: exportMode ? "export" : "preview",
668
+ palette,
669
+ isDark,
670
+ width: containerWidth
671
+ });
672
+ parent.selectAll("[data-legend-group]").classed("c4-legend-group", true);
673
+ }
674
+ function renderC4Containers(container, parsed, layout, palette, isDark, onClickItem, exportDims, activeTagGroup, exportMode) {
675
+ d3Selection.select(container).selectAll(":not([data-d3-tooltip])").remove();
676
+ const width = exportDims?.width ?? container.clientWidth;
677
+ const height = exportDims?.height ?? container.clientHeight;
678
+ if (width <= 0 || height <= 0) return;
679
+ const titleHeight = parsed.title && parsed.options["no-title"] !== "on" ? TITLE_HEIGHT + 10 : 0;
680
+ const diagramW = layout.width;
681
+ const hasLegend = layout.legend.length > 0;
682
+ const C4_LAYOUT_MARGIN = 40;
683
+ const LEGEND_FIXED_GAP = 8;
684
+ const fixedLegend = !exportDims && hasLegend;
685
+ const legendLayoutSpace = C4_LAYOUT_MARGIN + LEGEND_HEIGHT;
686
+ const legendReserveH = fixedLegend ? LEGEND_HEIGHT + LEGEND_FIXED_GAP : 0;
687
+ const diagramH = fixedLegend ? layout.height - legendLayoutSpace : layout.height;
688
+ const availH = height - titleHeight - legendReserveH;
689
+ const scaleX = (width - DIAGRAM_PADDING * 2) / diagramW;
690
+ const scaleY = (availH - DIAGRAM_PADDING * 2) / diagramH;
691
+ const scale = Math.min(MAX_SCALE, scaleX, scaleY);
692
+ const scaledW = diagramW * scale;
693
+ const offsetX = (width - scaledW) / 2;
694
+ const offsetY = titleHeight + DIAGRAM_PADDING + legendReserveH;
695
+ const svg = d3Selection.select(container).append("svg").attr("width", width).attr("height", height).attr("viewBox", `0 0 ${width} ${height}`).attr("preserveAspectRatio", "xMidYMid meet").style("font-family", FONT_FAMILY);
696
+ const defs = svg.append("defs");
697
+ const AW = 10;
698
+ const AH = 7;
699
+ defs.append("marker").attr("id", "c4-arrow-end").attr("viewBox", `0 0 ${AW} ${AH}`).attr("refX", AW).attr("refY", AH / 2).attr("markerWidth", AW).attr("markerHeight", AH).attr("orient", "auto").append("polygon").attr("points", `0,0 ${AW},${AH / 2} 0,${AH}`).attr("fill", palette.textMuted);
700
+ defs.append("marker").attr("id", "c4-arrow-start").attr("viewBox", `0 0 ${AW} ${AH}`).attr("refX", 0).attr("refY", AH / 2).attr("markerWidth", AW).attr("markerHeight", AH).attr("orient", "auto").append("polygon").attr("points", `${AW},0 0,${AH / 2} ${AW},${AH}`).attr("fill", palette.textMuted);
701
+ if (parsed.title && parsed.options["no-title"] !== "on") {
702
+ const titleEl = svg.append("text").attr("class", "chart-title").attr("x", width / 2).attr("y", 30).attr("text-anchor", "middle").attr("fill", palette.text).attr("font-size", TITLE_FONT_SIZE).attr("font-weight", TITLE_FONT_WEIGHT).style(
703
+ "cursor",
704
+ onClickItem && parsed.titleLineNumber ? "pointer" : "default"
705
+ ).text(parsed.title);
706
+ if (parsed.titleLineNumber) {
707
+ titleEl.attr("data-line-number", parsed.titleLineNumber);
708
+ if (onClickItem) {
709
+ titleEl.on("click", () => onClickItem(parsed.titleLineNumber)).on("mouseenter", function() {
710
+ d3Selection.select(this).attr("opacity", 0.7);
711
+ }).on("mouseleave", function() {
712
+ d3Selection.select(this).attr("opacity", 1);
713
+ });
714
+ }
715
+ }
716
+ }
717
+ const contentG = svg.append("g").attr("transform", `translate(${offsetX}, ${offsetY}) scale(${scale})`);
718
+ if (layout.boundary) {
719
+ const b = layout.boundary;
720
+ const boundaryFill = mix(palette.surface, palette.bg, 30);
721
+ const boundaryStroke = mix(palette.textMuted, palette.bg, 50);
722
+ const boundaryG = contentG.append("g").attr("class", "c4-boundary").attr("data-line-number", String(b.lineNumber));
723
+ if (onClickItem) {
724
+ boundaryG.style("cursor", "pointer").on("click", () => {
725
+ onClickItem(b.lineNumber);
726
+ });
727
+ }
728
+ boundaryG.append("rect").attr("x", b.x).attr("y", b.y).attr("width", b.width).attr("height", b.height).attr("rx", BOUNDARY_RADIUS).attr("ry", BOUNDARY_RADIUS).attr("fill", boundaryFill).attr("stroke", boundaryStroke).attr("stroke-width", BOUNDARY_STROKE_WIDTH);
729
+ boundaryG.append("text").attr("x", b.x + 12).attr("y", b.y + 16).attr("fill", palette.textMuted).attr("font-size", BOUNDARY_LABEL_FONT_SIZE).attr("font-style", "italic").text(`${b.label} \u2014 ${b.typeLabel}`);
730
+ }
731
+ if (layout.groupBoundaries.length > 0) {
732
+ const groupFill = mix(palette.surface, palette.bg, 15);
733
+ const groupStroke = mix(palette.textMuted, palette.bg, 60);
734
+ for (const gb of layout.groupBoundaries) {
735
+ const gbG = contentG.append("g").attr("class", "c4-group-boundary").attr("data-line-number", String(gb.lineNumber));
736
+ if (onClickItem) {
737
+ gbG.style("cursor", "pointer").on("click", () => {
738
+ onClickItem(gb.lineNumber);
739
+ });
740
+ }
741
+ gbG.append("rect").attr("x", gb.x).attr("y", gb.y).attr("width", gb.width).attr("height", gb.height).attr("rx", 6).attr("ry", 6).attr("fill", groupFill).attr("stroke", groupStroke).attr("stroke-width", 1);
742
+ gbG.append("text").attr("x", gb.x + 10).attr("y", gb.y + 14).attr("fill", palette.textMuted).attr("font-size", BOUNDARY_LABEL_FONT_SIZE).attr("font-style", "italic").text(gb.label);
743
+ }
744
+ }
745
+ const boundaryLabelObstacles = [];
746
+ if (layout.boundary) {
747
+ const b = layout.boundary;
748
+ const labelText = `${b.label} \u2014 ${b.typeLabel}`;
749
+ const w = measureText(labelText, BOUNDARY_LABEL_FONT_SIZE) + 12;
750
+ const h = BOUNDARY_LABEL_FONT_SIZE + 4;
751
+ boundaryLabelObstacles.push({ x: b.x + 12, y: b.y + 16 - h + 4, w, h });
752
+ }
753
+ for (const gb of layout.groupBoundaries) {
754
+ const w = measureText(gb.label, BOUNDARY_LABEL_FONT_SIZE) + 12;
755
+ const h = BOUNDARY_LABEL_FONT_SIZE + 4;
756
+ boundaryLabelObstacles.push({ x: gb.x + 10, y: gb.y + 14 - h + 4, w, h });
757
+ }
758
+ renderEdges(
759
+ contentG,
760
+ layout.edges,
761
+ palette,
762
+ onClickItem,
763
+ boundaryLabelObstacles
764
+ );
765
+ for (const node of layout.nodes) {
766
+ const nodeG = contentG.append("g").attr("transform", `translate(${node.x}, ${node.y})`).attr("class", "c4-card").attr("data-line-number", String(node.lineNumber)).attr("data-node-id", node.id);
767
+ if (activeTagGroup) {
768
+ const tagKey = tagAttrKey(activeTagGroup);
769
+ const tagValue = node.metadata[tagKey];
770
+ if (tagValue) {
771
+ nodeG.attr(`data-tag-${tagKey}`, tagValue.toLowerCase());
772
+ } else {
773
+ const tagGroup = parsed.tagGroups.find(
774
+ (g) => g.name.toLowerCase() === tagKey || g.alias?.toLowerCase() === tagKey
775
+ );
776
+ if (tagGroup?.defaultValue) {
777
+ nodeG.attr(`data-tag-${tagKey}`, tagGroup.defaultValue.toLowerCase());
778
+ }
779
+ }
780
+ }
781
+ if (node.shape) {
782
+ nodeG.attr("data-shape", node.shape);
783
+ }
784
+ if (node.importPath) {
785
+ nodeG.attr("data-import-path", node.importPath);
786
+ }
787
+ if (onClickItem) {
788
+ nodeG.style("cursor", "pointer").on("click", () => {
789
+ onClickItem(node.lineNumber);
790
+ });
791
+ }
792
+ const w = node.width;
793
+ const h = node.height;
794
+ const solid = parsed.options["solid-fill"] === "on";
795
+ const fill = nodeFill(palette, isDark, node.type, node.color, solid);
796
+ const stroke = nodeStroke(palette, node.type, node.color);
797
+ const onFillText = contrastText(
798
+ fill,
799
+ palette.textOnFillLight,
800
+ palette.textOnFillDark
801
+ );
802
+ const shape = node.shape ?? "default";
803
+ const isExternalShape = shape === "external";
804
+ if (shape === "database" || shape === "cache") {
805
+ drawCylinderCard(
806
+ nodeG,
807
+ w,
808
+ h,
809
+ fill,
810
+ stroke,
811
+ shape === "cache"
812
+ );
813
+ } else {
814
+ drawCardRect(nodeG, w, h, fill, stroke, isExternalShape);
815
+ }
816
+ let yPos = -h / 2 + CARD_V_PAD;
817
+ if (shape === "database" || shape === "cache") {
818
+ yPos += CYLINDER_RY;
819
+ }
820
+ if (node.type !== "container" && node.type !== "component") {
821
+ const typeLabel = `\xAB${node.type}\xBB`;
822
+ nodeG.append("text").attr("x", 0).attr("y", yPos + TYPE_FONT_SIZE / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("fill", onFillText).attr("font-size", TYPE_FONT_SIZE).attr("font-style", "italic").text(typeLabel);
823
+ yPos += TYPE_LABEL_HEIGHT;
824
+ }
825
+ if (node.type === "person") {
826
+ const textWidth = measureText(node.name, NAME_FONT_SIZE);
827
+ const gap = 6;
828
+ const totalWidth = PERSON_ICON_W + gap + textWidth;
829
+ const iconCx = -totalWidth / 2 + PERSON_ICON_W / 2;
830
+ const textX = iconCx + PERSON_ICON_W / 2 + gap;
831
+ drawPersonIcon(
832
+ nodeG,
833
+ iconCx,
834
+ yPos + NAME_FONT_SIZE / 2 - 2,
835
+ stroke
836
+ );
837
+ nodeG.append("text").attr("x", textX).attr("y", yPos + NAME_FONT_SIZE / 2).attr("text-anchor", "start").attr("dominant-baseline", "central").attr("fill", onFillText).attr("font-size", NAME_FONT_SIZE).attr("font-weight", "bold").text(node.name);
838
+ } else {
839
+ nodeG.append("text").attr("x", 0).attr("y", yPos + NAME_FONT_SIZE / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("fill", onFillText).attr("font-size", NAME_FONT_SIZE).attr("font-weight", "bold").text(node.name);
840
+ }
841
+ yPos += NAME_HEIGHT;
842
+ if (node.type === "container") {
843
+ if (node.description) {
844
+ const contentWidth = w - CARD_H_PAD * 2;
845
+ const lines = wrapTextToWidth(
846
+ node.description,
847
+ DESC_FONT_SIZE,
848
+ contentWidth
849
+ );
850
+ for (const line2 of lines) {
851
+ const textEl = nodeG.append("text").attr("x", 0).attr("y", yPos + DESC_FONT_SIZE / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("fill", onFillText).attr("font-size", DESC_FONT_SIZE);
852
+ renderInlineText(
853
+ textEl,
854
+ preprocessDescriptionLine(line2),
855
+ palette,
856
+ DESC_FONT_SIZE
857
+ );
858
+ yPos += DESC_LINE_HEIGHT;
859
+ }
860
+ }
861
+ const metaEntries = collectCardMetadata(node.metadata);
862
+ if (metaEntries.length > 0) {
863
+ nodeG.append("line").attr("x1", -w / 2 + CARD_H_PAD / 2).attr("y1", yPos).attr("x2", w / 2 - CARD_H_PAD / 2).attr("y2", yPos).attr("stroke", solid ? onFillText : stroke).attr("stroke-width", 0.5).attr("stroke-opacity", 0.4);
864
+ yPos += DIVIDER_GAP;
865
+ const maxKeyWidth = Math.max(
866
+ ...metaEntries.map((e) => measureText(`${e.key}: `, META_FONT_SIZE))
867
+ );
868
+ const valueX = -w / 2 + CARD_H_PAD + maxKeyWidth;
869
+ for (const entry of metaEntries) {
870
+ nodeG.append("text").attr("x", -w / 2 + CARD_H_PAD).attr("y", yPos + META_FONT_SIZE / 2).attr("text-anchor", "start").attr("dominant-baseline", "central").attr("fill", onFillText).attr("font-size", META_FONT_SIZE).text(`${entry.key}:`);
871
+ nodeG.append("text").attr("x", valueX).attr("y", yPos + META_FONT_SIZE / 2).attr("text-anchor", "start").attr("dominant-baseline", "central").attr("fill", onFillText).attr("font-size", META_FONT_SIZE).text(entry.value);
872
+ yPos += META_LINE_HEIGHT;
873
+ }
874
+ }
875
+ } else {
876
+ nodeG.append("line").attr("x1", -w / 2 + CARD_H_PAD / 2).attr("y1", yPos).attr("x2", w / 2 - CARD_H_PAD / 2).attr("y2", yPos).attr("stroke", solid ? onFillText : stroke).attr("stroke-width", 0.5).attr("stroke-opacity", 0.4);
877
+ yPos += DIVIDER_GAP;
878
+ if (node.description) {
879
+ const contentWidth = w - CARD_H_PAD * 2;
880
+ const lines = wrapTextToWidth(
881
+ node.description,
882
+ DESC_FONT_SIZE,
883
+ contentWidth
884
+ );
885
+ for (const line2 of lines) {
886
+ const textEl = nodeG.append("text").attr("x", 0).attr("y", yPos + DESC_FONT_SIZE / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("fill", onFillText).attr("font-size", DESC_FONT_SIZE);
887
+ renderInlineText(
888
+ textEl,
889
+ preprocessDescriptionLine(line2),
890
+ palette,
891
+ DESC_FONT_SIZE
892
+ );
893
+ yPos += DESC_LINE_HEIGHT;
894
+ }
895
+ }
896
+ }
897
+ if (node.drillable) {
898
+ const clipId = `clip-drill-${node.id.replace(/\s+/g, "-")}`;
899
+ nodeG.append("clipPath").attr("id", clipId).append("rect").attr("x", -w / 2).attr("y", -h / 2).attr("width", w).attr("height", h).attr("rx", CARD_RADIUS);
900
+ nodeG.append("rect").attr("x", -w / 2).attr("y", h / 2 - DRILL_BAR_HEIGHT).attr("width", w).attr("height", DRILL_BAR_HEIGHT).attr("fill", solid ? onFillText : stroke).attr("clip-path", `url(#${clipId})`).attr("class", "c4-drill-bar");
901
+ }
902
+ }
903
+ if (hasLegend) {
904
+ const legendParent = fixedLegend ? svg.append("g").attr("class", "c4-legend-fixed").attr("transform", `translate(0, ${DIAGRAM_PADDING + titleHeight})`) : contentG.append("g").attr("class", "c4-legend");
905
+ if (activeTagGroup) {
906
+ legendParent.attr("data-legend-active", tagAttrKey(activeTagGroup));
907
+ }
908
+ renderLegend(
909
+ legendParent,
910
+ layout,
911
+ palette,
912
+ isDark,
913
+ activeTagGroup,
914
+ fixedLegend ? width : null,
915
+ exportMode
916
+ );
917
+ }
918
+ }
919
+ function renderC4ContainersForExport(content, systemName, theme, palette) {
920
+ const parsed = parseC4(content, palette);
921
+ if (parsed.error || parsed.elements.length === 0) return "";
922
+ const layout = layoutC4Containers(parsed, systemName);
923
+ if (layout.nodes.length === 0) return "";
924
+ const isDark = theme === "dark";
925
+ const el = document.createElement("div");
926
+ const titleOffset = parsed.title && parsed.options["no-title"] !== "on" ? TITLE_HEIGHT + 10 : 0;
927
+ const exportWidth = layout.width + DIAGRAM_PADDING * 2;
928
+ const exportHeight = layout.height + DIAGRAM_PADDING * 2 + titleOffset;
929
+ el.style.width = `${exportWidth}px`;
930
+ el.style.height = `${exportHeight}px`;
931
+ el.style.position = "absolute";
932
+ el.style.left = "-9999px";
933
+ document.body.appendChild(el);
934
+ try {
935
+ renderC4Containers(el, parsed, layout, palette, isDark, void 0, {
936
+ width: exportWidth,
937
+ height: exportHeight
938
+ });
939
+ const svgEl = el.querySelector("svg");
940
+ if (!svgEl) return "";
941
+ if (theme === "transparent") {
942
+ svgEl.style.background = "none";
943
+ }
944
+ svgEl.setAttribute("xmlns", "http://www.w3.org/2000/svg");
945
+ svgEl.style.fontFamily = FONT_FAMILY;
946
+ return svgEl.outerHTML;
947
+ } finally {
948
+ document.body.removeChild(el);
949
+ }
950
+ }
951
+ function renderC4ComponentsForExport(content, systemName, containerName, theme, palette) {
952
+ const parsed = parseC4(content, palette);
953
+ if (parsed.error || parsed.elements.length === 0) return "";
954
+ const layout = layoutC4Components(parsed, systemName, containerName);
955
+ if (layout.nodes.length === 0) return "";
956
+ const isDark = theme === "dark";
957
+ const el = document.createElement("div");
958
+ const titleOffset = parsed.title && parsed.options["no-title"] !== "on" ? TITLE_HEIGHT + 10 : 0;
959
+ const exportWidth = layout.width + DIAGRAM_PADDING * 2;
960
+ const exportHeight = layout.height + DIAGRAM_PADDING * 2 + titleOffset;
961
+ el.style.width = `${exportWidth}px`;
962
+ el.style.height = `${exportHeight}px`;
963
+ el.style.position = "absolute";
964
+ el.style.left = "-9999px";
965
+ document.body.appendChild(el);
966
+ try {
967
+ renderC4Containers(el, parsed, layout, palette, isDark, void 0, {
968
+ width: exportWidth,
969
+ height: exportHeight
970
+ });
971
+ const svgEl = el.querySelector("svg");
972
+ if (!svgEl) return "";
973
+ if (theme === "transparent") {
974
+ svgEl.style.background = "none";
975
+ }
976
+ svgEl.setAttribute("xmlns", "http://www.w3.org/2000/svg");
977
+ svgEl.style.fontFamily = FONT_FAMILY;
978
+ return svgEl.outerHTML;
979
+ } finally {
980
+ document.body.removeChild(el);
981
+ }
982
+ }
983
+ function renderC4Deployment(container, parsed, layout, palette, isDark, onClickItem, exportDims, activeTagGroup, exportMode) {
984
+ renderC4Containers(
985
+ container,
986
+ parsed,
987
+ layout,
988
+ palette,
989
+ isDark,
990
+ onClickItem,
991
+ exportDims,
992
+ activeTagGroup,
993
+ exportMode
994
+ );
995
+ }
996
+ function renderC4DeploymentForExport(content, theme, palette) {
997
+ const parsed = parseC4(content, palette);
998
+ if (parsed.error || parsed.deployment.length === 0) return "";
999
+ const layout = layoutC4Deployment(parsed);
1000
+ if (layout.nodes.length === 0) return "";
1001
+ const isDark = theme === "dark";
1002
+ const el = document.createElement("div");
1003
+ const titleOffset = parsed.title && parsed.options["no-title"] !== "on" ? TITLE_HEIGHT + 10 : 0;
1004
+ const exportWidth = layout.width + DIAGRAM_PADDING * 2;
1005
+ const exportHeight = layout.height + DIAGRAM_PADDING * 2 + titleOffset;
1006
+ el.style.width = `${exportWidth}px`;
1007
+ el.style.height = `${exportHeight}px`;
1008
+ el.style.position = "absolute";
1009
+ el.style.left = "-9999px";
1010
+ document.body.appendChild(el);
1011
+ try {
1012
+ renderC4Containers(el, parsed, layout, palette, isDark, void 0, {
1013
+ width: exportWidth,
1014
+ height: exportHeight
1015
+ });
1016
+ const svgEl = el.querySelector("svg");
1017
+ if (!svgEl) return "";
1018
+ if (theme === "transparent") {
1019
+ svgEl.style.background = "none";
1020
+ }
1021
+ svgEl.setAttribute("xmlns", "http://www.w3.org/2000/svg");
1022
+ svgEl.style.fontFamily = FONT_FAMILY;
1023
+ return svgEl.outerHTML;
1024
+ } finally {
1025
+ document.body.removeChild(el);
1026
+ }
1027
+ }
1028
+
1029
+ export {
1030
+ renderC4Context,
1031
+ renderC4ContextForExport,
1032
+ renderC4Containers,
1033
+ renderC4ContainersForExport,
1034
+ renderC4ComponentsForExport,
1035
+ renderC4Deployment,
1036
+ renderC4DeploymentForExport
1037
+ };
1038
+ //# sourceMappingURL=chunk-P4RH4OSP.js.map