@diagrammo/dgmo 0.46.0 → 0.48.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 (534) hide show
  1. package/dist/advanced.d.ts +3 -855
  2. package/dist/advanced.js +541 -70107
  3. package/dist/analyzer-UYKBXAKR.js +15 -0
  4. package/dist/auto.js +1 -1
  5. package/dist/auto.mjs +1 -1
  6. package/dist/block.js +83 -69417
  7. package/dist/calculator-LDYQCDDY.js +11 -0
  8. package/dist/charts-d3-QOZHKTCT.js +25 -0
  9. package/dist/chunk-2DXMZRWJ.js +800 -0
  10. package/dist/chunk-2LACINWN.js +786 -0
  11. package/dist/chunk-33LCUH4H.js +893 -0
  12. package/dist/chunk-3CIOJ5YJ.js +35 -0
  13. package/dist/chunk-3HDBAXF5.js +86 -0
  14. package/dist/chunk-3JLKYGBQ.js +290 -0
  15. package/dist/chunk-3MQQNXPD.js +415 -0
  16. package/dist/chunk-3R5POCTZ.js +967 -0
  17. package/dist/chunk-3SMGIJDD.js +112 -0
  18. package/dist/chunk-44CM72SQ.js +409 -0
  19. package/dist/chunk-4GRI7MEB.js +1604 -0
  20. package/dist/chunk-4OHACBGX.js +606 -0
  21. package/dist/chunk-5DH3KYFH.js +21 -0
  22. package/dist/chunk-5MLYVTYM.js +76 -0
  23. package/dist/chunk-5QXL4NYS.js +553 -0
  24. package/dist/chunk-5WRI5ZAA.js +31 -0
  25. package/dist/chunk-6EK55ART.js +117 -0
  26. package/dist/chunk-6FZYCKXB.js +2065 -0
  27. package/dist/chunk-6HO5ZTXK.js +277 -0
  28. package/dist/chunk-6KFWC4CO.js +7 -0
  29. package/dist/chunk-6MTWIQVO.js +121 -0
  30. package/dist/chunk-6NYWYNBI.js +329 -0
  31. package/dist/chunk-6SM5NJQH.js +620 -0
  32. package/dist/chunk-725LDASD.js +422 -0
  33. package/dist/chunk-76OWZ3BA.js +130 -0
  34. package/{src/block/parser.ts → dist/chunk-7JEGTRLD.js} +100 -189
  35. package/dist/chunk-A44USUV7.js +923 -0
  36. package/dist/chunk-A4GOY3PE.js +25 -0
  37. package/dist/chunk-A4VFQH5G.js +251 -0
  38. package/dist/chunk-AELOBFW5.js +375 -0
  39. package/dist/chunk-AN2MOO5T.js +522 -0
  40. package/dist/chunk-BMR4CIEO.js +41 -0
  41. package/dist/chunk-BS7SV7C3.js +51 -0
  42. package/dist/chunk-BUPDIHML.js +613 -0
  43. package/dist/chunk-BUVJFRPY.js +887 -0
  44. package/dist/chunk-C3Z4DX3V.js +112 -0
  45. package/dist/chunk-CI2G3OEB.js +1818 -0
  46. package/dist/chunk-CKNVO24D.js +54 -0
  47. package/dist/chunk-CUVHQHZ7.js +391 -0
  48. package/dist/chunk-CXK4DZH3.js +114 -0
  49. package/dist/{editor.cjs → chunk-DKEPS5LH.js} +14 -225
  50. package/dist/chunk-DU7W62N5.js +487 -0
  51. package/dist/chunk-DZAQBW35.js +751 -0
  52. package/dist/chunk-EB6FPQUP.js +316 -0
  53. package/dist/chunk-EMS2SK26.js +376 -0
  54. package/dist/chunk-ERLBPDGU.js +510 -0
  55. package/dist/chunk-EVCVDTXE.js +732 -0
  56. package/dist/chunk-FCIIHT24.js +384 -0
  57. package/dist/chunk-FFUN5D66.js +97 -0
  58. package/dist/chunk-FITOLXSX.js +714 -0
  59. package/dist/chunk-FMDSF3OV.js +68 -0
  60. package/dist/chunk-FNB3PNGF.js +410 -0
  61. package/dist/chunk-G2H74XJZ.js +2104 -0
  62. package/dist/chunk-GMR5MFHJ.js +339 -0
  63. package/dist/chunk-H6PI3KJT.js +305 -0
  64. package/dist/chunk-I46X5VUA.js +59 -0
  65. package/dist/chunk-I5IGB56C.js +255 -0
  66. package/dist/chunk-IH6ZMVQ5.js +3950 -0
  67. package/dist/chunk-IJZFN3HM.js +463 -0
  68. package/dist/chunk-IPWPWM6R.js +3079 -0
  69. package/dist/chunk-IYULMRBE.js +23 -0
  70. package/dist/chunk-IZMRON6C.js +167 -0
  71. package/dist/chunk-J5FIXQRT.js +813 -0
  72. package/dist/chunk-JED6ZDEE.js +73 -0
  73. package/dist/chunk-JEUJOX5X.js +136 -0
  74. package/dist/chunk-JM4LEPY3.js +436 -0
  75. package/dist/chunk-L3UHP6TD.js +101 -0
  76. package/dist/chunk-L66GEFB6.js +524 -0
  77. package/dist/chunk-LIOHW36Z.js +469 -0
  78. package/dist/chunk-LP7EC4MI.js +16 -0
  79. package/dist/chunk-MITI6NOL.js +548 -0
  80. package/dist/chunk-MO5YHHMQ.js +168 -0
  81. package/dist/chunk-MZSBFAPJ.js +885 -0
  82. package/dist/chunk-NNAAJVBU.js +9 -0
  83. package/dist/chunk-NOTA6TEI.js +47 -0
  84. package/dist/chunk-NURGB5Z4.js +335 -0
  85. package/dist/chunk-NX4EQS3U.js +1062 -0
  86. package/dist/chunk-NXMESY4J.js +1 -0
  87. package/dist/chunk-OJR3MQYV.js +1458 -0
  88. package/dist/chunk-OURHMODR.js +454 -0
  89. package/dist/chunk-P235SVAH.js +1057 -0
  90. package/dist/chunk-P4RH4OSP.js +1038 -0
  91. package/dist/chunk-P6T66CCZ.js +226 -0
  92. package/dist/chunk-PBAHEPLQ.js +223 -0
  93. package/dist/chunk-PBHE5UG6.js +1530 -0
  94. package/dist/chunk-PDKUANDZ.js +35 -0
  95. package/dist/chunk-PWIHZ6NJ.js +296 -0
  96. package/{src/c4/layout.ts → dist/chunk-QEBUY5NY.js} +384 -1140
  97. package/dist/chunk-QLCCJEJI.js +253 -0
  98. package/dist/chunk-QOOUC2J5.js +52 -0
  99. package/dist/chunk-QYTPPDAO.js +402 -0
  100. package/dist/chunk-R3XI2VC7.js +801 -0
  101. package/dist/chunk-RCERWOJR.js +154 -0
  102. package/dist/chunk-RJE5JTVK.js +362 -0
  103. package/dist/chunk-RTLBSQ3G.js +1155 -0
  104. package/dist/chunk-RUNNI6VE.js +448 -0
  105. package/dist/chunk-RVMRZAPB.js +354 -0
  106. package/dist/chunk-RYEGFMUO.js +955 -0
  107. package/dist/chunk-RZCF2VVM.js +528 -0
  108. package/dist/chunk-SML67VPV.js +1025 -0
  109. package/dist/chunk-SQ7IY53Q.js +44 -0
  110. package/dist/chunk-SQAG3UXH.js +161 -0
  111. package/dist/chunk-SRTU6HMQ.js +1851 -0
  112. package/dist/chunk-T2OE3YQB.js +550 -0
  113. package/dist/chunk-T3UBLACL.js +299 -0
  114. package/dist/chunk-TLVIHFRO.js +296 -0
  115. package/dist/chunk-TYS6CSS5.js +52 -0
  116. package/dist/chunk-U6EQI3U6.js +130 -0
  117. package/dist/chunk-UJ4FJLBF.js +86 -0
  118. package/dist/chunk-ULOEATBZ.js +347 -0
  119. package/dist/chunk-UNCCZ3NU.js +127 -0
  120. package/dist/chunk-USZW2HHU.js +197 -0
  121. package/dist/chunk-UWN33VNX.js +1610 -0
  122. package/dist/chunk-UZ7EZW3B.js +40 -0
  123. package/dist/chunk-VOKGH627.js +37 -0
  124. package/dist/chunk-WBRZJOEC.js +204 -0
  125. package/dist/chunk-WCSOJFU3.js +380 -0
  126. package/dist/chunk-WCT3NYCV.js +160 -0
  127. package/dist/chunk-WVAJFAIA.js +529 -0
  128. package/dist/chunk-X5LCYCL5.js +394 -0
  129. package/dist/chunk-XAKEUMEJ.js +1256 -0
  130. package/dist/chunk-Y2ZE3MSG.js +590 -0
  131. package/dist/chunk-Y3J7VR52.js +646 -0
  132. package/dist/chunk-Y5QGVTVC.js +33 -0
  133. package/dist/chunk-YAA6IID3.js +548 -0
  134. package/dist/chunk-YGJTDXVR.js +692 -0
  135. package/{src/utils/time-ticks.ts → dist/chunk-YRYKTYMQ.js} +45 -112
  136. package/dist/chunk-YYVTBZZC.js +61 -0
  137. package/dist/chunk-Z5CGSFJB.js +647 -0
  138. package/{src/gantt/calculator.ts → dist/chunk-Z72XRXD4.js} +236 -428
  139. package/dist/chunk-ZGR7ULFJ.js +978 -0
  140. package/dist/chunk-ZJEL4H2R.js +851 -0
  141. package/dist/chunk-ZO27FGJS.js +554 -0
  142. package/dist/collapse-CLPJD2RL.js +11 -0
  143. package/dist/collapse-SLINJQW6.js +9 -0
  144. package/dist/collapse-ZWUFFCK2.js +9 -0
  145. package/dist/compute-G55RG53N.js +8 -0
  146. package/dist/dgmo-router-DZCIDUO5.js +73 -0
  147. package/dist/dimensions-J3FHIMJ6.js +22 -0
  148. package/dist/element.js +1 -1
  149. package/dist/element.mjs +1 -1
  150. package/dist/flowchart-parser-ZNSKIBVR.js +21 -0
  151. package/dist/flowchart-renderer-ZAEOL4DX.js +35 -0
  152. package/dist/index.d.ts +3 -469
  153. package/dist/index.js +137 -69685
  154. package/dist/layout-46N4JJYK.js +17 -0
  155. package/dist/layout-5RA6QWR6.js +26 -0
  156. package/dist/layout-5T6RH5M6.js +14 -0
  157. package/dist/layout-7QXCNOVS.js +13 -0
  158. package/dist/layout-EE6GE46Y.js +17 -0
  159. package/dist/layout-G23F7LDH.js +9 -0
  160. package/dist/layout-HRBZPFCN.js +23 -0
  161. package/dist/layout-HWDSRX5E.js +20 -0
  162. package/dist/layout-HYZPWAK5.js +18 -0
  163. package/dist/layout-N76NR4ZO.js +19 -0
  164. package/dist/layout-OB3RI63S.js +14 -0
  165. package/dist/layout-S2QBYSK2.js +15 -0
  166. package/dist/layout-UM5F4DJB.js +15 -0
  167. package/dist/layout-search-OYV77HHS.js +1655 -0
  168. package/dist/load-data-EHDDZ5ZS.js +8 -0
  169. package/dist/palettes-PRJ2RI6D.js +56 -0
  170. package/dist/parser-2MFP2H2E.js +11 -0
  171. package/dist/parser-3STE62GB.js +21 -0
  172. package/dist/parser-4FL2CMDG.js +18 -0
  173. package/dist/parser-4OGOBJJ4.js +13 -0
  174. package/dist/parser-6BTRLKRZ.js +15 -0
  175. package/dist/parser-AQETEVRW.js +21 -0
  176. package/dist/parser-CFZD5IBL.js +12 -0
  177. package/dist/parser-FUSKFGZN.js +16 -0
  178. package/dist/parser-I6KXEYFK.js +13 -0
  179. package/dist/parser-IXSZZQHY.js +18 -0
  180. package/dist/parser-JAL2HD4I.js +13 -0
  181. package/dist/parser-KNSQSKQO.js +20 -0
  182. package/dist/parser-N5O4IHUN.js +13 -0
  183. package/dist/parser-NGU3ETO6.js +16 -0
  184. package/dist/parser-NRAI6O25.js +19 -0
  185. package/dist/parser-O5J4VBII.js +11 -0
  186. package/dist/parser-OV7NOBW6.js +21 -0
  187. package/dist/parser-PQ3POX45.js +23 -0
  188. package/dist/parser-TFZFBVWN.js +11 -0
  189. package/dist/parser-TRTHDFPP.js +18 -0
  190. package/dist/parser-VQP2UJ6W.js +11 -0
  191. package/dist/parser-XDVELHRK.js +13 -0
  192. package/dist/parser-XKRP7QKP.js +11 -0
  193. package/dist/parser-Y6WTBU76.js +14 -0
  194. package/dist/parser-ZSDPZYQP.js +12 -0
  195. package/dist/renderer-2XQQQ6QR.js +28 -0
  196. package/dist/renderer-5NDVNDAZ.js +26 -0
  197. package/dist/renderer-6EEZHMH4.js +26 -0
  198. package/dist/renderer-6W5462RB.js +43 -0
  199. package/dist/renderer-7MVLWWQD.js +22 -0
  200. package/dist/renderer-AWWAF6XD.js +33 -0
  201. package/dist/renderer-BYCNMBF7.js +34 -0
  202. package/dist/renderer-CLJHQPBJ.js +29 -0
  203. package/dist/renderer-GHPZMFU2.js +15 -0
  204. package/dist/renderer-JGBKUCZD.js +16 -0
  205. package/dist/renderer-KQB7UGDS.js +19 -0
  206. package/dist/renderer-PGX2TKVS.js +24 -0
  207. package/dist/renderer-QIWVHWZO.js +15 -0
  208. package/dist/renderer-RPOA5YPZ.js +35 -0
  209. package/dist/renderer-SMIBCRVB.js +24 -0
  210. package/dist/renderer-T7YKC5VP.js +28 -0
  211. package/dist/renderer-TOCOVLHS.js +37 -0
  212. package/dist/renderer-UWA5XS74.js +39 -0
  213. package/dist/renderer-VACVQYML.js +25 -0
  214. package/dist/renderer-W4M6QLIJ.js +37 -0
  215. package/dist/renderer-WZRREQ2P.js +23 -0
  216. package/dist/renderer-YY2OSF6E.js +27 -0
  217. package/dist/renderer-Z7I3W2PP.js +24 -0
  218. package/dist/renderer-ZGNTUWC4.js +22 -0
  219. package/dist/renderer-ZTR3DER3.js +19 -0
  220. package/dist/renderer-radial-C32GPJQ7.js +21 -0
  221. package/dist/resolver-NOAA7ID7.js +13 -0
  222. package/dist/state-collapse-HCGD6IVJ.js +8 -0
  223. package/dist/state-parser-Z2V23ATR.js +18 -0
  224. package/dist/state-renderer-RFYHSP4K.js +34 -0
  225. package/dist/themes-1-CuKpeH.d.ts +908 -0
  226. package/package.json +19 -70
  227. package/dist/advanced.cjs +0 -71648
  228. package/dist/advanced.d.cts +0 -6571
  229. package/dist/auto.cjs +0 -70648
  230. package/dist/auto.d.cts +0 -54
  231. package/dist/block.cjs +0 -70208
  232. package/dist/block.d.cts +0 -115
  233. package/dist/editor.d.cts +0 -26
  234. package/dist/element.cjs +0 -70530
  235. package/dist/element.d.cts +0 -62
  236. package/dist/highlight.cjs +0 -852
  237. package/dist/highlight.d.cts +0 -49
  238. package/dist/index.cjs +0 -70491
  239. package/dist/index.d.cts +0 -626
  240. package/dist/pert.cjs +0 -325
  241. package/dist/pert.d.cts +0 -559
  242. package/src/advanced.ts +0 -873
  243. package/src/arc/parser.ts +0 -15
  244. package/src/arc/renderer.ts +0 -633
  245. package/src/auto/index.ts +0 -621
  246. package/src/auto/shared.ts +0 -370
  247. package/src/auto/styles.ts +0 -73
  248. package/src/block/layout.ts +0 -198
  249. package/src/block/renderer.ts +0 -484
  250. package/src/block/types.ts +0 -67
  251. package/src/boxes-and-lines/collapse.ts +0 -96
  252. package/src/boxes-and-lines/focus.ts +0 -257
  253. package/src/boxes-and-lines/label-placement.ts +0 -339
  254. package/src/boxes-and-lines/layout-grouped.ts +0 -912
  255. package/src/boxes-and-lines/layout-layered.ts +0 -722
  256. package/src/boxes-and-lines/layout-search.ts +0 -1678
  257. package/src/boxes-and-lines/layout.ts +0 -398
  258. package/src/boxes-and-lines/parser.ts +0 -1181
  259. package/src/boxes-and-lines/renderer.ts +0 -1465
  260. package/src/boxes-and-lines/types.ts +0 -66
  261. package/src/c4/parser.ts +0 -1158
  262. package/src/c4/renderer.ts +0 -2008
  263. package/src/c4/types.ts +0 -89
  264. package/src/chart-type-registry.ts +0 -529
  265. package/src/chart-types.ts +0 -225
  266. package/src/chart.ts +0 -685
  267. package/src/charts-d3/bar.ts +0 -338
  268. package/src/charts-d3/chord.ts +0 -140
  269. package/src/charts-d3/function.ts +0 -147
  270. package/src/charts-d3/funnel.ts +0 -104
  271. package/src/charts-d3/heatmap.ts +0 -145
  272. package/src/charts-d3/index.ts +0 -337
  273. package/src/charts-d3/interactions.ts +0 -477
  274. package/src/charts-d3/line.ts +0 -344
  275. package/src/charts-d3/mount.ts +0 -151
  276. package/src/charts-d3/pie.ts +0 -117
  277. package/src/charts-d3/polar.ts +0 -89
  278. package/src/charts-d3/radar.ts +0 -0
  279. package/src/charts-d3/sankey.ts +0 -0
  280. package/src/charts-d3/scatter.ts +0 -185
  281. package/src/charts-d3/shared.ts +0 -208
  282. package/src/class/layout.ts +0 -326
  283. package/src/class/parser.ts +0 -635
  284. package/src/class/renderer.ts +0 -793
  285. package/src/class/types.ts +0 -58
  286. package/src/cli-banner.ts +0 -106
  287. package/src/cli.ts +0 -1080
  288. package/src/colors-diagnostics.ts +0 -35
  289. package/src/colors.ts +0 -379
  290. package/src/completion-types.ts +0 -27
  291. package/src/completion.ts +0 -2668
  292. package/src/cycle/layout.ts +0 -1026
  293. package/src/cycle/parser.ts +0 -387
  294. package/src/cycle/renderer.ts +0 -653
  295. package/src/cycle/types.ts +0 -101
  296. package/src/d3.ts +0 -1434
  297. package/src/data-chart-parser.ts +0 -1007
  298. package/src/dgmo-router.ts +0 -296
  299. package/src/diagnostics-registry.ts +0 -108
  300. package/src/diagnostics.ts +0 -0
  301. package/src/dimensions.ts +0 -20
  302. package/src/directives-registry.ts +0 -396
  303. package/src/editor/dgmo.grammar +0 -76
  304. package/src/editor/dgmo.grammar.d.ts +0 -2
  305. package/src/editor/dgmo.grammar.js +0 -18
  306. package/src/editor/dgmo.grammar.terms.d.ts +0 -5
  307. package/src/editor/dgmo.grammar.terms.js +0 -35
  308. package/src/editor/highlight-api.ts +0 -596
  309. package/src/editor/highlight.ts +0 -36
  310. package/src/editor/index.ts +0 -27
  311. package/src/editor/keywords.ts +0 -128
  312. package/src/editor/tokens.ts +0 -30
  313. package/src/element/index.ts +0 -432
  314. package/src/embed/css.ts +0 -312
  315. package/src/embed/escape.ts +0 -15
  316. package/src/embed/index.ts +0 -280
  317. package/src/er/classify.ts +0 -228
  318. package/src/er/layout.ts +0 -530
  319. package/src/er/parser.ts +0 -750
  320. package/src/er/renderer.ts +0 -725
  321. package/src/er/types.ts +0 -52
  322. package/src/error-card.ts +0 -269
  323. package/src/event-line/diagnostics.ts +0 -117
  324. package/src/event-line/parser.ts +0 -643
  325. package/src/event-line/renderer.ts +0 -1742
  326. package/src/event-line/types.ts +0 -74
  327. package/src/fonts.ts +0 -3
  328. package/src/gantt/parser.ts +0 -1641
  329. package/src/gantt/renderer.ts +0 -3787
  330. package/src/gantt/resolver.ts +0 -172
  331. package/src/gantt/types.ts +0 -206
  332. package/src/graph/diagnostics.ts +0 -64
  333. package/src/graph/edge-spline.ts +0 -29
  334. package/src/graph/flowchart-parser.ts +0 -759
  335. package/src/graph/flowchart-renderer.ts +0 -817
  336. package/src/graph/layout.ts +0 -472
  337. package/src/graph/notes.ts +0 -21
  338. package/src/graph/state-collapse.ts +0 -78
  339. package/src/graph/state-parser.ts +0 -574
  340. package/src/graph/state-renderer.ts +0 -653
  341. package/src/graph/types.ts +0 -65
  342. package/src/index.ts +0 -261
  343. package/src/infra/compute.ts +0 -1385
  344. package/src/infra/layout.ts +0 -888
  345. package/src/infra/parser.ts +0 -1234
  346. package/src/infra/renderer.ts +0 -2534
  347. package/src/infra/roles.ts +0 -75
  348. package/src/infra/types.ts +0 -216
  349. package/src/infra/validation.ts +0 -201
  350. package/src/journey-map/layout.ts +0 -490
  351. package/src/journey-map/parser.ts +0 -557
  352. package/src/journey-map/renderer.ts +0 -1580
  353. package/src/journey-map/types.ts +0 -47
  354. package/src/kanban/mutations.ts +0 -175
  355. package/src/kanban/parser.ts +0 -509
  356. package/src/kanban/renderer.ts +0 -1449
  357. package/src/kanban/types.ts +0 -39
  358. package/src/label-layout.ts +0 -368
  359. package/src/map/colorize.ts +0 -54
  360. package/src/map/completion.ts +0 -316
  361. package/src/map/context-labels.ts +0 -609
  362. package/src/map/data/PROVENANCE.json +0 -1
  363. package/src/map/data/README.md +0 -40
  364. package/src/map/data/airport-collisions.json +0 -1
  365. package/src/map/data/airports.json +0 -1
  366. package/src/map/data/gazetteer.json +0 -1
  367. package/src/map/data/lakes.json +0 -1
  368. package/src/map/data/mountain-ranges.json +0 -1
  369. package/src/map/data/na-lakes.json +0 -1
  370. package/src/map/data/na-land.json +0 -1
  371. package/src/map/data/region-names.json +0 -1
  372. package/src/map/data/rivers.json +0 -1
  373. package/src/map/data/types.ts +0 -121
  374. package/src/map/data/us-states.json +0 -1
  375. package/src/map/data/water-bodies.json +0 -1
  376. package/src/map/data/world-coarse.json +0 -1
  377. package/src/map/data/world-detail.json +0 -1
  378. package/src/map/diagnostics.ts +0 -134
  379. package/src/map/dimensions.ts +0 -150
  380. package/src/map/geo-query.ts +0 -295
  381. package/src/map/geo.ts +0 -438
  382. package/src/map/invert.ts +0 -111
  383. package/src/map/layout.ts +0 -5069
  384. package/src/map/legend-band.ts +0 -127
  385. package/src/map/load-data.ts +0 -186
  386. package/src/map/parser.ts +0 -783
  387. package/src/map/renderer.ts +0 -1445
  388. package/src/map/resolved-types.ts +0 -160
  389. package/src/map/resolver.ts +0 -1146
  390. package/src/map/types.ts +0 -194
  391. package/src/mindmap/collapse.ts +0 -67
  392. package/src/mindmap/layout.ts +0 -649
  393. package/src/mindmap/parser.ts +0 -430
  394. package/src/mindmap/renderer.ts +0 -637
  395. package/src/mindmap/text-wrap.ts +0 -218
  396. package/src/mindmap/types.ts +0 -56
  397. package/src/org/collapse.ts +0 -151
  398. package/src/org/layout.ts +0 -1262
  399. package/src/org/parser.ts +0 -519
  400. package/src/org/renderer.ts +0 -866
  401. package/src/org/resolver.ts +0 -636
  402. package/src/palettes/atlas.ts +0 -77
  403. package/src/palettes/blueprint.ts +0 -73
  404. package/src/palettes/catppuccin.ts +0 -84
  405. package/src/palettes/color-utils.ts +0 -443
  406. package/src/palettes/index.ts +0 -67
  407. package/src/palettes/nord.ts +0 -67
  408. package/src/palettes/registry.ts +0 -114
  409. package/src/palettes/slate.ts +0 -73
  410. package/src/palettes/tidewater.ts +0 -73
  411. package/src/palettes/tokyo-night.ts +0 -86
  412. package/src/palettes/types.ts +0 -88
  413. package/src/pert/analyzer.ts +0 -1183
  414. package/src/pert/diagnostics.ts +0 -110
  415. package/src/pert/index.ts +0 -43
  416. package/src/pert/internal.ts +0 -252
  417. package/src/pert/layout.ts +0 -971
  418. package/src/pert/monte-carlo.ts +0 -446
  419. package/src/pert/parser.ts +0 -1606
  420. package/src/pert/renderer.ts +0 -4009
  421. package/src/pert/share-normalize.ts +0 -62
  422. package/src/pert/types.ts +0 -604
  423. package/src/pyramid/parser.ts +0 -185
  424. package/src/pyramid/renderer.ts +0 -768
  425. package/src/pyramid/types.ts +0 -28
  426. package/src/quadrant/parser.ts +0 -18
  427. package/src/quadrant/renderer.ts +0 -638
  428. package/src/raci/diagnostics.ts +0 -213
  429. package/src/raci/index.ts +0 -23
  430. package/src/raci/mutations.ts +0 -321
  431. package/src/raci/parser.ts +0 -699
  432. package/src/raci/renderer.ts +0 -1627
  433. package/src/raci/types.ts +0 -82
  434. package/src/raci/variants.ts +0 -320
  435. package/src/render.ts +0 -268
  436. package/src/ring/parser.ts +0 -212
  437. package/src/ring/renderer.ts +0 -490
  438. package/src/ring/types.ts +0 -27
  439. package/src/sequence/collapse.ts +0 -174
  440. package/src/sequence/parser.ts +0 -1596
  441. package/src/sequence/participant-inference.ts +0 -152
  442. package/src/sequence/renderer.ts +0 -3168
  443. package/src/sequence/tag-resolution.ts +0 -164
  444. package/src/sharing.ts +0 -211
  445. package/src/sitemap/collapse.ts +0 -100
  446. package/src/sitemap/layout.ts +0 -925
  447. package/src/sitemap/parser.ts +0 -755
  448. package/src/sitemap/renderer.ts +0 -828
  449. package/src/sitemap/types.ts +0 -42
  450. package/src/slope/parser.ts +0 -18
  451. package/src/slope/renderer.ts +0 -378
  452. package/src/swimlane/diagnostics.ts +0 -94
  453. package/src/swimlane/layout.ts +0 -597
  454. package/src/swimlane/parser.ts +0 -613
  455. package/src/swimlane/renderer.ts +0 -681
  456. package/src/swimlane/types.ts +0 -151
  457. package/src/tech-radar/interactive.ts +0 -1129
  458. package/src/tech-radar/layout.ts +0 -197
  459. package/src/tech-radar/parser.ts +0 -436
  460. package/src/tech-radar/renderer.ts +0 -1239
  461. package/src/tech-radar/shared.ts +0 -151
  462. package/src/tech-radar/types.ts +0 -86
  463. package/src/themes.ts +0 -22
  464. package/src/timeline/parser.ts +0 -472
  465. package/src/timeline/renderer.ts +0 -3024
  466. package/src/timeline/types.ts +0 -33
  467. package/src/timeline/viz-parser.ts +0 -21
  468. package/src/treemap/diagnostics.ts +0 -62
  469. package/src/treemap/layout-radial.ts +0 -233
  470. package/src/treemap/layout.ts +0 -189
  471. package/src/treemap/parser.ts +0 -463
  472. package/src/treemap/renderer-radial.ts +0 -438
  473. package/src/treemap/renderer.ts +0 -572
  474. package/src/treemap/treemap-shared.ts +0 -309
  475. package/src/treemap/types.ts +0 -60
  476. package/src/utils/arrow-markers.ts +0 -51
  477. package/src/utils/arrows-diagnostics.ts +0 -62
  478. package/src/utils/arrows.ts +0 -227
  479. package/src/utils/brand.ts +0 -61
  480. package/src/utils/card.ts +0 -183
  481. package/src/utils/collapse-engine/tree-edges.ts +0 -115
  482. package/src/utils/collapse-engine/tree.ts +0 -117
  483. package/src/utils/d3-helpers.ts +0 -219
  484. package/src/utils/d3-types.ts +0 -9
  485. package/src/utils/description-helpers.ts +0 -38
  486. package/src/utils/duration.ts +0 -312
  487. package/src/utils/export-container.ts +0 -41
  488. package/src/utils/expr-eval.ts +0 -27
  489. package/src/utils/fit-canvas.ts +0 -64
  490. package/src/utils/hover-styles.ts +0 -673
  491. package/src/utils/inline-markdown.ts +0 -96
  492. package/src/utils/legend-constants.ts +0 -48
  493. package/src/utils/legend-d3.ts +0 -618
  494. package/src/utils/legend-integration.ts +0 -107
  495. package/src/utils/legend-layout.ts +0 -776
  496. package/src/utils/legend-svg.ts +0 -214
  497. package/src/utils/legend-types.ts +0 -272
  498. package/src/utils/name-normalize.ts +0 -163
  499. package/src/utils/note-box/constants.ts +0 -25
  500. package/src/utils/note-box/index.ts +0 -11
  501. package/src/utils/note-box/metrics.ts +0 -90
  502. package/src/utils/note-box/svg.ts +0 -331
  503. package/src/utils/notes/bounds.ts +0 -30
  504. package/src/utils/notes/build.ts +0 -131
  505. package/src/utils/notes/index.ts +0 -18
  506. package/src/utils/notes/model.ts +0 -19
  507. package/src/utils/notes/parse.ts +0 -131
  508. package/src/utils/notes/place.ts +0 -177
  509. package/src/utils/notes/resolve.ts +0 -88
  510. package/src/utils/number-format.ts +0 -36
  511. package/src/utils/parsing.ts +0 -953
  512. package/src/utils/reserved-key-registry.ts +0 -239
  513. package/src/utils/safe-href.ts +0 -38
  514. package/src/utils/scaling.ts +0 -97
  515. package/src/utils/svg-embed.ts +0 -251
  516. package/src/utils/tag-groups.ts +0 -607
  517. package/src/utils/text-measure.ts +0 -122
  518. package/src/utils/title-constants.ts +0 -24
  519. package/src/utils/visual-conventions.ts +0 -61
  520. package/src/utils/wrapped-desc.ts +0 -87
  521. package/src/venn/parser.ts +0 -18
  522. package/src/venn/renderer.ts +0 -1047
  523. package/src/version-control/diagnostics.ts +0 -67
  524. package/src/version-control/parser.ts +0 -544
  525. package/src/version-control/renderer.ts +0 -724
  526. package/src/version-control/types.ts +0 -100
  527. package/src/visualizations/parse.ts +0 -1560
  528. package/src/visualizations/types.ts +0 -262
  529. package/src/wireframe/layout.ts +0 -479
  530. package/src/wireframe/parser.ts +0 -1114
  531. package/src/wireframe/renderer.ts +0 -1318
  532. package/src/wireframe/types.ts +0 -110
  533. package/src/wordcloud/parser.ts +0 -18
  534. package/src/wordcloud/renderer.ts +0 -357
