@diagrammo/dgmo 0.45.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 (250) hide show
  1. package/dist/advanced.cjs +31 -25
  2. package/dist/advanced.d.cts +3 -855
  3. package/dist/advanced.d.ts +3 -855
  4. package/dist/advanced.js +541 -70101
  5. package/dist/analyzer-UYKBXAKR.js +15 -0
  6. package/dist/auto.cjs +500 -341
  7. package/dist/auto.css +342 -159
  8. package/dist/auto.d.cts +10 -8
  9. package/dist/auto.d.ts +10 -8
  10. package/dist/auto.js +373 -265
  11. package/dist/auto.mjs +500 -341
  12. package/dist/block.cjs +31 -25
  13. package/dist/block.js +83 -69411
  14. package/dist/calculator-LDYQCDDY.js +11 -0
  15. package/dist/charts-d3-QOZHKTCT.js +25 -0
  16. package/dist/chunk-2DXMZRWJ.js +800 -0
  17. package/dist/chunk-2LACINWN.js +786 -0
  18. package/dist/chunk-33LCUH4H.js +893 -0
  19. package/dist/chunk-3CIOJ5YJ.js +35 -0
  20. package/dist/chunk-3HDBAXF5.js +86 -0
  21. package/dist/chunk-3JLKYGBQ.js +290 -0
  22. package/dist/chunk-3MQQNXPD.js +415 -0
  23. package/dist/chunk-3R5POCTZ.js +967 -0
  24. package/dist/chunk-3SMGIJDD.js +112 -0
  25. package/dist/chunk-44CM72SQ.js +409 -0
  26. package/dist/chunk-4GRI7MEB.js +1604 -0
  27. package/dist/chunk-4OHACBGX.js +606 -0
  28. package/dist/chunk-5DH3KYFH.js +21 -0
  29. package/dist/chunk-5MLYVTYM.js +76 -0
  30. package/dist/chunk-5QXL4NYS.js +553 -0
  31. package/dist/chunk-5WRI5ZAA.js +31 -0
  32. package/dist/chunk-6EK55ART.js +117 -0
  33. package/dist/chunk-6FZYCKXB.js +2065 -0
  34. package/dist/chunk-6HO5ZTXK.js +277 -0
  35. package/dist/chunk-6KFWC4CO.js +7 -0
  36. package/dist/chunk-6MTWIQVO.js +121 -0
  37. package/dist/chunk-6NYWYNBI.js +329 -0
  38. package/dist/chunk-6SM5NJQH.js +620 -0
  39. package/dist/chunk-725LDASD.js +422 -0
  40. package/dist/chunk-76OWZ3BA.js +130 -0
  41. package/dist/chunk-7JEGTRLD.js +338 -0
  42. package/dist/chunk-A44USUV7.js +923 -0
  43. package/dist/chunk-A4GOY3PE.js +25 -0
  44. package/dist/chunk-A4VFQH5G.js +251 -0
  45. package/dist/chunk-AELOBFW5.js +375 -0
  46. package/dist/chunk-AN2MOO5T.js +522 -0
  47. package/dist/chunk-BMR4CIEO.js +41 -0
  48. package/dist/chunk-BS7SV7C3.js +51 -0
  49. package/dist/chunk-BUPDIHML.js +613 -0
  50. package/dist/chunk-BUVJFRPY.js +887 -0
  51. package/dist/chunk-C3Z4DX3V.js +112 -0
  52. package/dist/chunk-CI2G3OEB.js +1818 -0
  53. package/dist/chunk-CKNVO24D.js +54 -0
  54. package/dist/chunk-CUVHQHZ7.js +391 -0
  55. package/dist/chunk-CXK4DZH3.js +114 -0
  56. package/dist/chunk-DKEPS5LH.js +315 -0
  57. package/dist/chunk-DU7W62N5.js +487 -0
  58. package/dist/chunk-DZAQBW35.js +751 -0
  59. package/dist/chunk-EB6FPQUP.js +316 -0
  60. package/dist/chunk-EMS2SK26.js +376 -0
  61. package/dist/chunk-ERLBPDGU.js +510 -0
  62. package/dist/chunk-EVCVDTXE.js +732 -0
  63. package/dist/chunk-FCIIHT24.js +384 -0
  64. package/dist/chunk-FFUN5D66.js +97 -0
  65. package/dist/chunk-FITOLXSX.js +714 -0
  66. package/dist/chunk-FMDSF3OV.js +68 -0
  67. package/dist/chunk-FNB3PNGF.js +410 -0
  68. package/dist/chunk-G2H74XJZ.js +2104 -0
  69. package/dist/chunk-GMR5MFHJ.js +339 -0
  70. package/dist/chunk-H6PI3KJT.js +305 -0
  71. package/dist/chunk-I46X5VUA.js +59 -0
  72. package/dist/chunk-I5IGB56C.js +255 -0
  73. package/dist/chunk-IH6ZMVQ5.js +3950 -0
  74. package/dist/chunk-IJZFN3HM.js +463 -0
  75. package/dist/chunk-IPWPWM6R.js +3079 -0
  76. package/dist/chunk-IYULMRBE.js +23 -0
  77. package/dist/chunk-IZMRON6C.js +167 -0
  78. package/dist/chunk-J5FIXQRT.js +813 -0
  79. package/dist/chunk-JED6ZDEE.js +73 -0
  80. package/dist/chunk-JEUJOX5X.js +136 -0
  81. package/dist/chunk-JM4LEPY3.js +436 -0
  82. package/dist/chunk-L3UHP6TD.js +101 -0
  83. package/dist/chunk-L66GEFB6.js +524 -0
  84. package/dist/chunk-LIOHW36Z.js +469 -0
  85. package/dist/chunk-LP7EC4MI.js +16 -0
  86. package/dist/chunk-MITI6NOL.js +548 -0
  87. package/dist/chunk-MO5YHHMQ.js +168 -0
  88. package/dist/chunk-MZSBFAPJ.js +885 -0
  89. package/dist/chunk-NNAAJVBU.js +9 -0
  90. package/dist/chunk-NOTA6TEI.js +47 -0
  91. package/dist/chunk-NURGB5Z4.js +335 -0
  92. package/dist/chunk-NX4EQS3U.js +1062 -0
  93. package/dist/chunk-NXMESY4J.js +1 -0
  94. package/dist/chunk-OJR3MQYV.js +1458 -0
  95. package/dist/chunk-OURHMODR.js +454 -0
  96. package/dist/chunk-P235SVAH.js +1057 -0
  97. package/dist/chunk-P4RH4OSP.js +1038 -0
  98. package/dist/chunk-P6T66CCZ.js +226 -0
  99. package/dist/chunk-PBAHEPLQ.js +223 -0
  100. package/dist/chunk-PBHE5UG6.js +1530 -0
  101. package/dist/chunk-PDKUANDZ.js +35 -0
  102. package/dist/chunk-PWIHZ6NJ.js +296 -0
  103. package/dist/chunk-QEBUY5NY.js +1685 -0
  104. package/dist/chunk-QLCCJEJI.js +253 -0
  105. package/dist/chunk-QOOUC2J5.js +52 -0
  106. package/dist/chunk-QYTPPDAO.js +402 -0
  107. package/dist/chunk-R3XI2VC7.js +801 -0
  108. package/dist/chunk-RCERWOJR.js +154 -0
  109. package/dist/chunk-RJE5JTVK.js +362 -0
  110. package/dist/chunk-RTLBSQ3G.js +1155 -0
  111. package/dist/chunk-RUNNI6VE.js +448 -0
  112. package/dist/chunk-RVMRZAPB.js +354 -0
  113. package/dist/chunk-RYEGFMUO.js +955 -0
  114. package/dist/chunk-RZCF2VVM.js +528 -0
  115. package/dist/chunk-SML67VPV.js +1025 -0
  116. package/dist/chunk-SQ7IY53Q.js +44 -0
  117. package/dist/chunk-SQAG3UXH.js +161 -0
  118. package/dist/chunk-SRTU6HMQ.js +1851 -0
  119. package/dist/chunk-T2OE3YQB.js +550 -0
  120. package/dist/chunk-T3UBLACL.js +299 -0
  121. package/dist/chunk-TLVIHFRO.js +296 -0
  122. package/dist/chunk-TYS6CSS5.js +52 -0
  123. package/dist/chunk-U6EQI3U6.js +130 -0
  124. package/dist/chunk-UJ4FJLBF.js +86 -0
  125. package/dist/chunk-ULOEATBZ.js +347 -0
  126. package/dist/chunk-UNCCZ3NU.js +127 -0
  127. package/dist/chunk-USZW2HHU.js +197 -0
  128. package/dist/chunk-UWN33VNX.js +1610 -0
  129. package/dist/chunk-UZ7EZW3B.js +40 -0
  130. package/dist/chunk-VOKGH627.js +37 -0
  131. package/dist/chunk-WBRZJOEC.js +204 -0
  132. package/dist/chunk-WCSOJFU3.js +380 -0
  133. package/dist/chunk-WCT3NYCV.js +160 -0
  134. package/dist/chunk-WVAJFAIA.js +529 -0
  135. package/dist/chunk-X5LCYCL5.js +394 -0
  136. package/dist/chunk-XAKEUMEJ.js +1256 -0
  137. package/dist/chunk-Y2ZE3MSG.js +590 -0
  138. package/dist/chunk-Y3J7VR52.js +646 -0
  139. package/dist/chunk-Y5QGVTVC.js +33 -0
  140. package/dist/chunk-YAA6IID3.js +548 -0
  141. package/dist/chunk-YGJTDXVR.js +692 -0
  142. package/dist/chunk-YRYKTYMQ.js +186 -0
  143. package/dist/chunk-YYVTBZZC.js +61 -0
  144. package/dist/chunk-Z5CGSFJB.js +647 -0
  145. package/dist/chunk-Z72XRXD4.js +787 -0
  146. package/dist/chunk-ZGR7ULFJ.js +978 -0
  147. package/dist/chunk-ZJEL4H2R.js +851 -0
  148. package/dist/chunk-ZO27FGJS.js +554 -0
  149. package/dist/cli.cjs +112 -112
  150. package/dist/collapse-CLPJD2RL.js +11 -0
  151. package/dist/collapse-SLINJQW6.js +9 -0
  152. package/dist/collapse-ZWUFFCK2.js +9 -0
  153. package/dist/compute-G55RG53N.js +8 -0
  154. package/dist/dgmo-router-DZCIDUO5.js +73 -0
  155. package/dist/dimensions-J3FHIMJ6.js +22 -0
  156. package/dist/element.cjs +492 -325
  157. package/dist/element.js +367 -259
  158. package/dist/element.mjs +492 -325
  159. package/dist/flowchart-parser-ZNSKIBVR.js +21 -0
  160. package/dist/flowchart-renderer-ZAEOL4DX.js +35 -0
  161. package/dist/index.cjs +34 -28
  162. package/dist/index.d.cts +3 -469
  163. package/dist/index.d.ts +3 -469
  164. package/dist/index.js +137 -69679
  165. package/dist/layout-46N4JJYK.js +17 -0
  166. package/dist/layout-5RA6QWR6.js +26 -0
  167. package/dist/layout-5T6RH5M6.js +14 -0
  168. package/dist/layout-7QXCNOVS.js +13 -0
  169. package/dist/layout-EE6GE46Y.js +17 -0
  170. package/dist/layout-G23F7LDH.js +9 -0
  171. package/dist/layout-HRBZPFCN.js +23 -0
  172. package/dist/layout-HWDSRX5E.js +20 -0
  173. package/dist/layout-HYZPWAK5.js +18 -0
  174. package/dist/layout-N76NR4ZO.js +19 -0
  175. package/dist/layout-OB3RI63S.js +14 -0
  176. package/dist/layout-S2QBYSK2.js +15 -0
  177. package/dist/layout-UM5F4DJB.js +15 -0
  178. package/dist/layout-search-OYV77HHS.js +1655 -0
  179. package/dist/load-data-EHDDZ5ZS.js +8 -0
  180. package/dist/palettes-PRJ2RI6D.js +56 -0
  181. package/dist/parser-2MFP2H2E.js +11 -0
  182. package/dist/parser-3STE62GB.js +21 -0
  183. package/dist/parser-4FL2CMDG.js +18 -0
  184. package/dist/parser-4OGOBJJ4.js +13 -0
  185. package/dist/parser-6BTRLKRZ.js +15 -0
  186. package/dist/parser-AQETEVRW.js +21 -0
  187. package/dist/parser-CFZD5IBL.js +12 -0
  188. package/dist/parser-FUSKFGZN.js +16 -0
  189. package/dist/parser-I6KXEYFK.js +13 -0
  190. package/dist/parser-IXSZZQHY.js +18 -0
  191. package/dist/parser-JAL2HD4I.js +13 -0
  192. package/dist/parser-KNSQSKQO.js +20 -0
  193. package/dist/parser-N5O4IHUN.js +13 -0
  194. package/dist/parser-NGU3ETO6.js +16 -0
  195. package/dist/parser-NRAI6O25.js +19 -0
  196. package/dist/parser-O5J4VBII.js +11 -0
  197. package/dist/parser-OV7NOBW6.js +21 -0
  198. package/dist/parser-PQ3POX45.js +23 -0
  199. package/dist/parser-TFZFBVWN.js +11 -0
  200. package/dist/parser-TRTHDFPP.js +18 -0
  201. package/dist/parser-VQP2UJ6W.js +11 -0
  202. package/dist/parser-XDVELHRK.js +13 -0
  203. package/dist/parser-XKRP7QKP.js +11 -0
  204. package/dist/parser-Y6WTBU76.js +14 -0
  205. package/dist/parser-ZSDPZYQP.js +12 -0
  206. package/dist/renderer-2XQQQ6QR.js +28 -0
  207. package/dist/renderer-5NDVNDAZ.js +26 -0
  208. package/dist/renderer-6EEZHMH4.js +26 -0
  209. package/dist/renderer-6W5462RB.js +43 -0
  210. package/dist/renderer-7MVLWWQD.js +22 -0
  211. package/dist/renderer-AWWAF6XD.js +33 -0
  212. package/dist/renderer-BYCNMBF7.js +34 -0
  213. package/dist/renderer-CLJHQPBJ.js +29 -0
  214. package/dist/renderer-GHPZMFU2.js +15 -0
  215. package/dist/renderer-JGBKUCZD.js +16 -0
  216. package/dist/renderer-KQB7UGDS.js +19 -0
  217. package/dist/renderer-PGX2TKVS.js +24 -0
  218. package/dist/renderer-QIWVHWZO.js +15 -0
  219. package/dist/renderer-RPOA5YPZ.js +35 -0
  220. package/dist/renderer-SMIBCRVB.js +24 -0
  221. package/dist/renderer-T7YKC5VP.js +28 -0
  222. package/dist/renderer-TOCOVLHS.js +37 -0
  223. package/dist/renderer-UWA5XS74.js +39 -0
  224. package/dist/renderer-VACVQYML.js +25 -0
  225. package/dist/renderer-W4M6QLIJ.js +37 -0
  226. package/dist/renderer-WZRREQ2P.js +23 -0
  227. package/dist/renderer-YY2OSF6E.js +27 -0
  228. package/dist/renderer-Z7I3W2PP.js +24 -0
  229. package/dist/renderer-ZGNTUWC4.js +22 -0
  230. package/dist/renderer-ZTR3DER3.js +19 -0
  231. package/dist/renderer-radial-C32GPJQ7.js +21 -0
  232. package/dist/resolver-NOAA7ID7.js +13 -0
  233. package/dist/state-collapse-HCGD6IVJ.js +8 -0
  234. package/dist/state-parser-Z2V23ATR.js +18 -0
  235. package/dist/state-renderer-RFYHSP4K.js +34 -0
  236. package/dist/themes-1-CuKpeH.d.cts +908 -0
  237. package/dist/themes-1-CuKpeH.d.ts +908 -0
  238. package/package.json +1 -1
  239. package/src/auto/index.ts +55 -57
  240. package/src/auto/shared.ts +142 -142
  241. package/src/auto/styles.ts +44 -199
  242. package/src/c4/renderer.ts +2 -2
  243. package/src/element/index.ts +29 -21
  244. package/src/infra/renderer.ts +1 -1
  245. package/src/render.ts +34 -14
  246. package/src/sitemap/renderer.ts +1 -1
  247. package/src/timeline/renderer.ts +1 -1
  248. package/src/utils/hover-styles.ts +6 -1
  249. package/src/utils/legend-d3.ts +1 -4
  250. package/src/utils/legend-svg.ts +1 -1