@@ -1,971 +0,0 @@
1
- // ============================================================
2
- // PERT Layout — dagre wrapper
3
- // ============================================================
4
- //
5
- // Pure functions; no UI state. The diagrammo-app holds expansion state
6
- // in its own store and feeds an override map into `relayoutPert`.
7
-
8
- import dagre from '@dagrejs/dagre';
9
- import type {
10
- ResolvedPert,
11
- LayoutResult,
12
- PertLayoutNode,
13
- PertLayoutGroup,
14
- PertDirection,
15
- } from './types';
16
- import type { LayoutOverrides } from './internal';
17
- import {
18
- formatDuration,
19
- formatScheduleValue,
20
- formatSprintCell,
21
- formatSlackValue,
22
- } from './internal';
23
- import { measureText } from '../utils/text-measure';
24
-
25
- // Textbook 3×3 PERT/CPM box: top row [ES | dur | EF], middle row
26
- // [name spanning all three columns], bottom row [LS | slack | LF].
27
- // Heights are fixed; widths are computed from cell content via
28
- // `computeNodeSizing` so a non-date diagram (where ES/EF are short
29
- // duration labels) renders narrower than a date-anchored diagram
30
- // (where those cells hold a 10-char `YYYY-MM-DD`).
31
- const DEFAULT_NODE_HEIGHT = 90;
32
- const MILESTONE_NODE_HEIGHT = DEFAULT_NODE_HEIGHT;
33
- const COLLAPSED_GROUP_HEIGHT = 90;
34
- const DIAGRAM_PADDING = 20;
35
- const GROUP_PADDING = 18;
36
- // Top padding reserves space for the centered group label band — matches
37
- // org's `CONTAINER_HEADER_HEIGHT`. See `docs/architecture/diagram-visual-
38
- // conventions.md` §2 ("Reserved header band").
39
- const GROUP_TOP_PADDING = 28;
40
-
41
- // Swim-lane layout constants. When a diagram has groups, members are
42
- // placed in horizontal (LR) or vertical (TB) bands so group rectangles
43
- // never overlap, even when cross-group edges scatter members across ranks.
44
- const SWIMLANE_SLOT_SEP = 40;
45
- const SWIMLANE_GAP = 24;
46
-
47
- // Cell-sizing constants — see `computeNodeSizing` below.
48
- const NODE_CELL_FONT_SIZE = 11;
49
- const NODE_NAME_FONT_SIZE = 13;
50
- // Milestone name renders at 12pt with a leading `◆ ` glyph (see renderer).
51
- const MILESTONE_NAME_FONT_SIZE = 12;
52
- const CELL_PAD_X = 8;
53
- const NAME_PAD_X = 6;
54
- // Anchor icon + gap (renderer reserves space to the left of the name
55
- // when the activity is the forward source / backward sink).
56
- const NAME_PIN_WIDTH = 13 + 4;
57
- const MIN_CELL_WIDTH = 30;
58
- const MIN_NODE_WIDTH = 120;
59
- const MAX_NODE_WIDTH = 280;
60
- const MIN_MILESTONE_WIDTH = 80;
61
- const MAX_MILESTONE_WIDTH = 160;
62
-
63
- /**
64
- * Per-diagram node geometry. All non-milestone activity nodes (and any
65
- * collapsed-group cards) share `activityWidth` so the lane reads as a
66
- * uniform grid; `outerColW`/`midColW` distribute that width so the
67
- * outer cells (ES/EF/LS/LF) fit their longest displayed value and the
68
- * middle cells (dur/slack) fit theirs. Milestones get their own width.
69
- */
70
- export interface NodeSizing {
71
- activityWidth: number;
72
- milestoneWidth: number;
73
- outerColW: number;
74
- midColW: number;
75
- }
76
-
77
- /**
78
- * Walk every resolved activity, format each cell with the same fn the
79
- * renderer uses, and return the column/node widths needed to hold the
80
- * longest cell text (plus the longest name, capped at MAX_NODE_WIDTH —
81
- * names beyond that truncate with ellipsis at draw time). Sprint /
82
- * date / duration modes each have a characteristic cell-width profile,
83
- * so the diagram tightens automatically when ES/EF only hold "5w" vs.
84
- * "2026-05-15".
85
- */
86
- export function computeNodeSizing(resolved: ResolvedPert): NodeSizing {
87
- const unit = resolved.options.timeUnit;
88
- const sprintMode = resolved.options.sprintMode;
89
- const sprintNumber = resolved.options.sprintNumber ?? 1;
90
- const projectStart = resolved.projectStart;
91
-
92
- const fmtSchedule = (v: number | null, isTbd: boolean): string =>
93
- sprintMode
94
- ? formatSprintCell(v, sprintNumber, isTbd ? '?' : null)
95
- : formatScheduleValue(v, projectStart, unit, isTbd ? '?' : null);
96
- const fmtSlack = (v: number | null, isTbd: boolean): string =>
97
- formatSlackValue(v, projectStart, unit, isTbd ? '?' : null);
98
- const fmtDur = (v: number | null, isTbd: boolean): string =>
99
- formatDuration(v, unit, isTbd ? '?' : null);
100
-
101
- // Track the widest rendered pixel width per cell category — measured at
102
- // the exact font sizes the renderer draws with so reserved width
103
- // matches drawn text.
104
- let maxOuterW = 0;
105
- let maxMidW = 0;
106
- let maxNameW = 0;
107
- let maxMilestoneTopW = 0;
108
- let maxMilestoneSlackW = 0;
109
- let maxMilestoneNameW = 0;
110
-
111
- for (const r of resolved.activities) {
112
- const isTbd = r.es === null;
113
- if (r.activity.isMilestone) {
114
- const dateStr = fmtSchedule(r.es, isTbd);
115
- const slackStr = fmtSlack(r.slack, isTbd);
116
- const slackHidden = !isTbd && /^0[a-z]?$/.test(slackStr);
117
- maxMilestoneTopW = Math.max(
118
- maxMilestoneTopW,
119
- measureText(dateStr, NODE_CELL_FONT_SIZE)
120
- );
121
- if (!slackHidden) {
122
- maxMilestoneSlackW = Math.max(
123
- maxMilestoneSlackW,
124
- measureText(slackStr, NODE_CELL_FONT_SIZE)
125
- );
126
- }
127
- // The milestone glyph prefix `◆ ` precedes the name.
128
- maxMilestoneNameW = Math.max(
129
- maxMilestoneNameW,
130
- measureText(`◆ ${r.activity.name}`, MILESTONE_NAME_FONT_SIZE)
131
- );
132
- continue;
133
- }
134
- const esStr = fmtSchedule(r.es, isTbd);
135
- const efStr = fmtSchedule(r.ef, isTbd);
136
- const lsStr = fmtSchedule(r.ls, isTbd);
137
- const lfStr = fmtSchedule(r.lf, isTbd);
138
- const durStr = fmtDur(r.mu, isTbd);
139
- const slackStr = fmtSlack(r.slack, isTbd);
140
- maxOuterW = Math.max(
141
- maxOuterW,
142
- measureText(esStr, NODE_CELL_FONT_SIZE),
143
- measureText(efStr, NODE_CELL_FONT_SIZE),
144
- measureText(lsStr, NODE_CELL_FONT_SIZE),
145
- measureText(lfStr, NODE_CELL_FONT_SIZE)
146
- );
147
- maxMidW = Math.max(
148
- maxMidW,
149
- measureText(durStr, NODE_CELL_FONT_SIZE),
150
- measureText(slackStr, NODE_CELL_FONT_SIZE)
151
- );
152
- maxNameW = Math.max(
153
- maxNameW,
154
- measureText(r.activity.name, NODE_NAME_FONT_SIZE)
155
- );
156
- }
157
-
158
- // Also account for any collapsed-group rolled-up label width — the
159
- // renderer draws those with the same textbook-card chrome, so their
160
- // name needs to fit in the shared `activityWidth`.
161
- for (const rg of resolved.groups) {
162
- maxNameW = Math.max(
163
- maxNameW,
164
- measureText(rg.group.name, NODE_NAME_FONT_SIZE)
165
- );
166
- }
167
-
168
- const outerCell = Math.ceil(maxOuterW) + 2 * CELL_PAD_X;
169
- const midCell = Math.ceil(maxMidW) + 2 * CELL_PAD_X;
170
- const outerNeeded = Math.max(MIN_CELL_WIDTH, outerCell);
171
- const midNeeded = Math.max(MIN_CELL_WIDTH, midCell);
172
- const cellsTotalW = 2 * outerNeeded + midNeeded;
173
-
174
- // The name dictates a lower bound too — anchor-pinned cards reserve
175
- // a NAME_PIN_WIDTH on the left, so size for the worst case.
176
- const nameTextW = Math.ceil(maxNameW);
177
- const nameTotalW = nameTextW + NAME_PIN_WIDTH + 2 * NAME_PAD_X;
178
-
179
- const activityWidth = Math.max(
180
- MIN_NODE_WIDTH,
181
- Math.min(MAX_NODE_WIDTH, Math.max(cellsTotalW, nameTotalW))
182
- );
183
-
184
- // Distribute the chosen activityWidth across columns. The ES/EF/LS/LF
185
- // pillars must always be the same dimension, so any slack between
186
- // `cellsTotalW` and `activityWidth` (from MIN_NODE_WIDTH inflation or
187
- // a long name) goes to the outer columns. The middle stays at its
188
- // natural need so the dur/slack cells don't grow needlessly wide. If
189
- // mid's natural need ever exceeds the outer share, fall back to three
190
- // equal columns so the middle never reads as the widest cell.
191
- let midColW = midNeeded;
192
- let outerColW = (activityWidth - midColW) / 2;
193
- if (midColW > outerColW) {
194
- outerColW = activityWidth / 3;
195
- midColW = activityWidth - 2 * outerColW;
196
- }
197
-
198
- // Milestones are independent: a one-cell-tall date row, a name row,
199
- // and (optionally) a slack row. Width should fit whichever is widest.
200
- const mTop = Math.ceil(maxMilestoneTopW) + 2 * CELL_PAD_X;
201
- const mSlack =
202
- maxMilestoneSlackW > 0 ? Math.ceil(maxMilestoneSlackW) + 2 * CELL_PAD_X : 0;
203
- // Milestone name renders at 12pt with anchor-pin reserve.
204
- const mName = Math.ceil(maxMilestoneNameW) + NAME_PIN_WIDTH + 2 * NAME_PAD_X;
205
- const milestoneWidth = Math.max(
206
- MIN_MILESTONE_WIDTH,
207
- Math.min(MAX_MILESTONE_WIDTH, Math.max(mTop, mSlack, mName))
208
- );
209
-
210
- return { activityWidth, milestoneWidth, outerColW, midColW };
211
- }
212
-
213
- function nodeDimensions(
214
- resolved: ResolvedPert,
215
- id: string,
216
- sizing: NodeSizing,
217
- overrides?: LayoutOverrides
218
- ): { width: number; height: number } {
219
- if (overrides?.[id]) {
220
- return { width: overrides[id].width, height: overrides[id].height };
221
- }
222
- const r = resolved.activities.find((a) => a.activity.id === id);
223
- if (r?.activity.isMilestone) {
224
- return { width: sizing.milestoneWidth, height: MILESTONE_NODE_HEIGHT };
225
- }
226
- return { width: sizing.activityWidth, height: DEFAULT_NODE_HEIGHT };
227
- }
228
-
229
- export function layoutPert(resolved: ResolvedPert): LayoutResult {
230
- return relayoutPert(resolved, {});
231
- }
232
-
233
- export function relayoutPert(
234
- resolved: ResolvedPert,
235
- overrides: LayoutOverrides,
236
- collapsedGroupIds: ReadonlySet<string> = new Set()
237
- ): LayoutResult {
238
- if (resolved.activities.length === 0) {
239
- return { nodes: [], edges: [], groups: [], width: 0, height: 0 };
240
- }
241
-
242
- // Build member-activity → collapsed-group lookup so we can replace
243
- // member activities with a single virtual dagre node per group.
244
- const memberToGroup = new Map<string, string>();
245
- for (const rg of resolved.groups) {
246
- if (collapsedGroupIds.has(rg.group.id)) {
247
- for (const aid of rg.group.activityIds) {
248
- memberToGroup.set(aid, rg.group.id);
249
- }
250
- }
251
- }
252
- const dagreId = (id: string): string => memberToGroup.get(id) ?? id;
253
-
254
- const g = new dagre.graphlib.Graph();
255
- g.setGraph({
256
- rankdir: resolved.options.direction,
257
- nodesep: 50,
258
- ranksep: 60,
259
- edgesep: 18,
260
- marginx: DIAGRAM_PADDING,
261
- marginy: DIAGRAM_PADDING,
262
- });
263
- g.setDefaultEdgeLabel(() => ({}));
264
-
265
- const sizing = computeNodeSizing(resolved);
266
-
267
- // Add real activity nodes (skipping members of collapsed groups).
268
- for (const r of resolved.activities) {
269
- if (memberToGroup.has(r.activity.id)) continue;
270
- const { width, height } = nodeDimensions(
271
- resolved,
272
- r.activity.id,
273
- sizing,
274
- overrides
275
- );
276
- g.setNode(r.activity.id, { width, height });
277
- }
278
- // Add one virtual node per collapsed group — uses the same shared
279
- // activity width so the rolled-up envelope lines up with siblings.
280
- for (const rg of resolved.groups) {
281
- if (collapsedGroupIds.has(rg.group.id)) {
282
- g.setNode(rg.group.id, {
283
- width: sizing.activityWidth,
284
- height: COLLAPSED_GROUP_HEIGHT,
285
- });
286
- }
287
- }
288
-
289
- // Add edges; rewrite endpoints that land on collapsed members,
290
- // dedupe, and drop edges that fold to a self-loop (internal-only
291
- // edges of a collapsed group).
292
- const seenEdges = new Set<string>();
293
- for (const e of resolved.edges) {
294
- const src = dagreId(e.source);
295
- const tgt = dagreId(e.target);
296
- if (src === tgt) continue;
297
- const k = `${src}|${tgt}`;
298
- if (seenEdges.has(k)) continue;
299
- seenEdges.add(k);
300
- g.setEdge(src, tgt, {});
301
- }
302
-
303
- dagre.layout(g);
304
-
305
- // When the diagram has any expanded group, switch to swim-lane layout:
306
- // ungrouped activities merge into the largest group's lane (the
307
- // "spine") so they share the same row as the spine's members, and any
308
- // additional groups get their own horizontal band below — guarantees
309
- // group rectangles never overlap and cross-group edges read as
310
- // explicit lane transitions. Falls back to per-rank criticality
311
- // centering when there are no groups (or all collapsed).
312
- const swimApplied = applySwimLanes(
313
- g,
314
- resolved,
315
- memberToGroup,
316
- collapsedGroupIds
317
- );
318
- if (!swimApplied) {
319
- // Pull critical-path activities toward the vertical center of each
320
- // rank. Dagre orders nodes within a rank to minimize edge crossings,
321
- // which is criticality-blind — so the dominant path drifts wherever
322
- // dagre chose. After layout, swap y-positions per rank so the
323
- // highest-criticality node sits in the middle and lower-criticality
324
- // ones fan out to the edges.
325
- centerByCriticality(g, resolved, memberToGroup, collapsedGroupIds);
326
- // Criticality centering is crossing-blind — it can undo dagre's
327
- // barycenter ordering and reintroduce edge crossings. Run a greedy
328
- // adjacent-swap sweep that keeps any swap that reduces total edge
329
- // crossings, preserving criticality-at-center where it doesn't
330
- // conflict.
331
- reduceCrossings(g, resolved.options.direction);
332
- }
333
-
334
- // Output nodes — only un-collapsed activities (virtual group nodes
335
- // are rendered through the `groups` channel, not here).
336
- const nodes: PertLayoutNode[] = resolved.activities
337
- .filter((r) => !memberToGroup.has(r.activity.id))
338
- .map((r) => {
339
- const pos = g.node(r.activity.id);
340
- return {
341
- id: r.activity.id,
342
- x: pos.x,
343
- y: pos.y,
344
- width: pos.width,
345
- height: pos.height,
346
- };
347
- });
348
-
349
- // Output edges — one per dedup'd dagre edge. Endpoints already point
350
- // at virtual group ids when a side is collapsed, which the renderer
351
- // uses verbatim for `data-source` / `data-target`.
352
- const edges = Array.from(seenEdges).map((k) => {
353
- // In-bounds: `seenEdges` keys are always `source|target` (built via join).
354
- const [source, target] = k.split('|') as [string, string];
355
- const data = g.edge(source, target);
356
- return { source, target, points: data?.points ?? [] };
357
- });
358
-
359
- // Pool of every laid-out rect (real activities + virtual group nodes)
360
- // for both diagram-bounds and group-rect math.
361
- const nodeMap = new Map<
362
- string,
363
- { x: number; y: number; width: number; height: number }
364
- >();
365
- for (const n of nodes) nodeMap.set(n.id, n);
366
- for (const rg of resolved.groups) {
367
- if (collapsedGroupIds.has(rg.group.id)) {
368
- const vn = g.node(rg.group.id);
369
- nodeMap.set(rg.group.id, {
370
- x: vn.x,
371
- y: vn.y,
372
- width: vn.width,
373
- height: vn.height,
374
- });
375
- }
376
- }
377
-
378
- const groups: PertLayoutGroup[] = resolved.groups.map((rg) => {
379
- const classification = rg.group.classification ?? 'cluster';
380
- if (collapsedGroupIds.has(rg.group.id)) {
381
- const vn = nodeMap.get(rg.group.id);
382
- if (!vn) {
383
- return {
384
- id: rg.group.id,
385
- x: 0,
386
- y: 0,
387
- width: 0,
388
- height: 0,
389
- classification,
390
- collapsed: true,
391
- };
392
- }
393
- return {
394
- id: rg.group.id,
395
- x: vn.x - vn.width / 2,
396
- y: vn.y - vn.height / 2,
397
- width: vn.width,
398
- height: vn.height,
399
- classification,
400
- collapsed: true,
401
- };
402
- }
403
-
404
- const members = rg.group.activityIds
405
- .map((id) => nodeMap.get(id))
406
- .filter(
407
- (n): n is { x: number; y: number; width: number; height: number } =>
408
- n !== undefined
409
- );
410
-
411
- if (members.length === 0) {
412
- return {
413
- id: rg.group.id,
414
- x: 0,
415
- y: 0,
416
- width: 0,
417
- height: 0,
418
- classification,
419
- };
420
- }
421
-
422
- let minX = Infinity;
423
- let minY = Infinity;
424
- let maxX = -Infinity;
425
- let maxY = -Infinity;
426
- for (const m of members) {
427
- const left = m.x - m.width / 2;
428
- const right = m.x + m.width / 2;
429
- const top = m.y - m.height / 2;
430
- const bottom = m.y + m.height / 2;
431
- if (left < minX) minX = left;
432
- if (right > maxX) maxX = right;
433
- if (top < minY) minY = top;
434
- if (bottom > maxY) maxY = bottom;
435
- }
436
- // Group rectangle is a tight bounding box around its members plus
437
- // label-band + bottom padding. Swim-lane placement already guarantees
438
- // non-overlap by giving each group a distinct slot band, so we don't
439
- // need to extend the rect to the full lane height — that would just
440
- // bake in empty space when the spine lane absorbs ungrouped members
441
- // at ranks the spine doesn't span.
442
- return {
443
- id: rg.group.id,
444
- x: minX - GROUP_PADDING,
445
- y: minY - GROUP_TOP_PADDING,
446
- width: maxX - minX + GROUP_PADDING * 2,
447
- height: maxY - minY + GROUP_TOP_PADDING + GROUP_PADDING,
448
- classification,
449
- };
450
- });
451
-
452
- // Diagram bounds — include both nodes and group rects (collapsed
453
- // groups contribute their virtual-node bounds via `groups`).
454
- let totalW = 0;
455
- let totalH = 0;
456
- for (const n of nodes) {
457
- const right = n.x + n.width / 2;
458
- const bottom = n.y + n.height / 2;
459
- if (right > totalW) totalW = right;
460
- if (bottom > totalH) totalH = bottom;
461
- }
462
- for (const grp of groups) {
463
- if (grp.x + grp.width > totalW) totalW = grp.x + grp.width;
464
- if (grp.y + grp.height > totalH) totalH = grp.y + grp.height;
465
- }
466
-
467
- return {
468
- nodes,
469
- edges,
470
- groups,
471
- width: totalW + DIAGRAM_PADDING,
472
- height: totalH + DIAGRAM_PADDING,
473
- };
474
- }
475
-
476
- // ============================================================
477
- // Swim-lane layout (post-process pass)
478
- // ============================================================
479
- //
480
- // When the diagram declares any group, dagre's rank-only layout will
481
- // scatter group members across ranks that the OTHER group also occupies,
482
- // causing post-hoc bounding rectangles to overlap (the classic "two
483
- // groups crammed into the same column band" failure). To fix this, we
484
- // reassign positions along the slot axis so each group gets its own
485
- // contiguous band along the slot axis. Dagre's rank assignments (rank
486
- // axis) are preserved so the diagram still reads as a topological flow.
487
- //
488
- // Lane order: the SPINE lane (the largest expanded group by member
489
- // count) goes first, with all ungrouped activities + collapsed-group
490
- // cards merged into it so they share the spine's primary row — wreck-
491
- // located, dive-ops member, log-inventory all sit at the same y in LR.
492
- // Other expanded groups stack below in source-declaration order. The
493
- // spine group's rectangle remains tight around its own members (not the
494
- // merged-in ungrouped ones), so ungrouped nodes sit at the spine row
495
- // but outside the rect, in the columns the spine doesn't span.
496
-
497
- function applySwimLanes(
498
- g: any,
499
- resolved: ResolvedPert,
500
- _memberToGroup: Map<string, string>,
501
- collapsedGroupIds: ReadonlySet<string>
502
- ): boolean {
503
- const expanded = resolved.groups.filter(
504
- (rg) => !collapsedGroupIds.has(rg.group.id)
505
- );
506
- if (expanded.length === 0) return false;
507
-
508
- const isLR = resolved.options.direction !== 'TB';
509
- const rankAxis = isLR ? 'x' : 'y';
510
- const slotAxis = isLR ? 'y' : 'x';
511
-
512
- // Spine = largest expanded group; ungrouped activities (and collapsed-
513
- // group cards) all merge into this lane so they ride the spine row.
514
- // Tiebreak by source-declaration order (first largest wins).
515
- let spineIdx = 1;
516
- let spineMembers = 0;
517
- expanded.forEach((rg, i) => {
518
- if (rg.group.activityIds.length > spineMembers) {
519
- spineMembers = rg.group.activityIds.length;
520
- spineIdx = i + 1;
521
- }
522
- });
523
-
524
- // Lane index per node. 1..N = expanded groups in declaration order;
525
- // ungrouped activities + collapsed-group cards get spineIdx so they
526
- // share the band with the spine's members.
527
- const laneOf = new Map<string, number>();
528
- expanded.forEach((rg, i) => {
529
- for (const aid of rg.group.activityIds) laneOf.set(aid, i + 1);
530
- });
531
- for (const id of g.nodes()) {
532
- if (!laneOf.has(id)) laneOf.set(id, spineIdx);
533
- }
534
-
535
- // Track which nodes are "spine members" — group members of the spine
536
- // (NOT the merged-in ungrouped nodes). This drives in-cell ordering:
537
- // spine members sit at the top slots so they form a continuous row.
538
- const spineMemberSet = new Set<string>(
539
- expanded[spineIdx - 1]?.group.activityIds ?? []
540
- );
541
-
542
- // Bucket every dagre node by rank, then by lane within each rank.
543
- const numLanes = expanded.length + 1; // index 0 unused (kept for clarity)
544
- const rankBuckets = new Map<number, Map<number, string[]>>();
545
- for (const id of g.nodes()) {
546
- const node = g.node(id);
547
- if (!node) continue;
548
- const rankKey = Math.round(node[rankAxis] as number);
549
- const laneIdx = laneOf.get(id) ?? spineIdx;
550
- let byLane = rankBuckets.get(rankKey);
551
- if (!byLane) {
552
- byLane = new Map();
553
- rankBuckets.set(rankKey, byLane);
554
- }
555
- if (!byLane.has(laneIdx)) byLane.set(laneIdx, []);
556
- byLane.get(laneIdx)!.push(id);
557
- }
558
-
559
- // Per-lane slot capacity = max members of that lane in any single rank.
560
- const laneSlots = new Array<number>(numLanes).fill(0);
561
- for (const byLane of rankBuckets.values()) {
562
- for (const [li, members] of byLane) {
563
- // In-bounds: laneSlots indexed by li from byLane which was keyed under numLanes.
564
- if (members.length > laneSlots[li]!) laneSlots[li] = members.length;
565
- }
566
- }
567
-
568
- // Slot size along the slot axis. All non-milestone activity / collapsed-
569
- // group nodes share DEFAULT_NODE_HEIGHT in LR; in TB we use the uniform
570
- // activityWidth (first node's width is representative).
571
- const slotSize = isLR
572
- ? DEFAULT_NODE_HEIGHT
573
- : (g.node(g.nodes()[0])?.width ?? DEFAULT_NODE_HEIGHT);
574
-
575
- // Lane sizes (along the slot axis) = LABEL_PAD + slot strip + BOTTOM_PAD
576
- // for any lane with at least one member; empty lanes contribute 0.
577
- const laneSize: number[] = new Array<number>(numLanes).fill(0);
578
- for (let i = 1; i < numLanes; i++) {
579
- // In-bounds by loop guard.
580
- const slots = laneSlots[i]!;
581
- if (slots === 0) {
582
- laneSize[i] = 0;
583
- continue;
584
- }
585
- const slotsExtent =
586
- slots * slotSize + Math.max(0, slots - 1) * SWIMLANE_SLOT_SEP;
587
- laneSize[i] = GROUP_TOP_PADDING + slotsExtent + GROUP_PADDING;
588
- }
589
-
590
- // Lane start offsets along the slot axis (cumulative). Spine goes first
591
- // so the spine row sits at the top of the diagram; other lanes stack
592
- // below in declaration order.
593
- const laneOrder: number[] = [
594
- spineIdx,
595
- ...Array.from({ length: expanded.length }, (_, i) => i + 1).filter(
596
- (i) => i !== spineIdx
597
- ),
598
- ];
599
- const laneStart: number[] = new Array<number>(numLanes).fill(0);
600
- let cursor = DIAGRAM_PADDING;
601
- for (const li of laneOrder) {
602
- laneStart[li] = cursor;
603
- // In-bounds: laneSize is sized numLanes; li drawn from laneOrder which contains valid indices.
604
- if (laneSize[li]! > 0) cursor += laneSize[li]! + SWIMLANE_GAP;
605
- }
606
-
607
- // Criticality lookup — used as a tie-breaker when ordering peers in
608
- // the same cell.
609
- const critOf = (id: string): number => {
610
- const r = resolved.activities.find((a) => a.activity.id === id);
611
- if (r) {
612
- return r.criticality !== null ? r.criticality : r.isCriticalPath ? 1 : 0;
613
- }
614
- const rg = resolved.groups.find((x) => x.group.id === id);
615
- if (!rg) return 0;
616
- let m = 0;
617
- for (const aid of rg.group.activityIds) {
618
- const ar = resolved.activities.find((a) => a.activity.id === aid);
619
- if (!ar) continue;
620
- const c =
621
- ar.criticality !== null ? ar.criticality : ar.isCriticalPath ? 1 : 0;
622
- if (c > m) m = c;
623
- }
624
- return m;
625
- };
626
-
627
- // Pre-shuffle slot positions for stable tie-breaking.
628
- const oldSlot = new Map<string, number>();
629
- for (const id of g.nodes()) {
630
- oldSlot.set(id, g.node(id)![slotAxis] as number);
631
- }
632
-
633
- // Reposition each node into its (lane, rank) cell — packed from the
634
- // band top so slot 1 stays at the same y across ranks (the "spine
635
- // row"). Spine members win the top slot; ungrouped tag along beneath.
636
- for (const byLane of rankBuckets.values()) {
637
- for (const [li, members] of byLane) {
638
- // In-bounds: laneStart sized numLanes; li drawn from the same lane-index domain.
639
- const slotStripStart = laneStart[li]! + GROUP_TOP_PADDING;
640
-
641
- const sorted = [...members].sort((a, b) => {
642
- // Spine members first (so they own slot 1 — the spine row).
643
- const aSpine = spineMemberSet.has(a) ? 0 : 1;
644
- const bSpine = spineMemberSet.has(b) ? 0 : 1;
645
- if (aSpine !== bSpine) return aSpine - bSpine;
646
- const ca = critOf(a);
647
- const cb = critOf(b);
648
- if (cb !== ca) return cb - ca;
649
- return (oldSlot.get(a) ?? 0) - (oldSlot.get(b) ?? 0);
650
- });
651
-
652
- sorted.forEach((id, idx) => {
653
- const node = g.node(id);
654
- node[slotAxis] =
655
- slotStripStart + slotSize / 2 + idx * (slotSize + SWIMLANE_SLOT_SEP);
656
- });
657
- }
658
- }
659
-
660
- // Re-route every edge with a clean S-curve — almost every node moved
661
- // along the slot axis, so dagre's original control points are now
662
- // routed through empty space at best, through other nodes at worst.
663
- const dir = resolved.options.direction;
664
- for (const e of g.edges()) {
665
- const src = g.node(e.v);
666
- const tgt = g.node(e.w);
667
- if (!src || !tgt) continue;
668
- const data = g.edge(e);
669
- if (!data) continue;
670
- data.points = smoothEdge(src, tgt, dir);
671
- }
672
-
673
- return true;
674
- }
675
-
676
- // ============================================================
677
- // Criticality-aware vertical centering (post-process pass)
678
- // ============================================================
679
- //
680
- // For each dagre rank (column in LR / row in TB), reorder nodes so the
681
- // highest-criticality node occupies the middle slot and lower ones
682
- // fan out symmetrically. Edges are kept in shape but their first/last
683
- // y-coordinates shift to match the new endpoints, with a linear
684
- // interpolation across the polyline so the curve still terminates on
685
- // the new node centers.
686
-
687
- // `@dagrejs/dagre` ships no type definitions, so `g` is typed as `any`
688
- // here — every prop access (`nodes()`, `node(id)`, `edges()`, etc.) is
689
- // untyped within this function.
690
-
691
- function centerByCriticality(
692
- g: any,
693
- resolved: ResolvedPert,
694
- memberToGroup: Map<string, string>,
695
- collapsedGroupIds: ReadonlySet<string>
696
- ): void {
697
- // Look up criticality per laid-out node. For activities we use the
698
- // MC criticality index when present, falling back to binary critical-
699
- // path membership in analytical mode. Collapsed groups inherit the
700
- // max-of-members so they sort with their internal hottest path.
701
- const critById = new Map<string, number>();
702
- for (const r of resolved.activities) {
703
- if (memberToGroup.has(r.activity.id)) continue;
704
- const c = r.criticality !== null ? r.criticality : r.isCriticalPath ? 1 : 0;
705
- critById.set(r.activity.id, c);
706
- }
707
- for (const rg of resolved.groups) {
708
- if (!collapsedGroupIds.has(rg.group.id)) continue;
709
- let maxC = 0;
710
- for (const aid of rg.group.activityIds) {
711
- const r = resolved.activities.find((x) => x.activity.id === aid);
712
- if (!r) continue;
713
- const c =
714
- r.criticality !== null ? r.criticality : r.isCriticalPath ? 1 : 0;
715
- if (c > maxC) maxC = c;
716
- }
717
- critById.set(rg.group.id, maxC);
718
- }
719
-
720
- // Bucket nodes by rank. dagre gives every node in a rank an identical
721
- // x (LR) or y (TB) coordinate, so a single rounded key per axis is
722
- // sufficient.
723
- const rankAxis = resolved.options.direction === 'TB' ? 'y' : 'x';
724
- const slotAxis = resolved.options.direction === 'TB' ? 'x' : 'y';
725
-
726
- const buckets = new Map<number, string[]>();
727
- for (const id of g.nodes()) {
728
- const node = g.node(id);
729
- if (!node) continue;
730
- const key = Math.round(node[rankAxis] as number);
731
- if (!buckets.has(key)) buckets.set(key, []);
732
- buckets.get(key)!.push(id);
733
- }
734
-
735
- // For each rank with > 1 member, recompute slot assignments.
736
- // `shifts` records the delta between the old slot value (along
737
- // `slotAxis`) and the new one, so edges can be translated to match.
738
- const shifts = new Map<string, number>();
739
-
740
- for (const ids of buckets.values()) {
741
- if (ids.length < 2) continue;
742
-
743
- // Existing slot positions (sorted ascending = top→bottom in LR).
744
- const slots = ids
745
- .map((id) => g.node(id)![slotAxis] as number)
746
- .sort((a, b) => a - b);
747
-
748
- // Sort ids by criticality descending; tie-break by current slot
749
- // so deterministic & stable when criticalities are equal.
750
- const currentSlot = new Map<string, number>();
751
- for (const id of ids) currentSlot.set(id, g.node(id)![slotAxis] as number);
752
- const sortedDesc = [...ids].sort((a, b) => {
753
- const ca = critById.get(a) ?? 0;
754
- const cb = critById.get(b) ?? 0;
755
- if (cb !== ca) return cb - ca;
756
- return (currentSlot.get(a) ?? 0) - (currentSlot.get(b) ?? 0);
757
- });
758
-
759
- // Middle-out arrangement: highest goes to the middle, then alternate
760
- // above/below for the next-ranked, so the lowest-criticality ends
761
- // up at the outermost edges.
762
- const top: string[] = [];
763
- const bottom: string[] = [];
764
- let middle: string | null = null;
765
- sortedDesc.forEach((id, i) => {
766
- if (i === 0) middle = id;
767
- else if (i % 2 === 1) top.unshift(id);
768
- else bottom.push(id);
769
- });
770
- const newOrder: string[] = middle
771
- ? [...top, middle, ...bottom]
772
- : [...top, ...bottom];
773
-
774
- newOrder.forEach((id, i) => {
775
- const node = g.node(id);
776
- const oldVal = node[slotAxis] as number;
777
- // In-bounds: slots was built with same length as newOrder above.
778
- const newVal = slots[i]!;
779
- shifts.set(id, newVal - oldVal);
780
- (node as Record<string, unknown>)[slotAxis] = newVal;
781
- });
782
- }
783
-
784
- // Re-route every edge whose endpoints moved. Dagre routes edges
785
- // through gaps between same-rank nodes; once we permute nodes within
786
- // a rank those gaps no longer exist where the polyline expected, and
787
- // a linear-shift hack would push control points through other nodes.
788
- // Replace the polyline with a clean 4-control-point smooth curve
789
- // (entry/exit are pulled along the flow axis so `curveBasis` gives a
790
- // gentle horizontal-leaning S, matching dagre's typical edge shape).
791
- if (shifts.size > 0) {
792
- const dir = resolved.options.direction;
793
- for (const e of g.edges()) {
794
- // Only regenerate edges where at least one endpoint moved, OR
795
- // an edge between two ranks where any node moved (so that
796
- // unaffected edges keep dagre's nicer multi-rank routing).
797
- if (!shifts.has(e.v) && !shifts.has(e.w)) continue;
798
- const src = g.node(e.v);
799
- const tgt = g.node(e.w);
800
- if (!src || !tgt) continue;
801
- const data = g.edge(e);
802
- if (!data) continue;
803
- data.points = smoothEdge(src, tgt, dir);
804
- }
805
- }
806
- }
807
-
808
- /**
809
- * Build a 4-control-point polyline that exits the source rectangle
810
- * along the flow axis, enters the target rectangle the same way, and
811
- * smooths under `curveBasis` into a gentle S-curve. Falls back to a
812
- * straight line when source and target are colinear along the flow.
813
- */
814
- function smoothEdge(
815
- src: { x: number; y: number; width: number; height: number },
816
- tgt: { x: number; y: number; width: number; height: number },
817
- direction: PertDirection
818
- ): { x: number; y: number }[] {
819
- if (direction === 'TB') {
820
- const sy = src.y + src.height / 2;
821
- const ty = tgt.y - tgt.height / 2;
822
- const span = ty - sy;
823
- const pad = Math.max(20, Math.abs(span) * 0.3);
824
- return [
825
- { x: src.x, y: sy },
826
- { x: src.x, y: sy + Math.sign(span || 1) * pad },
827
- { x: tgt.x, y: ty - Math.sign(span || 1) * pad },
828
- { x: tgt.x, y: ty },
829
- ];
830
- }
831
- // LR
832
- const sx = src.x + src.width / 2;
833
- const tx = tgt.x - tgt.width / 2;
834
- const span = tx - sx;
835
- const pad = Math.max(20, Math.abs(span) * 0.3);
836
- return [
837
- { x: sx, y: src.y },
838
- { x: sx + Math.sign(span || 1) * pad, y: src.y },
839
- { x: tx - Math.sign(span || 1) * pad, y: tgt.y },
840
- { x: tx, y: tgt.y },
841
- ];
842
- }
843
-
844
- // ============================================================
845
- // Crossing reduction (post-process pass)
846
- // ============================================================
847
- //
848
- // Greedy adjacent-swap sweep that runs after `centerByCriticality` so
849
- // crossings introduced by criticality permutation can be reversed when
850
- // the swap reduces total edge crossings. Edges are modeled as straight
851
- // segments between node centers — geometrically exact for the polyline
852
- // shape `smoothEdge` produces (the curve only deflects along the flow
853
- // axis, not the slot axis, so two edges cross visually iff their slot-
854
- // axis center segments cross). Caps iterations to bound cost.
855
-
856
- function reduceCrossings(g: any, direction: PertDirection): void {
857
- const isLR = direction !== 'TB';
858
- const rankAxis = isLR ? 'x' : 'y';
859
- const slotAxis = isLR ? 'y' : 'x';
860
-
861
- // Bucket nodes by rank.
862
- const buckets = new Map<number, string[]>();
863
- for (const id of g.nodes()) {
864
- const node = g.node(id);
865
- if (!node) continue;
866
- const key = Math.round(node[rankAxis] as number);
867
- if (!buckets.has(key)) buckets.set(key, []);
868
- buckets.get(key)!.push(id);
869
- }
870
-
871
- // Collect edges once — each is a pair of node ids whose current
872
- // positions we read on every count.
873
- const edges: Array<{ v: string; w: string }> = g
874
- .edges()
875
- .map((e: { v: string; w: string }) => ({ v: e.v, w: e.w }));
876
-
877
- const countCrossings = (): number => {
878
- let total = 0;
879
- for (let i = 0; i < edges.length; i++) {
880
- // In-bounds by loop guard.
881
- const a = edges[i]!;
882
- const a1 = g.node(a.v);
883
- const a2 = g.node(a.w);
884
- if (!a1 || !a2) continue;
885
- for (let j = i + 1; j < edges.length; j++) {
886
- // In-bounds by loop guard.
887
- const b = edges[j]!;
888
- // Shared endpoint → segments meet at a node, not a crossing.
889
- if (a.v === b.v || a.v === b.w || a.w === b.v || a.w === b.w) continue;
890
- const b1 = g.node(b.v);
891
- const b2 = g.node(b.w);
892
- if (!b1 || !b2) continue;
893
- if (segmentsCross(a1, a2, b1, b2)) total++;
894
- }
895
- }
896
- return total;
897
- };
898
-
899
- // Cap iterations — bubble-style sweep converges fast on small DAGs,
900
- // but a pathological case shouldn't spend O(rank * pairs) per pass
901
- // indefinitely.
902
- const MAX_ITER = 8;
903
- let baseline = countCrossings();
904
- if (baseline === 0) return;
905
-
906
- for (let iter = 0; iter < MAX_ITER; iter++) {
907
- let improved = false;
908
- for (const ids of buckets.values()) {
909
- if (ids.length < 2) continue;
910
- const sorted = ids
911
- .slice()
912
- .sort(
913
- (a, b) =>
914
- (g.node(a)![slotAxis] as number) - (g.node(b)![slotAxis] as number)
915
- );
916
- for (let i = 0; i < sorted.length - 1; i++) {
917
- const a = sorted[i];
918
- const b = sorted[i + 1];
919
- const an = g.node(a)!;
920
- const bn = g.node(b)!;
921
- const av = an[slotAxis] as number;
922
- const bv = bn[slotAxis] as number;
923
- an[slotAxis] = bv;
924
- bn[slotAxis] = av;
925
- const after = countCrossings();
926
- if (after < baseline) {
927
- baseline = after;
928
- improved = true;
929
- } else {
930
- an[slotAxis] = av;
931
- bn[slotAxis] = bv;
932
- }
933
- }
934
- }
935
- if (!improved || baseline === 0) break;
936
- }
937
-
938
- // Re-route every edge — node slot positions changed, so dagre's
939
- // original control points no longer connect cleanly.
940
- for (const e of g.edges()) {
941
- const src = g.node(e.v);
942
- const tgt = g.node(e.w);
943
- if (!src || !tgt) continue;
944
- const data = g.edge(e);
945
- if (!data) continue;
946
- data.points = smoothEdge(src, tgt, direction);
947
- }
948
- }
949
-
950
- // Standard CCW segment-intersection test. Returns true only for proper
951
- // (non-collinear, non-endpoint-shared) crossings.
952
- function segmentsCross(
953
- a1: { x: number; y: number },
954
- a2: { x: number; y: number },
955
- b1: { x: number; y: number },
956
- b2: { x: number; y: number }
957
- ): boolean {
958
- const ccw = (
959
- p: { x: number; y: number },
960
- q: { x: number; y: number },
961
- r: { x: number; y: number }
962
- ) => (q.x - p.x) * (r.y - p.y) - (q.y - p.y) * (r.x - p.x);
963
- const d1 = ccw(b1, b2, a1);
964
- const d2 = ccw(b1, b2, a2);
965
- const d3 = ccw(a1, a2, b1);
966
- const d4 = ccw(a1, a2, b2);
967
- return (
968
- ((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) &&
969
- ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))
970
- );
971
- }