@@ -0,0 +1,786 @@
1
+ import {
2
+ NOTE_BADGE_RADIUS,
3
+ noteConnectorPoints,
4
+ renderNoteBadge,
5
+ renderNoteBox,
6
+ renderNoteConnector
7
+ } from "./chunk-JEUJOX5X.js";
8
+ import {
9
+ renderInlineText
10
+ } from "./chunk-3HDBAXF5.js";
11
+ import {
12
+ wrapDescriptionLines
13
+ } from "./chunk-BMR4CIEO.js";
14
+ import {
15
+ EDGE_STROKE_WIDTH,
16
+ NODE_STROKE_WIDTH
17
+ } from "./chunk-3CIOJ5YJ.js";
18
+ import {
19
+ renderIntegratedLegend
20
+ } from "./chunk-PWIHZ6NJ.js";
21
+ import {
22
+ ScaleContext
23
+ } from "./chunk-YYVTBZZC.js";
24
+ import {
25
+ getMaxLegendReservedHeight
26
+ } from "./chunk-YAA6IID3.js";
27
+ import {
28
+ TITLE_FONT_SIZE,
29
+ TITLE_FONT_WEIGHT,
30
+ TITLE_Y
31
+ } from "./chunk-A4GOY3PE.js";
32
+ import {
33
+ FONT_FAMILY
34
+ } from "./chunk-6KFWC4CO.js";
35
+ import {
36
+ CHAR_WIDTH_RATIO,
37
+ measureText,
38
+ truncateText
39
+ } from "./chunk-WCT3NYCV.js";
40
+ import {
41
+ contrastText,
42
+ mix,
43
+ relativeLuminance,
44
+ shapeFill,
45
+ themeBaseBg,
46
+ valueRampColor
47
+ } from "./chunk-P6T66CCZ.js";
48
+ import {
49
+ resolveTagColor
50
+ } from "./chunk-QLCCJEJI.js";
51
+ import {
52
+ resolveColor
53
+ } from "./chunk-TLVIHFRO.js";
54
+
55
+ // src/boxes-and-lines/renderer.ts
56
+ import * as d3Selection from "d3-selection";
57
+ import * as d3Shape from "d3-shape";
58
+ var DIAGRAM_PADDING = 20;
59
+ var NODE_FONT_SIZE = 11;
60
+ var MIN_NODE_FONT_SIZE = 9;
61
+ var EDGE_LABEL_FONT_SIZE = 11;
62
+ var NODE_RX = 8;
63
+ var COLLAPSE_BAR_HEIGHT = 4;
64
+ var ARROWHEAD_W = 5;
65
+ var ARROWHEAD_H = 4;
66
+ var DESC_FONT_SIZE = 10;
67
+ var DESC_LINE_HEIGHT = 1.4;
68
+ var MAX_DESC_LINES = 6;
69
+ var NODE_TEXT_PADDING = 12;
70
+ var GROUP_RX = 8;
71
+ var GROUP_LABEL_FONT_SIZE = 14;
72
+ var GROUP_LABEL_ZONE = 32;
73
+ var RAMP_FLOOR = 15;
74
+ var VALUE_FONT_SIZE = 11;
75
+ var lineGeneratorLR = d3Shape.line().x((d) => d.x).y((d) => d.y).curve(d3Shape.curveBasis);
76
+ var lineGeneratorTB = d3Shape.line().x((d) => d.x).y((d) => d.y).curve(d3Shape.curveBasis);
77
+ var lineGeneratorStraight = d3Shape.line().x((d) => d.x).y((d) => d.y).curve(d3Shape.curveLinear);
78
+ function splitCamelCase(word) {
79
+ const parts = [];
80
+ let start = 0;
81
+ for (let i = 1; i < word.length; i++) {
82
+ const prev = word[i - 1];
83
+ const curr = word[i];
84
+ const next = i + 1 < word.length ? word[i + 1] : "";
85
+ const lowerToUpper = prev >= "a" && prev <= "z" && curr >= "A" && curr <= "Z";
86
+ const upperRunEnd = prev >= "A" && prev <= "Z" && curr >= "A" && curr <= "Z" && next >= "a" && next <= "z";
87
+ if (lowerToUpper || upperRunEnd) {
88
+ parts.push(word.slice(start, i));
89
+ start = i;
90
+ }
91
+ }
92
+ parts.push(word.slice(start));
93
+ return parts.length > 1 ? parts : [word];
94
+ }
95
+ function fitLabelToHeader(label, nodeWidth, maxLines) {
96
+ const maxTextWidth = nodeWidth - NODE_TEXT_PADDING * 2;
97
+ const rawParts = label.split(/(\s+|-)/);
98
+ const words = [];
99
+ for (const part of rawParts) {
100
+ if (!part || /^\s+$/.test(part) || part === "-") continue;
101
+ words.push(...splitCamelCase(part));
102
+ }
103
+ for (let fontSize = NODE_FONT_SIZE; fontSize >= MIN_NODE_FONT_SIZE; fontSize--) {
104
+ if (maxTextWidth < measureText("MM", fontSize)) continue;
105
+ const lines = [];
106
+ let current = "";
107
+ for (const word of words) {
108
+ const test = current ? `${current} ${word}` : word;
109
+ if (measureText(test, fontSize) <= maxTextWidth) {
110
+ current = test;
111
+ } else {
112
+ if (current) lines.push(current);
113
+ current = word;
114
+ }
115
+ }
116
+ if (current) lines.push(current);
117
+ const fits = (l) => measureText(l, fontSize) <= maxTextWidth;
118
+ if (lines.length <= maxLines && lines.every(fits)) {
119
+ return { lines, fontSize };
120
+ }
121
+ if (lines.length <= maxLines) {
122
+ const result2 = lines.map(
123
+ (l) => fits(l) ? l : truncateText(l, fontSize, maxTextWidth)
124
+ );
125
+ return { lines: result2, fontSize };
126
+ }
127
+ const result = lines.slice(0, maxLines).map((l) => fits(l) ? l : truncateText(l, fontSize, maxTextWidth));
128
+ const last = result[maxLines - 1];
129
+ if (!last.endsWith("\u2026")) {
130
+ result[maxLines - 1] = truncateText(
131
+ last + "\u2026",
132
+ fontSize,
133
+ maxTextWidth
134
+ );
135
+ }
136
+ return { lines: result, fontSize };
137
+ }
138
+ const truncated = truncateText(label, MIN_NODE_FONT_SIZE, maxTextWidth);
139
+ return { lines: [truncated], fontSize: MIN_NODE_FONT_SIZE };
140
+ }
141
+ function nodeColors(node, tagGroups, activeGroupName, palette, isDark, value, solid) {
142
+ const neutralFill = mix(palette.bg, palette.text, isDark ? 90 : 95);
143
+ if (value.active) {
144
+ if (node.value === void 0) {
145
+ const text3 = contrastText(
146
+ neutralFill,
147
+ palette.textOnFillLight,
148
+ palette.textOnFillDark
149
+ );
150
+ return { fill: neutralFill, stroke: value.hue, text: text3 };
151
+ }
152
+ const rampColor = value.fillForValue(node.value);
153
+ const fill2 = shapeFill(palette, rampColor, isDark, {
154
+ ...solid !== void 0 && { solid }
155
+ });
156
+ const stroke2 = value.twoColor ? rampColor : value.hue;
157
+ const text2 = contrastText(
158
+ fill2,
159
+ palette.textOnFillLight,
160
+ palette.textOnFillDark
161
+ );
162
+ return { fill: fill2, stroke: stroke2, text: text2 };
163
+ }
164
+ const tagColor = resolveTagColor(
165
+ node.metadata,
166
+ [...tagGroups],
167
+ activeGroupName
168
+ );
169
+ if (tagColor) {
170
+ const fill2 = shapeFill(palette, tagColor, isDark, {
171
+ ...solid !== void 0 && { solid }
172
+ });
173
+ const stroke2 = tagColor;
174
+ const text2 = contrastText(
175
+ fill2,
176
+ palette.textOnFillLight,
177
+ palette.textOnFillDark
178
+ );
179
+ return { fill: fill2, stroke: stroke2, text: text2 };
180
+ }
181
+ const fill = mix(palette.bg, palette.text, isDark ? 90 : 95);
182
+ const stroke = mix(palette.text, palette.bg, isDark ? 60 : 40);
183
+ const text = palette.text;
184
+ return { fill, stroke, text };
185
+ }
186
+ function edgeColor(edge, tagGroups, activeGroupName, palette) {
187
+ const hasTagMeta = Object.keys(edge.metadata).length > 0 && activeGroupName != null;
188
+ if (hasTagMeta) {
189
+ const tagColor = resolveTagColor(
190
+ edge.metadata,
191
+ [...tagGroups],
192
+ activeGroupName
193
+ );
194
+ if (tagColor) return tagColor;
195
+ }
196
+ return palette.textMuted;
197
+ }
198
+ function ensureArrowMarkers(defs, colors) {
199
+ for (const color of colors) {
200
+ const id = `bl-arrow-${color.replace("#", "")}`;
201
+ if (!defs.select(`#${id}`).empty()) continue;
202
+ defs.append("marker").attr("id", id).attr("viewBox", `0 0 ${ARROWHEAD_W * 2} ${ARROWHEAD_H * 2}`).attr("refX", ARROWHEAD_W * 2).attr("refY", ARROWHEAD_H).attr("markerWidth", ARROWHEAD_W).attr("markerHeight", ARROWHEAD_H).attr("orient", "auto").append("polygon").attr(
203
+ "points",
204
+ `0,0 ${ARROWHEAD_W * 2},${ARROWHEAD_H} 0,${ARROWHEAD_H * 2}`
205
+ ).attr("fill", color);
206
+ const revId = `bl-arrow-rev-${color.replace("#", "")}`;
207
+ if (!defs.select(`#${revId}`).empty()) continue;
208
+ defs.append("marker").attr("id", revId).attr("viewBox", `0 0 ${ARROWHEAD_W * 2} ${ARROWHEAD_H * 2}`).attr("refX", 0).attr("refY", ARROWHEAD_H).attr("markerWidth", ARROWHEAD_W).attr("markerHeight", ARROWHEAD_H).attr("orient", "auto").append("polygon").attr(
209
+ "points",
210
+ `${ARROWHEAD_W * 2},0 0,${ARROWHEAD_H} ${ARROWHEAD_W * 2},${ARROWHEAD_H * 2}`
211
+ ).attr("fill", color);
212
+ }
213
+ }
214
+ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options) {
215
+ const {
216
+ onClickItem,
217
+ exportDims,
218
+ activeTagGroup,
219
+ hiddenTagValues,
220
+ hideDescriptions,
221
+ controlsExpanded,
222
+ onToggleDescriptions,
223
+ onToggleControlsExpand,
224
+ exportMode = false,
225
+ controlsHost,
226
+ rampDomain
227
+ } = options ?? {};
228
+ d3Selection.select(container).selectAll(":not([data-d3-tooltip])").remove();
229
+ const width = exportDims?.width ?? container.clientWidth;
230
+ const height = exportDims?.height ?? container.clientHeight;
231
+ if (width <= 0 || height <= 0) return;
232
+ const sctx = ScaleContext.identity();
233
+ const sDiagramPadding = sctx.aesthetic(DIAGRAM_PADDING);
234
+ const sMinNodeFontSize = sctx.text(MIN_NODE_FONT_SIZE);
235
+ const sEdgeLabelFontSize = sctx.text(EDGE_LABEL_FONT_SIZE);
236
+ const sEdgeStrokeWidth = sctx.structural(EDGE_STROKE_WIDTH);
237
+ const sNodeStrokeWidth = sctx.structural(NODE_STROKE_WIDTH);
238
+ const sCollapseBarHeight = sctx.structural(COLLAPSE_BAR_HEIGHT);
239
+ const sDescFontSize = sctx.text(DESC_FONT_SIZE);
240
+ const sGroupLabelFontSize = sctx.text(GROUP_LABEL_FONT_SIZE);
241
+ const sGroupLabelZone = sctx.structural(GROUP_LABEL_ZONE);
242
+ const sTitleFontSize = sctx.text(TITLE_FONT_SIZE);
243
+ const sTitleY = sctx.structural(TITLE_Y);
244
+ const nodeValues = parsed.nodes.filter((n) => n.value !== void 0).map((n) => n.value);
245
+ const hasRamp = nodeValues.length > 0;
246
+ const rampMin = rampDomain?.min ?? (hasRamp ? Math.min(...nodeValues) : 0);
247
+ const rampMax = rampDomain?.max ?? Math.max(...nodeValues);
248
+ const rampHue = resolveColor(parsed.boxMetricColor ?? "", palette) ?? palette.primary;
249
+ const rampLow = parsed.boxMetricLowColor ? resolveColor(parsed.boxMetricLowColor, palette) ?? void 0 : void 0;
250
+ const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
251
+ const rampLowFloor = mix(rampHue, rampBase, RAMP_FLOOR);
252
+ const fillForValue = (v) => {
253
+ const t = rampMax > rampMin ? (v - rampMin) / (rampMax - rampMin) : 1;
254
+ if (rampLow !== void 0)
255
+ return valueRampColor(rampLow, rampHue, t, { isDark });
256
+ const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
257
+ return mix(rampHue, rampBase, pct);
258
+ };
259
+ const VALUE_NAME = hasRamp ? parsed.boxMetric?.trim() || "Value" : null;
260
+ const matchColorGroup = (v) => {
261
+ const lv = v.trim().toLowerCase();
262
+ if (lv === "" || lv === "none") return null;
263
+ const tg = parsed.tagGroups.find((g) => g.name.toLowerCase() === lv);
264
+ if (tg) return tg.name;
265
+ if (lv === VALUE_NAME?.toLowerCase()) return VALUE_NAME;
266
+ return v;
267
+ };
268
+ const override = activeTagGroup;
269
+ let activeGroup;
270
+ if (override !== void 0) {
271
+ activeGroup = override === null ? null : matchColorGroup(override);
272
+ } else if (parsed.options["active-tag"] !== void 0) {
273
+ activeGroup = matchColorGroup(parsed.options["active-tag"]);
274
+ } else {
275
+ activeGroup = VALUE_NAME ?? (parsed.tagGroups.length > 0 ? parsed.tagGroups[0].name : null);
276
+ }
277
+ const activeIsValue = VALUE_NAME !== null && activeGroup === VALUE_NAME;
278
+ const valueGroup = VALUE_NAME !== null ? {
279
+ name: VALUE_NAME,
280
+ entries: [],
281
+ gradient: {
282
+ min: rampMin,
283
+ max: rampMax,
284
+ low: rampLow ?? rampLowFloor,
285
+ high: rampHue
286
+ }
287
+ } : null;
288
+ const legendGroups = [
289
+ ...valueGroup ? [valueGroup] : [],
290
+ ...parsed.tagGroups
291
+ ];
292
+ const reserveHasDescriptions = parsed.nodes.some(
293
+ (n) => n.description && n.description.length > 0
294
+ );
295
+ const willRenderLegend = legendGroups.length > 0 || reserveHasDescriptions && controlsHost !== "app";
296
+ const sLegendHeight = willRenderLegend ? sctx.structural(
297
+ getMaxLegendReservedHeight(
298
+ {
299
+ groups: legendGroups,
300
+ position: { placement: "top-center", titleRelation: "below-title" },
301
+ mode: exportMode ? "export" : "preview"
302
+ },
303
+ width
304
+ )
305
+ ) : 0;
306
+ const hidden = hiddenTagValues ?? parsed.initialHiddenTagValues;
307
+ const nodeMap = /* @__PURE__ */ new Map();
308
+ for (const node of parsed.nodes) nodeMap.set(node.label, node);
309
+ const layoutNodeMap = /* @__PURE__ */ new Map();
310
+ for (const ln of layout.nodes) layoutNodeMap.set(ln.label, ln);
311
+ const showTitle = !!parsed.title && parsed.options["no-title"] !== "on";
312
+ const titleOffset = showTitle ? 40 : 0;
313
+ const hasAnyDescriptions = parsed.nodes.some(
314
+ (n) => n.description && n.description.length > 0
315
+ );
316
+ const needsLegend = legendGroups.length > 0 || hasAnyDescriptions && onToggleDescriptions;
317
+ const legendH = needsLegend ? sLegendHeight + 8 : 0;
318
+ const groupLabelsSet = new Set(layout.groups.map((g) => g.label));
319
+ let labelZoneExtension = 0;
320
+ for (const group of parsed.groups) {
321
+ if (group.children.some((c) => groupLabelsSet.has(c))) {
322
+ labelZoneExtension += sGroupLabelZone;
323
+ }
324
+ }
325
+ const contentW = layout.width;
326
+ const contentH = layout.height + titleOffset + legendH + labelZoneExtension;
327
+ const scaleX = width / (contentW + sDiagramPadding * 2);
328
+ const scaleY = height / (contentH + sDiagramPadding * 2);
329
+ const scale = Math.min(scaleX, scaleY, 3);
330
+ let centerShiftX = 0;
331
+ let centerShiftY = 0;
332
+ if (parsed.nodePositions && parsed.nodePositions.size > 0) {
333
+ let bMinX = Infinity, bMinY = Infinity, bMaxX = -Infinity, bMaxY = -Infinity;
334
+ const accB = (x, y) => {
335
+ if (x < bMinX) bMinX = x;
336
+ if (x > bMaxX) bMaxX = x;
337
+ if (y < bMinY) bMinY = y;
338
+ if (y > bMaxY) bMaxY = y;
339
+ };
340
+ for (const n of layout.nodes) {
341
+ accB(n.x - n.width / 2, n.y - n.height / 2);
342
+ accB(n.x + n.width / 2, n.y + n.height / 2);
343
+ }
344
+ for (const g of layout.groups) {
345
+ accB(g.x - g.width / 2, g.y - g.height / 2);
346
+ accB(g.x + g.width / 2, g.y + g.height / 2);
347
+ }
348
+ for (const e of layout.edges) for (const p of e.points) accB(p.x, p.y);
349
+ if (Number.isFinite(bMinX)) {
350
+ centerShiftX = (layout.width - bMaxX - bMinX) / 2;
351
+ centerShiftY = (layout.height - bMaxY - bMinY) / 2;
352
+ }
353
+ }
354
+ const offsetX = (width - contentW * scale) / 2 + centerShiftX * scale;
355
+ const offsetY = sDiagramPadding + titleOffset + legendH + centerShiftY * scale;
356
+ const svg = d3Selection.select(container).append("svg").attr("width", width).attr("height", height).attr("viewBox", `0 0 ${width} ${height}`).attr("preserveAspectRatio", "xMidYMin meet").style("font-family", FONT_FAMILY).style("background", palette.bg);
357
+ if (sctx.isBelowFloor) {
358
+ svg.attr("width", "100%");
359
+ }
360
+ const defs = svg.append("defs");
361
+ if (showTitle) {
362
+ svg.append("text").attr("x", width / 2).attr("y", sTitleY).attr("text-anchor", "middle").attr("font-size", sTitleFontSize).attr("font-weight", TITLE_FONT_WEIGHT).attr("fill", palette.text).text(parsed.title);
363
+ }
364
+ const diagramG = svg.append("g").attr("transform", `translate(${offsetX},${offsetY}) scale(${scale})`);
365
+ const arrowColors = /* @__PURE__ */ new Set();
366
+ const edgeColorMap = /* @__PURE__ */ new Map();
367
+ for (let i = 0; i < layout.edges.length; i++) {
368
+ const c = edgeColor(
369
+ // In-bounds by loop guard.
370
+ layout.edges[i],
371
+ parsed.tagGroups,
372
+ activeGroup,
373
+ palette
374
+ );
375
+ arrowColors.add(c);
376
+ edgeColorMap.set(i, c);
377
+ }
378
+ ensureArrowMarkers(defs, arrowColors);
379
+ const sortedGroups = [...layout.groups].sort(
380
+ (a, b) => b.width * b.height - a.width * a.height
381
+ );
382
+ const groupLabels = new Set(layout.groups.map((g) => g.label));
383
+ const hasSubGroups = /* @__PURE__ */ new Set();
384
+ for (const group of parsed.groups) {
385
+ for (const child of group.children) {
386
+ if (groupLabels.has(child)) hasSubGroups.add(group.label);
387
+ }
388
+ }
389
+ for (const group of sortedGroups) {
390
+ const gx = group.x - group.width / 2;
391
+ const needsExtra = !group.collapsed && hasSubGroups.has(group.label);
392
+ const gy = needsExtra ? group.y - group.height / 2 - sGroupLabelZone : group.y - group.height / 2;
393
+ const groupHeight = needsExtra ? group.height + sGroupLabelZone : group.height;
394
+ const groupG = diagramG.append("g").attr(
395
+ "class",
396
+ group.collapsed ? "bl-group bl-group-collapsed" : "bl-group"
397
+ ).attr("data-line-number", String(group.lineNumber)).attr("data-node-id", group.label).attr("data-group-toggle", group.label).style("cursor", "pointer");
398
+ if (group.collapsed) {
399
+ const fillColor = themeBaseBg(palette, isDark);
400
+ const strokeColor = palette.border;
401
+ groupG.append("rect").attr("x", gx).attr("y", gy).attr("width", group.width).attr("height", group.height).attr("rx", NODE_RX).attr("ry", NODE_RX).attr("fill", fillColor).attr("stroke", strokeColor).attr("stroke-width", sNodeStrokeWidth);
402
+ const clipId = `bl-clip-${group.label.replace(/[[\]\s]/g, "")}`;
403
+ groupG.append("clipPath").attr("id", clipId).append("rect").attr("x", gx).attr("y", gy).attr("width", group.width).attr("height", group.height).attr("rx", NODE_RX);
404
+ groupG.append("rect").attr("x", gx).attr("y", gy + group.height - sCollapseBarHeight).attr("width", group.width).attr("height", sCollapseBarHeight).attr("fill", strokeColor).attr("clip-path", `url(#${clipId})`).attr("class", "bl-collapse-bar");
405
+ const maxLabelLines = Math.max(
406
+ 2,
407
+ Math.floor((group.height - 16) / (sMinNodeFontSize * 1.3))
408
+ );
409
+ const fitted = fitLabelToHeader(group.label, group.width, maxLabelLines);
410
+ const lineH = fitted.fontSize * 1.3;
411
+ const totalH = fitted.lines.length * lineH;
412
+ for (let li = 0; li < fitted.lines.length; li++) {
413
+ groupG.append("text").attr("class", "bl-group-label").attr("x", group.x).attr("y", group.y - totalH / 2 + lineH / 2 + li * lineH).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-family", FONT_FAMILY).attr("font-size", fitted.fontSize).attr("font-weight", "600").attr("fill", palette.text).text(fitted.lines[li]);
414
+ }
415
+ } else {
416
+ groupG.append("rect").attr("x", gx).attr("y", gy).attr("width", group.width).attr("height", groupHeight).attr("rx", GROUP_RX).attr("ry", GROUP_RX).attr("fill", mix(palette.surface, palette.bg, 40)).attr("stroke", palette.textMuted).attr("stroke-width", 1).attr("stroke-opacity", 0.35);
417
+ groupG.append("text").attr("class", "bl-group-label").attr("x", gx + group.width / 2).attr("y", gy + 18).attr("text-anchor", "middle").attr("font-family", FONT_FAMILY).attr("font-size", sGroupLabelFontSize).attr("font-weight", "600").attr("fill", palette.text).text(group.label);
418
+ }
419
+ }
420
+ for (let i = 0; i < layout.edges.length; i++) {
421
+ const le = layout.edges[i];
422
+ const color = edgeColorMap.get(i) ?? palette.textMuted;
423
+ if (hidden.size > 0) {
424
+ let isHidden = false;
425
+ for (const [groupKey, hiddenVals] of hidden) {
426
+ const val = le.metadata[groupKey];
427
+ if (val && hiddenVals.has(val.toLowerCase())) {
428
+ isHidden = true;
429
+ break;
430
+ }
431
+ }
432
+ if (isHidden) continue;
433
+ }
434
+ if (le.source === le.target) {
435
+ const nodeLayout = layoutNodeMap.get(le.source);
436
+ if (nodeLayout) {
437
+ const edgeG2 = diagramG.append("g").attr("class", "bl-edge-group").attr("data-line-number", String(le.lineNumber)).attr("data-from", le.source).attr("data-to", le.target);
438
+ const markerId2 = `bl-arrow-${color.replace("#", "")}`;
439
+ const cx = nodeLayout.x;
440
+ const cy = nodeLayout.y;
441
+ const hw = nodeLayout.width / 2;
442
+ const hh = nodeLayout.height / 2;
443
+ const pad = 20;
444
+ const startX = cx + hw;
445
+ const startY = cy + hh * 0.4;
446
+ const endX = cx + hw * 0.4;
447
+ const endY = cy + hh;
448
+ const cp1x = startX + hw + pad;
449
+ const cp1y = startY;
450
+ const cp2x = endX;
451
+ const cp2y = endY + hh + pad;
452
+ edgeG2.append("path").attr("class", "bl-edge").attr(
453
+ "d",
454
+ `M ${startX} ${startY} C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${endX} ${endY}`
455
+ ).attr("fill", "none").attr("stroke", color).attr("stroke-width", sEdgeStrokeWidth).attr("marker-end", `url(#${markerId2})`);
456
+ }
457
+ continue;
458
+ }
459
+ let points;
460
+ if (le.yOffset !== 0 && le.parallelCount > 1) {
461
+ const srcLayout = layoutNodeMap.get(le.source);
462
+ const tgtLayout = layoutNodeMap.get(le.target);
463
+ const srcY = srcLayout?.y ?? le.points[0]?.y ?? 0;
464
+ const tgtY = tgtLayout?.y ?? le.points[le.points.length - 1]?.y ?? 0;
465
+ const srcX = le.points[0]?.x ?? 0;
466
+ const tgtX = le.points[le.points.length - 1]?.x ?? 0;
467
+ const midX = (srcX + tgtX) / 2;
468
+ const midY = (srcY + tgtY) / 2;
469
+ points = [
470
+ { x: srcX, y: srcY },
471
+ // port (bundled)
472
+ { x: srcX + (midX - srcX) * 0.3, y: srcY + le.yOffset * 0.5 },
473
+ // separate
474
+ { x: midX, y: midY + le.yOffset },
475
+ // full spread
476
+ { x: tgtX - (tgtX - midX) * 0.3, y: tgtY + le.yOffset * 0.5 },
477
+ // converge
478
+ { x: tgtX, y: tgtY }
479
+ // port (bundled)
480
+ ];
481
+ } else {
482
+ points = le.points.map((p) => ({ x: p.x, y: p.y }));
483
+ }
484
+ if (points.length < 2) continue;
485
+ const edgeG = diagramG.append("g").attr("class", "bl-edge-group").attr("data-line-number", String(le.lineNumber)).attr("data-from", le.source).attr("data-to", le.target);
486
+ const markerId = `bl-arrow-${color.replace("#", "")}`;
487
+ const gen = le.straight ? lineGeneratorStraight : parsed.direction === "TB" ? lineGeneratorTB : lineGeneratorLR;
488
+ const path = edgeG.append("path").attr("class", "bl-edge").attr("d", gen(points) ?? "").attr("fill", "none").attr("stroke", color).attr("stroke-width", sEdgeStrokeWidth).attr("marker-end", `url(#${markerId})`);
489
+ if (le.bidirectional) {
490
+ const revId = `bl-arrow-rev-${color.replace("#", "")}`;
491
+ path.attr("marker-start", `url(#${revId})`);
492
+ }
493
+ }
494
+ for (const ln of layout.nodes) {
495
+ const node = nodeMap.get(ln.label);
496
+ if (!node) continue;
497
+ if (hidden.size > 0) {
498
+ let isHidden = false;
499
+ for (const [groupKey, hiddenVals] of hidden) {
500
+ const val = node.metadata[groupKey];
501
+ if (val && hiddenVals.has(val.toLowerCase())) {
502
+ isHidden = true;
503
+ break;
504
+ }
505
+ }
506
+ if (isHidden) continue;
507
+ }
508
+ const solid = parsed.options["solid-fill"] === "on";
509
+ const colors = nodeColors(
510
+ node,
511
+ parsed.tagGroups,
512
+ activeGroup,
513
+ palette,
514
+ isDark,
515
+ {
516
+ active: activeIsValue,
517
+ hue: rampHue,
518
+ twoColor: rampLow !== void 0,
519
+ fillForValue
520
+ },
521
+ solid
522
+ );
523
+ const dividerStroke = solid ? colors.text : colors.stroke;
524
+ const nodeG = diagramG.append("g").attr("class", "bl-node").attr("transform", `translate(${ln.x},${ln.y})`).attr("data-line-number", node.lineNumber).attr("data-node-id", node.label).style("cursor", onClickItem ? "pointer" : "default").style("--bl-node-stroke", colors.stroke);
525
+ for (const [key, val] of Object.entries(node.metadata)) {
526
+ nodeG.attr(`data-tag-${key.toLowerCase()}`, val.toLowerCase());
527
+ }
528
+ if (node.value !== void 0) {
529
+ nodeG.attr("data-value", node.value);
530
+ }
531
+ if (onClickItem) {
532
+ nodeG.on("click", (event) => {
533
+ const target = event.target;
534
+ if (target?.closest("a")) return;
535
+ onClickItem(node.lineNumber);
536
+ });
537
+ }
538
+ const x = -ln.width / 2;
539
+ const y = -ln.height / 2;
540
+ nodeG.append("rect").attr("x", x).attr("y", y).attr("width", ln.width).attr("height", ln.height).attr("rx", NODE_RX).attr("ry", NODE_RX).attr("fill", colors.fill).attr("stroke", colors.stroke).attr("stroke-width", sNodeStrokeWidth);
541
+ const desc = node.description;
542
+ if (desc && desc.length > 0 && !hideDescriptions) {
543
+ const MAX_LABEL_LINES = 3;
544
+ const fitted = fitLabelToHeader(node.label, ln.width, MAX_LABEL_LINES);
545
+ const labelLines = fitted.lines;
546
+ const labelLineH = fitted.fontSize * 1.3;
547
+ const labelTotalH = labelLines.length * labelLineH;
548
+ const headerH = labelTotalH + 12;
549
+ const headerCenterY = -ln.height / 2 + headerH / 2;
550
+ for (let li = 0; li < labelLines.length; li++) {
551
+ nodeG.append("text").attr("x", 0).attr(
552
+ "y",
553
+ headerCenterY - labelTotalH / 2 + labelLineH / 2 + li * labelLineH
554
+ ).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", fitted.fontSize).attr("font-weight", "bold").attr("fill", colors.text).text(labelLines[li]);
555
+ }
556
+ const sepY = -ln.height / 2 + headerH;
557
+ nodeG.append("line").attr("x1", -ln.width / 2).attr("y1", sepY).attr("x2", ln.width / 2).attr("y2", sepY).attr("stroke", dividerStroke).attr("stroke-opacity", 0.3).attr("stroke-width", 1);
558
+ const descStartY = sepY + 4 + sDescFontSize;
559
+ const maxTextWidth = ln.width - NODE_TEXT_PADDING * 2;
560
+ const charsPerLine = Math.floor(
561
+ maxTextWidth / (sDescFontSize * CHAR_WIDTH_RATIO)
562
+ );
563
+ const descLineH = sDescFontSize * DESC_LINE_HEIGHT;
564
+ const displayLen = (text) => text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\*\*(.+?)\*\*/g, "$1").replace(/\*(.+?)\*/g, "$1").replace(/`(.+?)`/g, "$1").replace(/https?:\/\/\S+/g, (u) => u.slice(0, 20)).length;
565
+ const normalizedLines = [];
566
+ for (const descLine of desc) {
567
+ let normalized = descLine.startsWith("- ") ? "\u2022 " + descLine.slice(2) : descLine;
568
+ normalized = normalized.replace(
569
+ /\bhttps?\s+([\w][\w.-]+\.[a-z]{2,}(?:\/\S*)?)/gi,
570
+ (_, domain) => `https://${domain}`
571
+ );
572
+ normalizedLines.push(normalized);
573
+ }
574
+ const wrappedLinesShared = wrapDescriptionLines(
575
+ normalizedLines,
576
+ charsPerLine,
577
+ displayLen
578
+ );
579
+ const truncated = wrappedLinesShared.length > MAX_DESC_LINES;
580
+ const visibleLines = truncated ? wrappedLinesShared.slice(0, MAX_DESC_LINES) : wrappedLinesShared;
581
+ const BULLET_GLYPH_X = -ln.width / 2 + 6;
582
+ const BULLET_BODY_X = BULLET_GLYPH_X + 10;
583
+ const descColor = relativeLuminance(colors.fill) > 0.5 ? palette.textMuted : mix(colors.text, colors.fill, 75);
584
+ for (let li = 0; li < visibleLines.length; li++) {
585
+ const line2 = visibleLines[li];
586
+ let lineText = line2.text;
587
+ if (truncated && li === visibleLines.length - 1) {
588
+ lineText = measureText(lineText, sDescFontSize) >= maxTextWidth ? truncateText(lineText, sDescFontSize, maxTextWidth) : lineText + "\u2026";
589
+ }
590
+ const y2 = descStartY + li * descLineH;
591
+ if (line2.kind === "bullet-first") {
592
+ nodeG.append("text").attr("x", BULLET_GLYPH_X).attr("y", y2).attr("text-anchor", "start").attr("dominant-baseline", "central").attr("font-size", sDescFontSize).attr("fill", descColor).text("\u2022");
593
+ }
594
+ const isBullet = line2.kind === "bullet-first" || line2.kind === "bullet-cont";
595
+ const textEl = nodeG.append("text").attr("x", isBullet ? BULLET_BODY_X : 0).attr("y", y2).attr("text-anchor", isBullet ? "start" : "middle").attr("dominant-baseline", "central").attr("font-size", DESC_FONT_SIZE).attr("fill", descColor);
596
+ renderInlineText(textEl, lineText, palette, sDescFontSize);
597
+ }
598
+ if (truncated) {
599
+ const fullText = desc.join(" ");
600
+ const tooltipText = fullText.length > 200 ? fullText.slice(0, 199) + "\u2026" : fullText;
601
+ nodeG.append("title").text(tooltipText);
602
+ }
603
+ if (parsed.showValues && node.value !== void 0) {
604
+ const valueLabel = parsed.boxMetric ? `${parsed.boxMetric}: ${node.value}` : String(node.value);
605
+ nodeG.append("text").attr("class", "bl-node-value").attr("x", 0).attr("y", descStartY + visibleLines.length * descLineH).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", VALUE_FONT_SIZE).attr("font-weight", "600").attr("fill", colors.text).text(valueLabel);
606
+ }
607
+ } else if (parsed.showValues && node.value !== void 0) {
608
+ const valueLabel = parsed.boxMetric ? `${parsed.boxMetric}: ${node.value}` : String(node.value);
609
+ const headerH = ln.height / 2;
610
+ const sepY = -ln.height / 2 + headerH;
611
+ const fitted = fitLabelToHeader(node.label, ln.width, 2);
612
+ const labelLineH = fitted.fontSize * 1.3;
613
+ const labelTotalH = fitted.lines.length * labelLineH;
614
+ const headerCenterY = -ln.height / 2 + headerH / 2;
615
+ for (let li = 0; li < fitted.lines.length; li++) {
616
+ nodeG.append("text").attr("x", 0).attr(
617
+ "y",
618
+ headerCenterY - labelTotalH / 2 + labelLineH / 2 + li * labelLineH
619
+ ).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", fitted.fontSize).attr("font-weight", "bold").attr("fill", colors.text).text(fitted.lines[li]);
620
+ }
621
+ nodeG.append("line").attr("x1", -ln.width / 2).attr("y1", sepY).attr("x2", ln.width / 2).attr("y2", sepY).attr("stroke", dividerStroke).attr("stroke-opacity", 0.3).attr("stroke-width", 1);
622
+ nodeG.append("text").attr("class", "bl-node-value").attr("x", 0).attr("y", (sepY + ln.height / 2) / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", VALUE_FONT_SIZE).attr("fill", colors.text).attr("opacity", 0.85).text(valueLabel);
623
+ } else {
624
+ const maxLabelLines = Math.max(
625
+ 2,
626
+ Math.floor((ln.height - 16) / (sMinNodeFontSize * 1.3))
627
+ );
628
+ const fitted = fitLabelToHeader(node.label, ln.width, maxLabelLines);
629
+ const lineH = fitted.fontSize * 1.3;
630
+ const totalH = fitted.lines.length * lineH;
631
+ for (let li = 0; li < fitted.lines.length; li++) {
632
+ nodeG.append("text").attr("x", 0).attr("y", -totalH / 2 + lineH / 2 + li * lineH).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", fitted.fontSize).attr("font-weight", "600").attr("fill", colors.text).text(fitted.lines[li]);
633
+ }
634
+ }
635
+ if (ln.note) {
636
+ if (ln.note.collapsed) {
637
+ renderNoteBadge(
638
+ nodeG,
639
+ {
640
+ x: ln.width / 2 - NOTE_BADGE_RADIUS - 3,
641
+ y: -ln.height / 2 + NOTE_BADGE_RADIUS + 3
642
+ },
643
+ palette,
644
+ {
645
+ isDark,
646
+ ...ln.note.color && { color: ln.note.color },
647
+ lineNumber: ln.note.lineNumber,
648
+ endLineNumber: ln.note.endLineNumber
649
+ }
650
+ );
651
+ } else {
652
+ const [cx1, cy1, cx2, cy2] = noteConnectorPoints(
653
+ { width: ln.width, height: ln.height },
654
+ ln.note
655
+ );
656
+ renderNoteConnector(nodeG, cx1, cy1, cx2, cy2, palette);
657
+ renderNoteBox(
658
+ nodeG,
659
+ {
660
+ x: ln.note.x,
661
+ y: ln.note.y,
662
+ width: ln.note.width,
663
+ height: ln.note.height
664
+ },
665
+ ln.note.lines,
666
+ palette,
667
+ {
668
+ isDark,
669
+ ...ln.note.color && { color: ln.note.color },
670
+ lineNumber: ln.note.lineNumber,
671
+ endLineNumber: ln.note.endLineNumber,
672
+ interactive: true
673
+ }
674
+ );
675
+ }
676
+ }
677
+ }
678
+ const labelLayer = diagramG.append("g").attr("class", "bl-edge-labels");
679
+ const labelLineHeight = sEdgeLabelFontSize * 1.3;
680
+ for (const le of layout.edges) {
681
+ if (!le.labelLines || le.labelLines.length === 0) continue;
682
+ if (le.labelX === void 0 || le.labelY === void 0) continue;
683
+ const lx = le.labelX;
684
+ const ly = le.labelY;
685
+ if (hidden.size > 0) {
686
+ let isHidden = false;
687
+ for (const [groupKey, hiddenVals] of hidden) {
688
+ const val = le.metadata[groupKey];
689
+ if (val && hiddenVals.has(val.toLowerCase())) {
690
+ isHidden = true;
691
+ break;
692
+ }
693
+ }
694
+ if (isHidden) continue;
695
+ }
696
+ const lw = le.labelWidth ?? 0;
697
+ const lh = le.labelHeight ?? sEdgeLabelFontSize + 6;
698
+ const lines = le.labelLines;
699
+ const labelG = labelLayer.append("g").attr("class", "bl-edge-label").attr("data-line-number", String(le.lineNumber));
700
+ labelG.append("rect").attr("x", lx - lw / 2).attr("y", ly - lh / 2).attr("width", lw).attr("height", lh).attr("rx", 3).attr("fill", palette.bg).attr("opacity", 0.72);
701
+ const text = labelG.append("text").attr("x", lx).attr("text-anchor", "middle").attr("font-family", FONT_FAMILY).attr("font-size", sEdgeLabelFontSize).attr("fill", palette.textMuted);
702
+ const firstY = ly - (lines.length - 1) / 2 * labelLineHeight + sEdgeLabelFontSize / 3;
703
+ lines.forEach((line2, k) => {
704
+ text.append("tspan").attr("x", lx).attr("y", firstY + k * labelLineHeight).text(line2);
705
+ });
706
+ }
707
+ const hasDescriptions = parsed.nodes.some(
708
+ (n) => n.description && n.description.length > 0
709
+ );
710
+ const hasLegend = legendGroups.length > 0 || hasDescriptions && controlsHost !== "app";
711
+ if (hasLegend) {
712
+ let controlsGroup;
713
+ if (hasDescriptions && (onToggleDescriptions || controlsHost === "app")) {
714
+ controlsGroup = {
715
+ toggles: [
716
+ {
717
+ id: "descriptions",
718
+ type: "toggle",
719
+ label: "Descriptions",
720
+ active: !hideDescriptions,
721
+ onToggle: () => {
722
+ }
723
+ }
724
+ ]
725
+ };
726
+ }
727
+ const legendCallbacks = {
728
+ ...onToggleControlsExpand !== void 0 && {
729
+ onControlsExpand: onToggleControlsExpand
730
+ },
731
+ onControlsToggle: (toggleId, active) => {
732
+ if (toggleId === "descriptions" && onToggleDescriptions) {
733
+ onToggleDescriptions(active);
734
+ }
735
+ }
736
+ };
737
+ const legendG = svg.append("g").attr("transform", `translate(0,${titleOffset + 4})`);
738
+ renderIntegratedLegend(legendG, {
739
+ groups: legendGroups,
740
+ activeGroup,
741
+ mode: exportMode ? "export" : "preview",
742
+ // Keep inactive sibling tag groups visible as collapsed pills so the user
743
+ // can click one to flip the active colouring dimension (preview only).
744
+ showInactivePills: true,
745
+ ...controlsGroup !== void 0 && { controlsGroup },
746
+ ...controlsHost !== void 0 && { controlsHost },
747
+ ...controlsExpanded !== void 0 && { controlsExpanded },
748
+ callbacks: legendCallbacks,
749
+ palette,
750
+ isDark,
751
+ width
752
+ });
753
+ legendG.selectAll("[data-legend-group]").classed("bl-legend-group", true);
754
+ }
755
+ if (!exportDims && !exportMode) {
756
+ const iconSize = 14;
757
+ const focusG = svg.append("g").attr("class", "bl-focus-icon").attr("data-export-ignore", "true").style("display", "none").style("pointer-events", "auto").style("cursor", "pointer");
758
+ focusG.append("rect").attr("x", -3).attr("y", -3).attr("width", iconSize + 6).attr("height", iconSize + 6).attr("fill", "transparent");
759
+ const cx = iconSize / 2;
760
+ const cy = iconSize / 2;
761
+ focusG.append("circle").attr("cx", cx).attr("cy", cy).attr("r", iconSize / 2 - 1).attr("fill", palette.bg).attr("stroke", palette.textMuted).attr("stroke-width", 1.5);
762
+ focusG.append("circle").attr("cx", cx).attr("cy", cy).attr("r", 2).attr("fill", palette.textMuted);
763
+ }
764
+ }
765
+ function renderBoxesAndLinesForExport(container, parsed, layout, palette, isDark, options) {
766
+ renderBoxesAndLines(container, parsed, layout, palette, isDark, {
767
+ ...options?.exportDims !== void 0 && {
768
+ exportDims: options.exportDims
769
+ },
770
+ ...options?.activeTagGroup !== void 0 && {
771
+ activeTagGroup: options.activeTagGroup
772
+ },
773
+ ...options?.hiddenTagValues !== void 0 && {
774
+ hiddenTagValues: options.hiddenTagValues
775
+ },
776
+ ...options?.exportMode !== void 0 && {
777
+ exportMode: options.exportMode
778
+ }
779
+ });
780
+ }
781
+
782
+ export {
783
+ renderBoxesAndLines,
784
+ renderBoxesAndLinesForExport
785
+ };
786
+ //# sourceMappingURL=chunk-2LACINWN.js.map