@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.
- package/dist/advanced.d.ts +3 -855
- package/dist/advanced.js +541 -70107
- package/dist/analyzer-UYKBXAKR.js +15 -0
- package/dist/auto.js +1 -1
- package/dist/auto.mjs +1 -1
- package/dist/block.js +83 -69417
- package/dist/calculator-LDYQCDDY.js +11 -0
- package/dist/charts-d3-QOZHKTCT.js +25 -0
- package/dist/chunk-2DXMZRWJ.js +800 -0
- package/dist/chunk-2LACINWN.js +786 -0
- package/dist/chunk-33LCUH4H.js +893 -0
- package/dist/chunk-3CIOJ5YJ.js +35 -0
- package/dist/chunk-3HDBAXF5.js +86 -0
- package/dist/chunk-3JLKYGBQ.js +290 -0
- package/dist/chunk-3MQQNXPD.js +415 -0
- package/dist/chunk-3R5POCTZ.js +967 -0
- package/dist/chunk-3SMGIJDD.js +112 -0
- package/dist/chunk-44CM72SQ.js +409 -0
- package/dist/chunk-4GRI7MEB.js +1604 -0
- package/dist/chunk-4OHACBGX.js +606 -0
- package/dist/chunk-5DH3KYFH.js +21 -0
- package/dist/chunk-5MLYVTYM.js +76 -0
- package/dist/chunk-5QXL4NYS.js +553 -0
- package/dist/chunk-5WRI5ZAA.js +31 -0
- package/dist/chunk-6EK55ART.js +117 -0
- package/dist/chunk-6FZYCKXB.js +2065 -0
- package/dist/chunk-6HO5ZTXK.js +277 -0
- package/dist/chunk-6KFWC4CO.js +7 -0
- package/dist/chunk-6MTWIQVO.js +121 -0
- package/dist/chunk-6NYWYNBI.js +329 -0
- package/dist/chunk-6SM5NJQH.js +620 -0
- package/dist/chunk-725LDASD.js +422 -0
- package/dist/chunk-76OWZ3BA.js +130 -0
- package/{src/block/parser.ts → dist/chunk-7JEGTRLD.js} +100 -189
- package/dist/chunk-A44USUV7.js +923 -0
- package/dist/chunk-A4GOY3PE.js +25 -0
- package/dist/chunk-A4VFQH5G.js +251 -0
- package/dist/chunk-AELOBFW5.js +375 -0
- package/dist/chunk-AN2MOO5T.js +522 -0
- package/dist/chunk-BMR4CIEO.js +41 -0
- package/dist/chunk-BS7SV7C3.js +51 -0
- package/dist/chunk-BUPDIHML.js +613 -0
- package/dist/chunk-BUVJFRPY.js +887 -0
- package/dist/chunk-C3Z4DX3V.js +112 -0
- package/dist/chunk-CI2G3OEB.js +1818 -0
- package/dist/chunk-CKNVO24D.js +54 -0
- package/dist/chunk-CUVHQHZ7.js +391 -0
- package/dist/chunk-CXK4DZH3.js +114 -0
- package/dist/{editor.cjs → chunk-DKEPS5LH.js} +14 -225
- package/dist/chunk-DU7W62N5.js +487 -0
- package/dist/chunk-DZAQBW35.js +751 -0
- package/dist/chunk-EB6FPQUP.js +316 -0
- package/dist/chunk-EMS2SK26.js +376 -0
- package/dist/chunk-ERLBPDGU.js +510 -0
- package/dist/chunk-EVCVDTXE.js +732 -0
- package/dist/chunk-FCIIHT24.js +384 -0
- package/dist/chunk-FFUN5D66.js +97 -0
- package/dist/chunk-FITOLXSX.js +714 -0
- package/dist/chunk-FMDSF3OV.js +68 -0
- package/dist/chunk-FNB3PNGF.js +410 -0
- package/dist/chunk-G2H74XJZ.js +2104 -0
- package/dist/chunk-GMR5MFHJ.js +339 -0
- package/dist/chunk-H6PI3KJT.js +305 -0
- package/dist/chunk-I46X5VUA.js +59 -0
- package/dist/chunk-I5IGB56C.js +255 -0
- package/dist/chunk-IH6ZMVQ5.js +3950 -0
- package/dist/chunk-IJZFN3HM.js +463 -0
- package/dist/chunk-IPWPWM6R.js +3079 -0
- package/dist/chunk-IYULMRBE.js +23 -0
- package/dist/chunk-IZMRON6C.js +167 -0
- package/dist/chunk-J5FIXQRT.js +813 -0
- package/dist/chunk-JED6ZDEE.js +73 -0
- package/dist/chunk-JEUJOX5X.js +136 -0
- package/dist/chunk-JM4LEPY3.js +436 -0
- package/dist/chunk-L3UHP6TD.js +101 -0
- package/dist/chunk-L66GEFB6.js +524 -0
- package/dist/chunk-LIOHW36Z.js +469 -0
- package/dist/chunk-LP7EC4MI.js +16 -0
- package/dist/chunk-MITI6NOL.js +548 -0
- package/dist/chunk-MO5YHHMQ.js +168 -0
- package/dist/chunk-MZSBFAPJ.js +885 -0
- package/dist/chunk-NNAAJVBU.js +9 -0
- package/dist/chunk-NOTA6TEI.js +47 -0
- package/dist/chunk-NURGB5Z4.js +335 -0
- package/dist/chunk-NX4EQS3U.js +1062 -0
- package/dist/chunk-NXMESY4J.js +1 -0
- package/dist/chunk-OJR3MQYV.js +1458 -0
- package/dist/chunk-OURHMODR.js +454 -0
- package/dist/chunk-P235SVAH.js +1057 -0
- package/dist/chunk-P4RH4OSP.js +1038 -0
- package/dist/chunk-P6T66CCZ.js +226 -0
- package/dist/chunk-PBAHEPLQ.js +223 -0
- package/dist/chunk-PBHE5UG6.js +1530 -0
- package/dist/chunk-PDKUANDZ.js +35 -0
- package/dist/chunk-PWIHZ6NJ.js +296 -0
- package/{src/c4/layout.ts → dist/chunk-QEBUY5NY.js} +384 -1140
- package/dist/chunk-QLCCJEJI.js +253 -0
- package/dist/chunk-QOOUC2J5.js +52 -0
- package/dist/chunk-QYTPPDAO.js +402 -0
- package/dist/chunk-R3XI2VC7.js +801 -0
- package/dist/chunk-RCERWOJR.js +154 -0
- package/dist/chunk-RJE5JTVK.js +362 -0
- package/dist/chunk-RTLBSQ3G.js +1155 -0
- package/dist/chunk-RUNNI6VE.js +448 -0
- package/dist/chunk-RVMRZAPB.js +354 -0
- package/dist/chunk-RYEGFMUO.js +955 -0
- package/dist/chunk-RZCF2VVM.js +528 -0
- package/dist/chunk-SML67VPV.js +1025 -0
- package/dist/chunk-SQ7IY53Q.js +44 -0
- package/dist/chunk-SQAG3UXH.js +161 -0
- package/dist/chunk-SRTU6HMQ.js +1851 -0
- package/dist/chunk-T2OE3YQB.js +550 -0
- package/dist/chunk-T3UBLACL.js +299 -0
- package/dist/chunk-TLVIHFRO.js +296 -0
- package/dist/chunk-TYS6CSS5.js +52 -0
- package/dist/chunk-U6EQI3U6.js +130 -0
- package/dist/chunk-UJ4FJLBF.js +86 -0
- package/dist/chunk-ULOEATBZ.js +347 -0
- package/dist/chunk-UNCCZ3NU.js +127 -0
- package/dist/chunk-USZW2HHU.js +197 -0
- package/dist/chunk-UWN33VNX.js +1610 -0
- package/dist/chunk-UZ7EZW3B.js +40 -0
- package/dist/chunk-VOKGH627.js +37 -0
- package/dist/chunk-WBRZJOEC.js +204 -0
- package/dist/chunk-WCSOJFU3.js +380 -0
- package/dist/chunk-WCT3NYCV.js +160 -0
- package/dist/chunk-WVAJFAIA.js +529 -0
- package/dist/chunk-X5LCYCL5.js +394 -0
- package/dist/chunk-XAKEUMEJ.js +1256 -0
- package/dist/chunk-Y2ZE3MSG.js +590 -0
- package/dist/chunk-Y3J7VR52.js +646 -0
- package/dist/chunk-Y5QGVTVC.js +33 -0
- package/dist/chunk-YAA6IID3.js +548 -0
- package/dist/chunk-YGJTDXVR.js +692 -0
- package/{src/utils/time-ticks.ts → dist/chunk-YRYKTYMQ.js} +45 -112
- package/dist/chunk-YYVTBZZC.js +61 -0
- package/dist/chunk-Z5CGSFJB.js +647 -0
- package/{src/gantt/calculator.ts → dist/chunk-Z72XRXD4.js} +236 -428
- package/dist/chunk-ZGR7ULFJ.js +978 -0
- package/dist/chunk-ZJEL4H2R.js +851 -0
- package/dist/chunk-ZO27FGJS.js +554 -0
- package/dist/collapse-CLPJD2RL.js +11 -0
- package/dist/collapse-SLINJQW6.js +9 -0
- package/dist/collapse-ZWUFFCK2.js +9 -0
- package/dist/compute-G55RG53N.js +8 -0
- package/dist/dgmo-router-DZCIDUO5.js +73 -0
- package/dist/dimensions-J3FHIMJ6.js +22 -0
- package/dist/element.js +1 -1
- package/dist/element.mjs +1 -1
- package/dist/flowchart-parser-ZNSKIBVR.js +21 -0
- package/dist/flowchart-renderer-ZAEOL4DX.js +35 -0
- package/dist/index.d.ts +3 -469
- package/dist/index.js +137 -69685
- package/dist/layout-46N4JJYK.js +17 -0
- package/dist/layout-5RA6QWR6.js +26 -0
- package/dist/layout-5T6RH5M6.js +14 -0
- package/dist/layout-7QXCNOVS.js +13 -0
- package/dist/layout-EE6GE46Y.js +17 -0
- package/dist/layout-G23F7LDH.js +9 -0
- package/dist/layout-HRBZPFCN.js +23 -0
- package/dist/layout-HWDSRX5E.js +20 -0
- package/dist/layout-HYZPWAK5.js +18 -0
- package/dist/layout-N76NR4ZO.js +19 -0
- package/dist/layout-OB3RI63S.js +14 -0
- package/dist/layout-S2QBYSK2.js +15 -0
- package/dist/layout-UM5F4DJB.js +15 -0
- package/dist/layout-search-OYV77HHS.js +1655 -0
- package/dist/load-data-EHDDZ5ZS.js +8 -0
- package/dist/palettes-PRJ2RI6D.js +56 -0
- package/dist/parser-2MFP2H2E.js +11 -0
- package/dist/parser-3STE62GB.js +21 -0
- package/dist/parser-4FL2CMDG.js +18 -0
- package/dist/parser-4OGOBJJ4.js +13 -0
- package/dist/parser-6BTRLKRZ.js +15 -0
- package/dist/parser-AQETEVRW.js +21 -0
- package/dist/parser-CFZD5IBL.js +12 -0
- package/dist/parser-FUSKFGZN.js +16 -0
- package/dist/parser-I6KXEYFK.js +13 -0
- package/dist/parser-IXSZZQHY.js +18 -0
- package/dist/parser-JAL2HD4I.js +13 -0
- package/dist/parser-KNSQSKQO.js +20 -0
- package/dist/parser-N5O4IHUN.js +13 -0
- package/dist/parser-NGU3ETO6.js +16 -0
- package/dist/parser-NRAI6O25.js +19 -0
- package/dist/parser-O5J4VBII.js +11 -0
- package/dist/parser-OV7NOBW6.js +21 -0
- package/dist/parser-PQ3POX45.js +23 -0
- package/dist/parser-TFZFBVWN.js +11 -0
- package/dist/parser-TRTHDFPP.js +18 -0
- package/dist/parser-VQP2UJ6W.js +11 -0
- package/dist/parser-XDVELHRK.js +13 -0
- package/dist/parser-XKRP7QKP.js +11 -0
- package/dist/parser-Y6WTBU76.js +14 -0
- package/dist/parser-ZSDPZYQP.js +12 -0
- package/dist/renderer-2XQQQ6QR.js +28 -0
- package/dist/renderer-5NDVNDAZ.js +26 -0
- package/dist/renderer-6EEZHMH4.js +26 -0
- package/dist/renderer-6W5462RB.js +43 -0
- package/dist/renderer-7MVLWWQD.js +22 -0
- package/dist/renderer-AWWAF6XD.js +33 -0
- package/dist/renderer-BYCNMBF7.js +34 -0
- package/dist/renderer-CLJHQPBJ.js +29 -0
- package/dist/renderer-GHPZMFU2.js +15 -0
- package/dist/renderer-JGBKUCZD.js +16 -0
- package/dist/renderer-KQB7UGDS.js +19 -0
- package/dist/renderer-PGX2TKVS.js +24 -0
- package/dist/renderer-QIWVHWZO.js +15 -0
- package/dist/renderer-RPOA5YPZ.js +35 -0
- package/dist/renderer-SMIBCRVB.js +24 -0
- package/dist/renderer-T7YKC5VP.js +28 -0
- package/dist/renderer-TOCOVLHS.js +37 -0
- package/dist/renderer-UWA5XS74.js +39 -0
- package/dist/renderer-VACVQYML.js +25 -0
- package/dist/renderer-W4M6QLIJ.js +37 -0
- package/dist/renderer-WZRREQ2P.js +23 -0
- package/dist/renderer-YY2OSF6E.js +27 -0
- package/dist/renderer-Z7I3W2PP.js +24 -0
- package/dist/renderer-ZGNTUWC4.js +22 -0
- package/dist/renderer-ZTR3DER3.js +19 -0
- package/dist/renderer-radial-C32GPJQ7.js +21 -0
- package/dist/resolver-NOAA7ID7.js +13 -0
- package/dist/state-collapse-HCGD6IVJ.js +8 -0
- package/dist/state-parser-Z2V23ATR.js +18 -0
- package/dist/state-renderer-RFYHSP4K.js +34 -0
- package/dist/themes-1-CuKpeH.d.ts +908 -0
- package/package.json +19 -70
- package/dist/advanced.cjs +0 -71648
- package/dist/advanced.d.cts +0 -6571
- package/dist/auto.cjs +0 -70648
- package/dist/auto.d.cts +0 -54
- package/dist/block.cjs +0 -70208
- package/dist/block.d.cts +0 -115
- package/dist/editor.d.cts +0 -26
- package/dist/element.cjs +0 -70530
- package/dist/element.d.cts +0 -62
- package/dist/highlight.cjs +0 -852
- package/dist/highlight.d.cts +0 -49
- package/dist/index.cjs +0 -70491
- package/dist/index.d.cts +0 -626
- package/dist/pert.cjs +0 -325
- package/dist/pert.d.cts +0 -559
- package/src/advanced.ts +0 -873
- package/src/arc/parser.ts +0 -15
- package/src/arc/renderer.ts +0 -633
- package/src/auto/index.ts +0 -621
- package/src/auto/shared.ts +0 -370
- package/src/auto/styles.ts +0 -73
- package/src/block/layout.ts +0 -198
- package/src/block/renderer.ts +0 -484
- package/src/block/types.ts +0 -67
- package/src/boxes-and-lines/collapse.ts +0 -96
- package/src/boxes-and-lines/focus.ts +0 -257
- package/src/boxes-and-lines/label-placement.ts +0 -339
- package/src/boxes-and-lines/layout-grouped.ts +0 -912
- package/src/boxes-and-lines/layout-layered.ts +0 -722
- package/src/boxes-and-lines/layout-search.ts +0 -1678
- package/src/boxes-and-lines/layout.ts +0 -398
- package/src/boxes-and-lines/parser.ts +0 -1181
- package/src/boxes-and-lines/renderer.ts +0 -1465
- package/src/boxes-and-lines/types.ts +0 -66
- package/src/c4/parser.ts +0 -1158
- package/src/c4/renderer.ts +0 -2008
- package/src/c4/types.ts +0 -89
- package/src/chart-type-registry.ts +0 -529
- package/src/chart-types.ts +0 -225
- package/src/chart.ts +0 -685
- package/src/charts-d3/bar.ts +0 -338
- package/src/charts-d3/chord.ts +0 -140
- package/src/charts-d3/function.ts +0 -147
- package/src/charts-d3/funnel.ts +0 -104
- package/src/charts-d3/heatmap.ts +0 -145
- package/src/charts-d3/index.ts +0 -337
- package/src/charts-d3/interactions.ts +0 -477
- package/src/charts-d3/line.ts +0 -344
- package/src/charts-d3/mount.ts +0 -151
- package/src/charts-d3/pie.ts +0 -117
- package/src/charts-d3/polar.ts +0 -89
- package/src/charts-d3/radar.ts +0 -0
- package/src/charts-d3/sankey.ts +0 -0
- package/src/charts-d3/scatter.ts +0 -185
- package/src/charts-d3/shared.ts +0 -208
- package/src/class/layout.ts +0 -326
- package/src/class/parser.ts +0 -635
- package/src/class/renderer.ts +0 -793
- package/src/class/types.ts +0 -58
- package/src/cli-banner.ts +0 -106
- package/src/cli.ts +0 -1080
- package/src/colors-diagnostics.ts +0 -35
- package/src/colors.ts +0 -379
- package/src/completion-types.ts +0 -27
- package/src/completion.ts +0 -2668
- package/src/cycle/layout.ts +0 -1026
- package/src/cycle/parser.ts +0 -387
- package/src/cycle/renderer.ts +0 -653
- package/src/cycle/types.ts +0 -101
- package/src/d3.ts +0 -1434
- package/src/data-chart-parser.ts +0 -1007
- package/src/dgmo-router.ts +0 -296
- package/src/diagnostics-registry.ts +0 -108
- package/src/diagnostics.ts +0 -0
- package/src/dimensions.ts +0 -20
- package/src/directives-registry.ts +0 -396
- package/src/editor/dgmo.grammar +0 -76
- package/src/editor/dgmo.grammar.d.ts +0 -2
- package/src/editor/dgmo.grammar.js +0 -18
- package/src/editor/dgmo.grammar.terms.d.ts +0 -5
- package/src/editor/dgmo.grammar.terms.js +0 -35
- package/src/editor/highlight-api.ts +0 -596
- package/src/editor/highlight.ts +0 -36
- package/src/editor/index.ts +0 -27
- package/src/editor/keywords.ts +0 -128
- package/src/editor/tokens.ts +0 -30
- package/src/element/index.ts +0 -432
- package/src/embed/css.ts +0 -312
- package/src/embed/escape.ts +0 -15
- package/src/embed/index.ts +0 -280
- package/src/er/classify.ts +0 -228
- package/src/er/layout.ts +0 -530
- package/src/er/parser.ts +0 -750
- package/src/er/renderer.ts +0 -725
- package/src/er/types.ts +0 -52
- package/src/error-card.ts +0 -269
- package/src/event-line/diagnostics.ts +0 -117
- package/src/event-line/parser.ts +0 -643
- package/src/event-line/renderer.ts +0 -1742
- package/src/event-line/types.ts +0 -74
- package/src/fonts.ts +0 -3
- package/src/gantt/parser.ts +0 -1641
- package/src/gantt/renderer.ts +0 -3787
- package/src/gantt/resolver.ts +0 -172
- package/src/gantt/types.ts +0 -206
- package/src/graph/diagnostics.ts +0 -64
- package/src/graph/edge-spline.ts +0 -29
- package/src/graph/flowchart-parser.ts +0 -759
- package/src/graph/flowchart-renderer.ts +0 -817
- package/src/graph/layout.ts +0 -472
- package/src/graph/notes.ts +0 -21
- package/src/graph/state-collapse.ts +0 -78
- package/src/graph/state-parser.ts +0 -574
- package/src/graph/state-renderer.ts +0 -653
- package/src/graph/types.ts +0 -65
- package/src/index.ts +0 -261
- package/src/infra/compute.ts +0 -1385
- package/src/infra/layout.ts +0 -888
- package/src/infra/parser.ts +0 -1234
- package/src/infra/renderer.ts +0 -2534
- package/src/infra/roles.ts +0 -75
- package/src/infra/types.ts +0 -216
- package/src/infra/validation.ts +0 -201
- package/src/journey-map/layout.ts +0 -490
- package/src/journey-map/parser.ts +0 -557
- package/src/journey-map/renderer.ts +0 -1580
- package/src/journey-map/types.ts +0 -47
- package/src/kanban/mutations.ts +0 -175
- package/src/kanban/parser.ts +0 -509
- package/src/kanban/renderer.ts +0 -1449
- package/src/kanban/types.ts +0 -39
- package/src/label-layout.ts +0 -368
- package/src/map/colorize.ts +0 -54
- package/src/map/completion.ts +0 -316
- package/src/map/context-labels.ts +0 -609
- package/src/map/data/PROVENANCE.json +0 -1
- package/src/map/data/README.md +0 -40
- package/src/map/data/airport-collisions.json +0 -1
- package/src/map/data/airports.json +0 -1
- package/src/map/data/gazetteer.json +0 -1
- package/src/map/data/lakes.json +0 -1
- package/src/map/data/mountain-ranges.json +0 -1
- package/src/map/data/na-lakes.json +0 -1
- package/src/map/data/na-land.json +0 -1
- package/src/map/data/region-names.json +0 -1
- package/src/map/data/rivers.json +0 -1
- package/src/map/data/types.ts +0 -121
- package/src/map/data/us-states.json +0 -1
- package/src/map/data/water-bodies.json +0 -1
- package/src/map/data/world-coarse.json +0 -1
- package/src/map/data/world-detail.json +0 -1
- package/src/map/diagnostics.ts +0 -134
- package/src/map/dimensions.ts +0 -150
- package/src/map/geo-query.ts +0 -295
- package/src/map/geo.ts +0 -438
- package/src/map/invert.ts +0 -111
- package/src/map/layout.ts +0 -5069
- package/src/map/legend-band.ts +0 -127
- package/src/map/load-data.ts +0 -186
- package/src/map/parser.ts +0 -783
- package/src/map/renderer.ts +0 -1445
- package/src/map/resolved-types.ts +0 -160
- package/src/map/resolver.ts +0 -1146
- package/src/map/types.ts +0 -194
- package/src/mindmap/collapse.ts +0 -67
- package/src/mindmap/layout.ts +0 -649
- package/src/mindmap/parser.ts +0 -430
- package/src/mindmap/renderer.ts +0 -637
- package/src/mindmap/text-wrap.ts +0 -218
- package/src/mindmap/types.ts +0 -56
- package/src/org/collapse.ts +0 -151
- package/src/org/layout.ts +0 -1262
- package/src/org/parser.ts +0 -519
- package/src/org/renderer.ts +0 -866
- package/src/org/resolver.ts +0 -636
- package/src/palettes/atlas.ts +0 -77
- package/src/palettes/blueprint.ts +0 -73
- package/src/palettes/catppuccin.ts +0 -84
- package/src/palettes/color-utils.ts +0 -443
- package/src/palettes/index.ts +0 -67
- package/src/palettes/nord.ts +0 -67
- package/src/palettes/registry.ts +0 -114
- package/src/palettes/slate.ts +0 -73
- package/src/palettes/tidewater.ts +0 -73
- package/src/palettes/tokyo-night.ts +0 -86
- package/src/palettes/types.ts +0 -88
- package/src/pert/analyzer.ts +0 -1183
- package/src/pert/diagnostics.ts +0 -110
- package/src/pert/index.ts +0 -43
- package/src/pert/internal.ts +0 -252
- package/src/pert/layout.ts +0 -971
- package/src/pert/monte-carlo.ts +0 -446
- package/src/pert/parser.ts +0 -1606
- package/src/pert/renderer.ts +0 -4009
- package/src/pert/share-normalize.ts +0 -62
- package/src/pert/types.ts +0 -604
- package/src/pyramid/parser.ts +0 -185
- package/src/pyramid/renderer.ts +0 -768
- package/src/pyramid/types.ts +0 -28
- package/src/quadrant/parser.ts +0 -18
- package/src/quadrant/renderer.ts +0 -638
- package/src/raci/diagnostics.ts +0 -213
- package/src/raci/index.ts +0 -23
- package/src/raci/mutations.ts +0 -321
- package/src/raci/parser.ts +0 -699
- package/src/raci/renderer.ts +0 -1627
- package/src/raci/types.ts +0 -82
- package/src/raci/variants.ts +0 -320
- package/src/render.ts +0 -268
- package/src/ring/parser.ts +0 -212
- package/src/ring/renderer.ts +0 -490
- package/src/ring/types.ts +0 -27
- package/src/sequence/collapse.ts +0 -174
- package/src/sequence/parser.ts +0 -1596
- package/src/sequence/participant-inference.ts +0 -152
- package/src/sequence/renderer.ts +0 -3168
- package/src/sequence/tag-resolution.ts +0 -164
- package/src/sharing.ts +0 -211
- package/src/sitemap/collapse.ts +0 -100
- package/src/sitemap/layout.ts +0 -925
- package/src/sitemap/parser.ts +0 -755
- package/src/sitemap/renderer.ts +0 -828
- package/src/sitemap/types.ts +0 -42
- package/src/slope/parser.ts +0 -18
- package/src/slope/renderer.ts +0 -378
- package/src/swimlane/diagnostics.ts +0 -94
- package/src/swimlane/layout.ts +0 -597
- package/src/swimlane/parser.ts +0 -613
- package/src/swimlane/renderer.ts +0 -681
- package/src/swimlane/types.ts +0 -151
- package/src/tech-radar/interactive.ts +0 -1129
- package/src/tech-radar/layout.ts +0 -197
- package/src/tech-radar/parser.ts +0 -436
- package/src/tech-radar/renderer.ts +0 -1239
- package/src/tech-radar/shared.ts +0 -151
- package/src/tech-radar/types.ts +0 -86
- package/src/themes.ts +0 -22
- package/src/timeline/parser.ts +0 -472
- package/src/timeline/renderer.ts +0 -3024
- package/src/timeline/types.ts +0 -33
- package/src/timeline/viz-parser.ts +0 -21
- package/src/treemap/diagnostics.ts +0 -62
- package/src/treemap/layout-radial.ts +0 -233
- package/src/treemap/layout.ts +0 -189
- package/src/treemap/parser.ts +0 -463
- package/src/treemap/renderer-radial.ts +0 -438
- package/src/treemap/renderer.ts +0 -572
- package/src/treemap/treemap-shared.ts +0 -309
- package/src/treemap/types.ts +0 -60
- package/src/utils/arrow-markers.ts +0 -51
- package/src/utils/arrows-diagnostics.ts +0 -62
- package/src/utils/arrows.ts +0 -227
- package/src/utils/brand.ts +0 -61
- package/src/utils/card.ts +0 -183
- package/src/utils/collapse-engine/tree-edges.ts +0 -115
- package/src/utils/collapse-engine/tree.ts +0 -117
- package/src/utils/d3-helpers.ts +0 -219
- package/src/utils/d3-types.ts +0 -9
- package/src/utils/description-helpers.ts +0 -38
- package/src/utils/duration.ts +0 -312
- package/src/utils/export-container.ts +0 -41
- package/src/utils/expr-eval.ts +0 -27
- package/src/utils/fit-canvas.ts +0 -64
- package/src/utils/hover-styles.ts +0 -673
- package/src/utils/inline-markdown.ts +0 -96
- package/src/utils/legend-constants.ts +0 -48
- package/src/utils/legend-d3.ts +0 -618
- package/src/utils/legend-integration.ts +0 -107
- package/src/utils/legend-layout.ts +0 -776
- package/src/utils/legend-svg.ts +0 -214
- package/src/utils/legend-types.ts +0 -272
- package/src/utils/name-normalize.ts +0 -163
- package/src/utils/note-box/constants.ts +0 -25
- package/src/utils/note-box/index.ts +0 -11
- package/src/utils/note-box/metrics.ts +0 -90
- package/src/utils/note-box/svg.ts +0 -331
- package/src/utils/notes/bounds.ts +0 -30
- package/src/utils/notes/build.ts +0 -131
- package/src/utils/notes/index.ts +0 -18
- package/src/utils/notes/model.ts +0 -19
- package/src/utils/notes/parse.ts +0 -131
- package/src/utils/notes/place.ts +0 -177
- package/src/utils/notes/resolve.ts +0 -88
- package/src/utils/number-format.ts +0 -36
- package/src/utils/parsing.ts +0 -953
- package/src/utils/reserved-key-registry.ts +0 -239
- package/src/utils/safe-href.ts +0 -38
- package/src/utils/scaling.ts +0 -97
- package/src/utils/svg-embed.ts +0 -251
- package/src/utils/tag-groups.ts +0 -607
- package/src/utils/text-measure.ts +0 -122
- package/src/utils/title-constants.ts +0 -24
- package/src/utils/visual-conventions.ts +0 -61
- package/src/utils/wrapped-desc.ts +0 -87
- package/src/venn/parser.ts +0 -18
- package/src/venn/renderer.ts +0 -1047
- package/src/version-control/diagnostics.ts +0 -67
- package/src/version-control/parser.ts +0 -544
- package/src/version-control/renderer.ts +0 -724
- package/src/version-control/types.ts +0 -100
- package/src/visualizations/parse.ts +0 -1560
- package/src/visualizations/types.ts +0 -262
- package/src/wireframe/layout.ts +0 -479
- package/src/wireframe/parser.ts +0 -1114
- package/src/wireframe/renderer.ts +0 -1318
- package/src/wireframe/types.ts +0 -110
- package/src/wordcloud/parser.ts +0 -18
- package/src/wordcloud/renderer.ts +0 -357
|
@@ -1,181 +1,44 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// C4 Context Diagram Layout Engine (dagre)
|
|
3
|
-
// ============================================================
|
|
4
|
-
|
|
5
|
-
import { tagAttrKey } from '../utils/tag-groups';
|
|
6
|
-
import dagre from '@dagrejs/dagre';
|
|
7
|
-
import type {
|
|
8
|
-
ParsedC4,
|
|
9
|
-
C4Element,
|
|
10
|
-
C4Relationship,
|
|
11
|
-
C4ArrowType,
|
|
12
|
-
C4Shape,
|
|
13
|
-
C4DeploymentNode,
|
|
14
|
-
} from './types';
|
|
15
|
-
import type { TagGroup } from '../utils/tag-groups';
|
|
16
|
-
import type { Writable } from '../utils/brand';
|
|
17
|
-
|
|
18
|
-
/** Mutable edge variant for in-place position shifts during layout. */
|
|
19
|
-
type MutableC4LayoutEdge = Writable<C4LayoutEdge> & {
|
|
20
|
-
points: { x: number; y: number }[];
|
|
21
|
-
};
|
|
22
1
|
import {
|
|
23
|
-
|
|
24
|
-
LEGEND_ENTRY_FONT_SIZE,
|
|
25
|
-
LEGEND_HEIGHT,
|
|
26
|
-
LEGEND_PILL_PAD,
|
|
2
|
+
LEGEND_CAPSULE_PAD,
|
|
27
3
|
LEGEND_DOT_R,
|
|
28
4
|
LEGEND_ENTRY_DOT_GAP,
|
|
5
|
+
LEGEND_ENTRY_FONT_SIZE,
|
|
29
6
|
LEGEND_ENTRY_TRAIL,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
readonly description?: string;
|
|
65
|
-
readonly metadata: Readonly<Record<string, string>>;
|
|
66
|
-
readonly lineNumber: number;
|
|
67
|
-
readonly color?: string;
|
|
68
|
-
readonly shape?: C4Shape;
|
|
69
|
-
readonly technology?: string;
|
|
70
|
-
readonly drillable?: boolean;
|
|
71
|
-
readonly importPath?: string;
|
|
72
|
-
readonly x: number;
|
|
73
|
-
readonly y: number;
|
|
74
|
-
readonly width: number;
|
|
75
|
-
readonly height: number;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export interface C4LayoutEdge {
|
|
79
|
-
readonly source: string;
|
|
80
|
-
readonly target: string;
|
|
81
|
-
readonly arrowType: C4ArrowType;
|
|
82
|
-
readonly label?: string;
|
|
83
|
-
readonly technology?: string;
|
|
84
|
-
readonly lineNumber: number;
|
|
85
|
-
readonly points: ReadonlyArray<{ readonly x: number; readonly y: number }>;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export interface C4LegendEntry {
|
|
89
|
-
readonly value: string;
|
|
90
|
-
readonly color: string;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export interface C4LegendGroup {
|
|
94
|
-
readonly name: string;
|
|
95
|
-
readonly entries: readonly C4LegendEntry[];
|
|
96
|
-
readonly x: number;
|
|
97
|
-
readonly y: number;
|
|
98
|
-
readonly width: number;
|
|
99
|
-
readonly height: number;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export interface C4LayoutBoundary {
|
|
103
|
-
readonly label: string;
|
|
104
|
-
readonly typeLabel: string;
|
|
105
|
-
readonly lineNumber: number;
|
|
106
|
-
readonly x: number;
|
|
107
|
-
readonly y: number;
|
|
108
|
-
readonly width: number;
|
|
109
|
-
readonly height: number;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export interface C4LayoutResult {
|
|
113
|
-
readonly nodes: readonly C4LayoutNode[];
|
|
114
|
-
readonly edges: readonly C4LayoutEdge[];
|
|
115
|
-
readonly legend: readonly C4LegendGroup[];
|
|
116
|
-
readonly boundary?: C4LayoutBoundary;
|
|
117
|
-
readonly groupBoundaries: readonly C4LayoutBoundary[];
|
|
118
|
-
readonly width: number;
|
|
119
|
-
readonly height: number;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// ============================================================
|
|
123
|
-
// Constants
|
|
124
|
-
// ============================================================
|
|
125
|
-
|
|
126
|
-
const MIN_NODE_WIDTH = 160;
|
|
127
|
-
const MAX_NODE_WIDTH = 260;
|
|
128
|
-
const TYPE_LABEL_HEIGHT = 18;
|
|
129
|
-
const DIVIDER_GAP = 6;
|
|
130
|
-
const NAME_HEIGHT = 20;
|
|
131
|
-
const NAME_FONT_SIZE = 14;
|
|
132
|
-
const DESC_LINE_HEIGHT = 16;
|
|
133
|
-
const DESC_FONT_SIZE = 11;
|
|
134
|
-
const CARD_V_PAD = 14;
|
|
135
|
-
const CARD_H_PAD = 20;
|
|
136
|
-
const META_LINE_HEIGHT = 16;
|
|
137
|
-
const META_FONT_SIZE = 11;
|
|
138
|
-
const MARGIN = 40;
|
|
139
|
-
const BOUNDARY_PAD = 40;
|
|
140
|
-
const GROUP_BOUNDARY_PAD = 24;
|
|
141
|
-
|
|
142
|
-
// Legend constants (match org)
|
|
143
|
-
|
|
144
|
-
// ============================================================
|
|
145
|
-
// Post-Layout Crossing Reduction
|
|
146
|
-
// ============================================================
|
|
147
|
-
|
|
148
|
-
interface NodeGeometry {
|
|
149
|
-
y: number;
|
|
150
|
-
width: number;
|
|
151
|
-
height: number;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Large penalty per edge-node collision — dominates the distance term so the
|
|
155
|
-
// sifter strongly prefers orderings where edges don't pass through other nodes.
|
|
156
|
-
const EDGE_NODE_COLLISION_WEIGHT = 5000;
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Compute penalty for an edge ordering. Uses degree-weighted edge distance:
|
|
160
|
-
* long edges to high-degree nodes are penalized more than to low-degree nodes.
|
|
161
|
-
* This places shared/important nodes closer to their neighbors, reducing
|
|
162
|
-
* visual edge congestion.
|
|
163
|
-
*
|
|
164
|
-
* When nodeGeometry is provided, also adds a heavy penalty for each case where
|
|
165
|
-
* a straight-line edge bounding box overlaps another node — driving the sifter
|
|
166
|
-
* to prefer orderings that avoid edge-node collisions.
|
|
167
|
-
*/
|
|
168
|
-
function computeEdgePenalty(
|
|
169
|
-
edgeList: { source: string; target: string }[],
|
|
170
|
-
nodePositions: Map<string, number>,
|
|
171
|
-
degrees: Map<string, number>,
|
|
172
|
-
nodeGeometry?: Map<string, NodeGeometry>
|
|
173
|
-
): number {
|
|
7
|
+
LEGEND_HEIGHT,
|
|
8
|
+
LEGEND_PILL_FONT_SIZE,
|
|
9
|
+
LEGEND_PILL_PAD,
|
|
10
|
+
measureLegendText
|
|
11
|
+
} from "./chunk-QOOUC2J5.js";
|
|
12
|
+
import {
|
|
13
|
+
measureText,
|
|
14
|
+
wrapTextToWidth
|
|
15
|
+
} from "./chunk-WCT3NYCV.js";
|
|
16
|
+
import {
|
|
17
|
+
tagAttrKey
|
|
18
|
+
} from "./chunk-QLCCJEJI.js";
|
|
19
|
+
|
|
20
|
+
// src/c4/layout.ts
|
|
21
|
+
import dagre from "@dagrejs/dagre";
|
|
22
|
+
var gNode = (g, name) => g.node(name);
|
|
23
|
+
var gEdge = (g, v, w) => g.edge(v, w);
|
|
24
|
+
var MIN_NODE_WIDTH = 160;
|
|
25
|
+
var MAX_NODE_WIDTH = 260;
|
|
26
|
+
var TYPE_LABEL_HEIGHT = 18;
|
|
27
|
+
var DIVIDER_GAP = 6;
|
|
28
|
+
var NAME_HEIGHT = 20;
|
|
29
|
+
var NAME_FONT_SIZE = 14;
|
|
30
|
+
var DESC_LINE_HEIGHT = 16;
|
|
31
|
+
var DESC_FONT_SIZE = 11;
|
|
32
|
+
var CARD_V_PAD = 14;
|
|
33
|
+
var CARD_H_PAD = 20;
|
|
34
|
+
var META_LINE_HEIGHT = 16;
|
|
35
|
+
var META_FONT_SIZE = 11;
|
|
36
|
+
var MARGIN = 40;
|
|
37
|
+
var BOUNDARY_PAD = 40;
|
|
38
|
+
var GROUP_BOUNDARY_PAD = 24;
|
|
39
|
+
var EDGE_NODE_COLLISION_WEIGHT = 5e3;
|
|
40
|
+
function computeEdgePenalty(edgeList, nodePositions, degrees, nodeGeometry) {
|
|
174
41
|
let penalty = 0;
|
|
175
|
-
|
|
176
|
-
// Degree-weighted edge distance: longer edges to higher-degree nodes
|
|
177
|
-
// are penalized more, pulling "important" (shared) nodes closer to
|
|
178
|
-
// their neighbors.
|
|
179
42
|
for (const edge of edgeList) {
|
|
180
43
|
const sx = nodePositions.get(edge.source);
|
|
181
44
|
const tx = nodePositions.get(edge.target);
|
|
@@ -187,116 +50,74 @@ function computeEdgePenalty(
|
|
|
187
50
|
);
|
|
188
51
|
penalty += dist * weight;
|
|
189
52
|
}
|
|
190
|
-
|
|
191
|
-
// Edge-node collision penalty: for each edge A→B, check if any other node C
|
|
192
|
-
// has its bounding box inside the straight-line bounding box of the edge.
|
|
193
|
-
// Uses x from nodePositions (updated per permutation) and y/size from nodeGeometry.
|
|
194
53
|
if (nodeGeometry) {
|
|
195
54
|
for (const edge of edgeList) {
|
|
196
55
|
const geomA = nodeGeometry.get(edge.source);
|
|
197
56
|
const geomB = nodeGeometry.get(edge.target);
|
|
198
57
|
if (!geomA || !geomB) continue;
|
|
199
|
-
|
|
200
58
|
const ax = nodePositions.get(edge.source) ?? 0;
|
|
201
59
|
const bx = nodePositions.get(edge.target) ?? 0;
|
|
202
60
|
const ay = geomA.y;
|
|
203
61
|
const by = geomB.y;
|
|
204
|
-
|
|
205
|
-
// Skip edges within the same rank — they have no vertical span to check
|
|
206
62
|
if (ay === by) continue;
|
|
207
|
-
|
|
208
63
|
const edgeMinX = Math.min(ax, bx);
|
|
209
64
|
const edgeMaxX = Math.max(ax, bx);
|
|
210
65
|
const edgeMinY = Math.min(ay, by);
|
|
211
66
|
const edgeMaxY = Math.max(ay, by);
|
|
212
|
-
|
|
213
67
|
for (const [name, geomC] of nodeGeometry) {
|
|
214
68
|
if (name === edge.source || name === edge.target) continue;
|
|
215
69
|
const cx = nodePositions.get(name) ?? 0;
|
|
216
70
|
const cy = geomC.y;
|
|
217
71
|
const hw = geomC.width / 2;
|
|
218
72
|
const hh = geomC.height / 2;
|
|
219
|
-
|
|
220
|
-
// AABB overlap: node C's box intersects the edge's straight-line bounding box
|
|
221
|
-
if (
|
|
222
|
-
cx + hw > edgeMinX &&
|
|
223
|
-
cx - hw < edgeMaxX &&
|
|
224
|
-
cy + hh > edgeMinY &&
|
|
225
|
-
cy - hh < edgeMaxY
|
|
226
|
-
) {
|
|
73
|
+
if (cx + hw > edgeMinX && cx - hw < edgeMaxX && cy + hh > edgeMinY && cy - hh < edgeMaxY) {
|
|
227
74
|
penalty += EDGE_NODE_COLLISION_WEIGHT;
|
|
228
75
|
}
|
|
229
76
|
}
|
|
230
77
|
}
|
|
231
78
|
}
|
|
232
|
-
|
|
233
79
|
return penalty;
|
|
234
80
|
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Post-dagre crossing reduction via permutation search.
|
|
238
|
-
*
|
|
239
|
-
* For each rank, tries all permutations (up to rank size 8) to find the
|
|
240
|
-
* node ordering that minimizes degree-weighted edge distance. Nodes with
|
|
241
|
-
* more connections (like a database shared by multiple services) get placed
|
|
242
|
-
* closer to their neighbors, producing cleaner visual layouts.
|
|
243
|
-
*/
|
|
244
|
-
function reduceCrossings(
|
|
245
|
-
g: InstanceType<typeof dagre.graphlib.Graph>,
|
|
246
|
-
edgeList: { source: string; target: string }[],
|
|
247
|
-
nodeGroupMap?: Map<string, string>
|
|
248
|
-
): void {
|
|
81
|
+
function reduceCrossings(g, edgeList, nodeGroupMap) {
|
|
249
82
|
if (edgeList.length < 2) return;
|
|
250
|
-
|
|
251
|
-
// Compute degree (number of edges) for each node
|
|
252
|
-
const degrees = new Map<string, number>();
|
|
83
|
+
const degrees = /* @__PURE__ */ new Map();
|
|
253
84
|
for (const edge of edgeList) {
|
|
254
85
|
degrees.set(edge.source, (degrees.get(edge.source) ?? 0) + 1);
|
|
255
86
|
degrees.set(edge.target, (degrees.get(edge.target) ?? 0) + 1);
|
|
256
87
|
}
|
|
257
|
-
|
|
258
|
-
// Build geometry map for edge-node collision scoring
|
|
259
|
-
const nodeGeometry = new Map<string, NodeGeometry>();
|
|
88
|
+
const nodeGeometry = /* @__PURE__ */ new Map();
|
|
260
89
|
for (const name of g.nodes()) {
|
|
261
90
|
const pos = gNode(g, name);
|
|
262
91
|
if (pos)
|
|
263
92
|
nodeGeometry.set(name, {
|
|
264
93
|
y: pos.y,
|
|
265
94
|
width: pos.width,
|
|
266
|
-
height: pos.height
|
|
95
|
+
height: pos.height
|
|
267
96
|
});
|
|
268
97
|
}
|
|
269
|
-
|
|
270
|
-
// Group nodes by rank
|
|
271
|
-
const rankMap = new Map<number, string[]>();
|
|
98
|
+
const rankMap = /* @__PURE__ */ new Map();
|
|
272
99
|
for (const name of g.nodes()) {
|
|
273
100
|
const pos = gNode(g, name);
|
|
274
101
|
if (!pos) continue;
|
|
275
102
|
const rankY = Math.round(pos.y);
|
|
276
103
|
if (!rankMap.has(rankY)) rankMap.set(rankY, []);
|
|
277
|
-
rankMap.get(rankY)
|
|
104
|
+
rankMap.get(rankY).push(name);
|
|
278
105
|
}
|
|
279
|
-
|
|
280
|
-
// Sort each rank by current x position
|
|
281
106
|
for (const [, rankNodes] of rankMap) {
|
|
282
107
|
rankNodes.sort((a, b) => gNode(g, a).x - gNode(g, b).x);
|
|
283
108
|
}
|
|
284
|
-
|
|
285
109
|
let anyMoved = false;
|
|
286
|
-
|
|
287
110
|
for (const [, rankNodes] of rankMap) {
|
|
288
111
|
if (rankNodes.length < 2) continue;
|
|
289
|
-
|
|
290
|
-
// When groups exist, partition rank nodes by group and only permute within groups
|
|
291
|
-
const partitions: string[][] = [];
|
|
112
|
+
const partitions = [];
|
|
292
113
|
if (nodeGroupMap && nodeGroupMap.size > 0) {
|
|
293
|
-
const groupBuckets = new Map
|
|
294
|
-
const ungrouped
|
|
114
|
+
const groupBuckets = /* @__PURE__ */ new Map();
|
|
115
|
+
const ungrouped = [];
|
|
295
116
|
for (const name of rankNodes) {
|
|
296
117
|
const grp = nodeGroupMap.get(name);
|
|
297
118
|
if (grp) {
|
|
298
119
|
if (!groupBuckets.has(grp)) groupBuckets.set(grp, []);
|
|
299
|
-
groupBuckets.get(grp)
|
|
120
|
+
groupBuckets.get(grp).push(name);
|
|
300
121
|
} else {
|
|
301
122
|
ungrouped.push(name);
|
|
302
123
|
}
|
|
@@ -308,40 +129,28 @@ function reduceCrossings(
|
|
|
308
129
|
} else {
|
|
309
130
|
partitions.push(rankNodes);
|
|
310
131
|
}
|
|
311
|
-
|
|
312
132
|
for (const partition of partitions) {
|
|
313
133
|
if (partition.length < 2) continue;
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
const xSlots = partition
|
|
317
|
-
.map((name) => gNode(g, name).x)
|
|
318
|
-
.sort((a, b) => a - b);
|
|
319
|
-
|
|
320
|
-
// Build position map snapshot
|
|
321
|
-
const basePositions = new Map<string, number>();
|
|
134
|
+
const xSlots = partition.map((name) => gNode(g, name).x).sort((a, b) => a - b);
|
|
135
|
+
const basePositions = /* @__PURE__ */ new Map();
|
|
322
136
|
for (const name of g.nodes()) {
|
|
323
137
|
const pos = gNode(g, name);
|
|
324
138
|
if (pos) basePositions.set(name, pos.x);
|
|
325
139
|
}
|
|
326
|
-
|
|
327
|
-
// Current penalty
|
|
328
140
|
const currentPenalty = computeEdgePenalty(
|
|
329
141
|
edgeList,
|
|
330
142
|
basePositions,
|
|
331
143
|
degrees,
|
|
332
144
|
nodeGeometry
|
|
333
145
|
);
|
|
334
|
-
|
|
335
|
-
// Try permutations (feasible for partition sizes ≤ 8)
|
|
336
146
|
let bestPerm = [...partition];
|
|
337
147
|
let bestPenalty = currentPenalty;
|
|
338
|
-
|
|
339
148
|
if (partition.length <= 8) {
|
|
340
149
|
const perms = permutations(partition);
|
|
341
150
|
for (const perm of perms) {
|
|
342
151
|
const testPositions = new Map(basePositions);
|
|
343
152
|
for (let i = 0; i < perm.length; i++) {
|
|
344
|
-
testPositions.set(perm[i]
|
|
153
|
+
testPositions.set(perm[i], xSlots[i]);
|
|
345
154
|
}
|
|
346
155
|
const penalty = computeEdgePenalty(
|
|
347
156
|
edgeList,
|
|
@@ -355,7 +164,6 @@ function reduceCrossings(
|
|
|
355
164
|
}
|
|
356
165
|
}
|
|
357
166
|
} else {
|
|
358
|
-
// For large partitions, use adjacent swap
|
|
359
167
|
const workingOrder = [...partition];
|
|
360
168
|
let improved = true;
|
|
361
169
|
let passes = 0;
|
|
@@ -365,7 +173,7 @@ function reduceCrossings(
|
|
|
365
173
|
for (let i = 0; i < workingOrder.length - 1; i++) {
|
|
366
174
|
const testPositions = new Map(basePositions);
|
|
367
175
|
for (let k = 0; k < workingOrder.length; k++) {
|
|
368
|
-
testPositions.set(workingOrder[k]
|
|
176
|
+
testPositions.set(workingOrder[k], xSlots[k]);
|
|
369
177
|
}
|
|
370
178
|
const before = computeEdgePenalty(
|
|
371
179
|
edgeList,
|
|
@@ -373,14 +181,13 @@ function reduceCrossings(
|
|
|
373
181
|
degrees,
|
|
374
182
|
nodeGeometry
|
|
375
183
|
);
|
|
376
|
-
|
|
377
184
|
[workingOrder[i], workingOrder[i + 1]] = [
|
|
378
|
-
workingOrder[i + 1]
|
|
379
|
-
workingOrder[i]
|
|
185
|
+
workingOrder[i + 1],
|
|
186
|
+
workingOrder[i]
|
|
380
187
|
];
|
|
381
188
|
const testPositions2 = new Map(basePositions);
|
|
382
189
|
for (let k = 0; k < workingOrder.length; k++) {
|
|
383
|
-
testPositions2.set(workingOrder[k]
|
|
190
|
+
testPositions2.set(workingOrder[k], xSlots[k]);
|
|
384
191
|
}
|
|
385
192
|
const after = computeEdgePenalty(
|
|
386
193
|
edgeList,
|
|
@@ -388,7 +195,6 @@ function reduceCrossings(
|
|
|
388
195
|
degrees,
|
|
389
196
|
nodeGeometry
|
|
390
197
|
);
|
|
391
|
-
|
|
392
198
|
if (after < before) {
|
|
393
199
|
improved = true;
|
|
394
200
|
if (after < bestPenalty) {
|
|
@@ -397,28 +203,23 @@ function reduceCrossings(
|
|
|
397
203
|
}
|
|
398
204
|
} else {
|
|
399
205
|
[workingOrder[i], workingOrder[i + 1]] = [
|
|
400
|
-
workingOrder[i + 1]
|
|
401
|
-
workingOrder[i]
|
|
206
|
+
workingOrder[i + 1],
|
|
207
|
+
workingOrder[i]
|
|
402
208
|
];
|
|
403
209
|
}
|
|
404
210
|
}
|
|
405
211
|
}
|
|
406
212
|
}
|
|
407
|
-
|
|
408
|
-
// Apply best permutation if it differs from current
|
|
409
213
|
if (bestPerm.some((name, i) => name !== partition[i])) {
|
|
410
214
|
for (let i = 0; i < bestPerm.length; i++) {
|
|
411
|
-
gNode(g, bestPerm[i]
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
if (rankIdx >= 0) rankNodes[rankIdx] = bestPerm[i]!;
|
|
215
|
+
gNode(g, bestPerm[i]).x = xSlots[i];
|
|
216
|
+
const rankIdx = rankNodes.indexOf(partition[i]);
|
|
217
|
+
if (rankIdx >= 0) rankNodes[rankIdx] = bestPerm[i];
|
|
415
218
|
}
|
|
416
219
|
anyMoved = true;
|
|
417
220
|
}
|
|
418
221
|
}
|
|
419
222
|
}
|
|
420
|
-
|
|
421
|
-
// Recompute edge waypoints if any positions changed
|
|
422
223
|
if (anyMoved) {
|
|
423
224
|
for (const edge of edgeList) {
|
|
424
225
|
const edgeData = gEdge(g, edge.source, edge.target);
|
|
@@ -426,73 +227,45 @@ function reduceCrossings(
|
|
|
426
227
|
const srcPos = gNode(g, edge.source);
|
|
427
228
|
const tgtPos = gNode(g, edge.target);
|
|
428
229
|
if (!srcPos || !tgtPos) continue;
|
|
429
|
-
|
|
430
230
|
const srcBottom = { x: srcPos.x, y: srcPos.y + srcPos.height / 2 };
|
|
431
231
|
const tgtTop = { x: tgtPos.x, y: tgtPos.y - tgtPos.height / 2 };
|
|
432
232
|
const midY = (srcBottom.y + tgtTop.y) / 2;
|
|
433
|
-
|
|
434
233
|
edgeData.points = [
|
|
435
234
|
srcBottom,
|
|
436
235
|
{ x: srcBottom.x, y: midY },
|
|
437
236
|
{ x: tgtTop.x, y: midY },
|
|
438
|
-
tgtTop
|
|
237
|
+
tgtTop
|
|
439
238
|
];
|
|
440
239
|
}
|
|
441
240
|
}
|
|
442
241
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
function permutations<T>(arr: T[]): T[][] {
|
|
446
|
-
const result: T[][] = [];
|
|
242
|
+
function permutations(arr) {
|
|
243
|
+
const result = [];
|
|
447
244
|
const a = [...arr];
|
|
448
245
|
const n = a.length;
|
|
449
|
-
const c = new Array(n).fill(0)
|
|
450
|
-
|
|
246
|
+
const c = new Array(n).fill(0);
|
|
451
247
|
result.push([...a]);
|
|
452
|
-
|
|
453
248
|
let i = 0;
|
|
454
249
|
while (i < n) {
|
|
455
|
-
if (c[i]
|
|
250
|
+
if (c[i] < i) {
|
|
456
251
|
if (i % 2 === 0) {
|
|
457
|
-
[a[0], a[i]] = [a[i]
|
|
252
|
+
[a[0], a[i]] = [a[i], a[0]];
|
|
458
253
|
} else {
|
|
459
|
-
[a[c[i]
|
|
254
|
+
[a[c[i]], a[i]] = [a[i], a[c[i]]];
|
|
460
255
|
}
|
|
461
256
|
result.push([...a]);
|
|
462
|
-
c[i]
|
|
257
|
+
c[i]++;
|
|
463
258
|
i = 0;
|
|
464
259
|
} else {
|
|
465
260
|
c[i] = 0;
|
|
466
261
|
i++;
|
|
467
262
|
}
|
|
468
263
|
}
|
|
469
|
-
|
|
470
264
|
return result;
|
|
471
265
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
// ============================================================
|
|
476
|
-
|
|
477
|
-
export interface ContextRelationship {
|
|
478
|
-
sourceName: string;
|
|
479
|
-
targetName: string;
|
|
480
|
-
label?: string;
|
|
481
|
-
technology?: string;
|
|
482
|
-
arrowType: C4ArrowType;
|
|
483
|
-
lineNumber: number;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
/**
|
|
487
|
-
* Build a map from element name → top-level ancestor name.
|
|
488
|
-
* Top-level elements map to themselves.
|
|
489
|
-
*/
|
|
490
|
-
function buildOwnershipMap(
|
|
491
|
-
elements: readonly C4Element[]
|
|
492
|
-
): Map<string, string> {
|
|
493
|
-
const map = new Map<string, string>();
|
|
494
|
-
|
|
495
|
-
function walk(el: C4Element, ancestor: string): void {
|
|
266
|
+
function buildOwnershipMap(elements) {
|
|
267
|
+
const map = /* @__PURE__ */ new Map();
|
|
268
|
+
function walk(el, ancestor) {
|
|
496
269
|
map.set(el.name, ancestor);
|
|
497
270
|
for (const child of el.children) {
|
|
498
271
|
walk(child, ancestor);
|
|
@@ -503,24 +276,14 @@ function buildOwnershipMap(
|
|
|
503
276
|
}
|
|
504
277
|
}
|
|
505
278
|
}
|
|
506
|
-
|
|
507
279
|
for (const el of elements) {
|
|
508
280
|
walk(el, el.name);
|
|
509
281
|
}
|
|
510
|
-
|
|
511
282
|
return map;
|
|
512
283
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
*/
|
|
517
|
-
function collectAllRelationships(
|
|
518
|
-
elements: readonly C4Element[],
|
|
519
|
-
_ownerMap: Map<string, string>
|
|
520
|
-
): { sourceName: string; rel: C4Relationship }[] {
|
|
521
|
-
const result: { sourceName: string; rel: C4Relationship }[] = [];
|
|
522
|
-
|
|
523
|
-
function walk(el: C4Element): void {
|
|
284
|
+
function collectAllRelationships(elements, _ownerMap) {
|
|
285
|
+
const result = [];
|
|
286
|
+
function walk(el) {
|
|
524
287
|
for (const rel of el.relationships) {
|
|
525
288
|
result.push({ sourceName: el.name, rel });
|
|
526
289
|
}
|
|
@@ -533,96 +296,59 @@ function collectAllRelationships(
|
|
|
533
296
|
}
|
|
534
297
|
}
|
|
535
298
|
}
|
|
536
|
-
|
|
537
299
|
for (const el of elements) {
|
|
538
300
|
walk(el);
|
|
539
301
|
}
|
|
540
|
-
|
|
541
302
|
return result;
|
|
542
303
|
}
|
|
543
|
-
|
|
544
|
-
/**
|
|
545
|
-
* Roll up container/component-level relationships to system-to-system edges.
|
|
546
|
-
* - Skips internal relationships (same top-level ancestor).
|
|
547
|
-
* - Deduplicates: same source→target pair keeps only one (first seen).
|
|
548
|
-
* - Explicit system-level relationships override rolled-up ones.
|
|
549
|
-
*/
|
|
550
|
-
export function rollUpContextRelationships(
|
|
551
|
-
parsed: ParsedC4
|
|
552
|
-
): ContextRelationship[] {
|
|
304
|
+
function rollUpContextRelationships(parsed) {
|
|
553
305
|
const ownerMap = buildOwnershipMap(parsed.elements);
|
|
554
306
|
const allRels = collectAllRelationships(parsed.elements, ownerMap);
|
|
555
|
-
|
|
556
|
-
// Also include orphan relationships
|
|
557
|
-
// Separate system-level (explicit) from nested (rolled-up)
|
|
558
307
|
const topLevelNames = new Set(parsed.elements.map((e) => e.name));
|
|
559
|
-
const explicitKeys = new Set
|
|
560
|
-
const explicit
|
|
561
|
-
const nested
|
|
562
|
-
|
|
308
|
+
const explicitKeys = /* @__PURE__ */ new Set();
|
|
309
|
+
const explicit = [];
|
|
310
|
+
const nested = [];
|
|
563
311
|
for (const { sourceName, rel } of allRels) {
|
|
564
312
|
const sourceAncestor = ownerMap.get(sourceName) ?? sourceName;
|
|
565
313
|
const targetAncestor = ownerMap.get(rel.target) ?? rel.target;
|
|
566
|
-
|
|
567
|
-
// Skip internal relationships (both in same system)
|
|
568
314
|
if (sourceAncestor === targetAncestor) continue;
|
|
569
|
-
|
|
570
|
-
const entry: ContextRelationship = {
|
|
315
|
+
const entry = {
|
|
571
316
|
sourceName: sourceAncestor,
|
|
572
317
|
targetName: targetAncestor,
|
|
573
|
-
...
|
|
574
|
-
...
|
|
318
|
+
...rel.label !== void 0 && { label: rel.label },
|
|
319
|
+
...rel.technology !== void 0 && { technology: rel.technology },
|
|
575
320
|
arrowType: rel.arrowType,
|
|
576
|
-
lineNumber: rel.lineNumber
|
|
321
|
+
lineNumber: rel.lineNumber
|
|
577
322
|
};
|
|
578
|
-
|
|
579
|
-
// Check if source is a top-level element (explicit system-level rel)
|
|
580
323
|
if (topLevelNames.has(sourceName) && sourceName === sourceAncestor) {
|
|
581
|
-
const key = `${sourceAncestor}
|
|
324
|
+
const key = `${sourceAncestor}\u2192${targetAncestor}`;
|
|
582
325
|
explicitKeys.add(key);
|
|
583
326
|
explicit.push(entry);
|
|
584
327
|
} else {
|
|
585
328
|
nested.push(entry);
|
|
586
329
|
}
|
|
587
330
|
}
|
|
588
|
-
|
|
589
|
-
// Deduplicate: explicit overrides rolled-up
|
|
590
331
|
const result = [...explicit];
|
|
591
332
|
const seenKeys = new Set(explicitKeys);
|
|
592
|
-
|
|
593
333
|
for (const rel of nested) {
|
|
594
|
-
const key = `${rel.sourceName}
|
|
334
|
+
const key = `${rel.sourceName}\u2192${rel.targetName}`;
|
|
595
335
|
if (!seenKeys.has(key)) {
|
|
596
336
|
seenKeys.add(key);
|
|
597
337
|
result.push(rel);
|
|
598
338
|
}
|
|
599
339
|
}
|
|
600
|
-
|
|
601
340
|
return result;
|
|
602
341
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
// Tag Group Color Resolution
|
|
606
|
-
// ============================================================
|
|
607
|
-
|
|
608
|
-
function resolveNodeColor(
|
|
609
|
-
el: C4Element,
|
|
610
|
-
tagGroups: readonly TagGroup[],
|
|
611
|
-
activeGroupName: string | null,
|
|
612
|
-
ancestors?: readonly C4Element[]
|
|
613
|
-
): string | undefined {
|
|
614
|
-
// Check metadata for explicit color
|
|
615
|
-
const colorMeta = el.metadata['color'];
|
|
342
|
+
function resolveNodeColor(el, tagGroups, activeGroupName, ancestors) {
|
|
343
|
+
const colorMeta = el.metadata["color"];
|
|
616
344
|
if (colorMeta) return colorMeta;
|
|
617
|
-
if (!activeGroupName) return
|
|
618
|
-
|
|
345
|
+
if (!activeGroupName) return void 0;
|
|
619
346
|
const group = tagGroups.find(
|
|
620
347
|
(g) => g.name.toLowerCase() === activeGroupName.toLowerCase()
|
|
621
348
|
);
|
|
622
|
-
if (!group) return
|
|
349
|
+
if (!group) return void 0;
|
|
623
350
|
const key = tagAttrKey(group.name);
|
|
624
|
-
|
|
625
|
-
let metaValue: string | undefined = el.metadata[key];
|
|
351
|
+
let metaValue = el.metadata[key];
|
|
626
352
|
if (!metaValue && ancestors) {
|
|
627
353
|
for (const ancestor of ancestors) {
|
|
628
354
|
metaValue = ancestor.metadata[key];
|
|
@@ -630,185 +356,105 @@ function resolveNodeColor(
|
|
|
630
356
|
}
|
|
631
357
|
}
|
|
632
358
|
const resolvedValue = metaValue ?? group.defaultValue;
|
|
633
|
-
if (!resolvedValue) return
|
|
634
|
-
return (
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
)?.color ?? '#999999'
|
|
638
|
-
);
|
|
359
|
+
if (!resolvedValue) return "#999999";
|
|
360
|
+
return group.entries.find(
|
|
361
|
+
(e) => e.value.toLowerCase() === resolvedValue.toLowerCase()
|
|
362
|
+
)?.color ?? "#999999";
|
|
639
363
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
/** Keys to exclude from the below-divider metadata display. */
|
|
646
|
-
const META_EXCLUDE_KEYS = new Set([
|
|
647
|
-
'description',
|
|
648
|
-
'tech',
|
|
649
|
-
'technology',
|
|
650
|
-
'is a',
|
|
364
|
+
var META_EXCLUDE_KEYS = /* @__PURE__ */ new Set([
|
|
365
|
+
"description",
|
|
366
|
+
"tech",
|
|
367
|
+
"technology",
|
|
368
|
+
"is a"
|
|
651
369
|
]);
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
): { key: string; value: string }[] {
|
|
657
|
-
const entries: { key: string; value: string }[] = [];
|
|
658
|
-
// Technology first
|
|
659
|
-
const tech = metadata['tech'] ?? metadata['technology'];
|
|
660
|
-
if (tech) entries.push({ key: 'Technology', value: tech });
|
|
661
|
-
// Then other metadata (tag groups, etc.)
|
|
370
|
+
function collectCardMetadata(metadata) {
|
|
371
|
+
const entries = [];
|
|
372
|
+
const tech = metadata["tech"] ?? metadata["technology"];
|
|
373
|
+
if (tech) entries.push({ key: "Technology", value: tech });
|
|
662
374
|
for (const [k, v] of Object.entries(metadata)) {
|
|
663
375
|
if (META_EXCLUDE_KEYS.has(k.toLowerCase())) continue;
|
|
664
|
-
// Capitalize key for display
|
|
665
376
|
const displayKey = k.charAt(0).toUpperCase() + k.slice(1);
|
|
666
377
|
entries.push({ key: displayKey, value: v });
|
|
667
378
|
}
|
|
668
379
|
return entries;
|
|
669
380
|
}
|
|
670
|
-
|
|
671
|
-
export function computeC4NodeDimensions(
|
|
672
|
-
el: C4Element,
|
|
673
|
-
options?: { showTechnology?: boolean }
|
|
674
|
-
): { width: number; height: number } {
|
|
675
|
-
// Width: based on name length, clamped
|
|
381
|
+
function computeC4NodeDimensions(el, options) {
|
|
676
382
|
const nameWidth = measureText(el.name, NAME_FONT_SIZE) + CARD_H_PAD * 2;
|
|
677
383
|
let width = Math.max(MIN_NODE_WIDTH, Math.min(MAX_NODE_WIDTH, nameWidth));
|
|
678
|
-
|
|
679
384
|
if (options?.showTechnology) {
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
const desc = el.description?.join('\n');
|
|
685
|
-
if (desc) {
|
|
385
|
+
let height2 = CARD_V_PAD + NAME_HEIGHT;
|
|
386
|
+
const desc2 = el.description?.join("\n");
|
|
387
|
+
if (desc2) {
|
|
686
388
|
const contentWidth = width - CARD_H_PAD * 2;
|
|
687
|
-
const lines = wrapTextToWidth(
|
|
688
|
-
|
|
389
|
+
const lines = wrapTextToWidth(desc2, DESC_FONT_SIZE, contentWidth);
|
|
390
|
+
height2 += lines.length * DESC_LINE_HEIGHT;
|
|
689
391
|
}
|
|
690
|
-
|
|
691
|
-
// Metadata rows below divider
|
|
692
392
|
const metaEntries = collectCardMetadata(el.metadata);
|
|
693
393
|
if (metaEntries.length > 0) {
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
// Widen card if metadata rows need more space
|
|
394
|
+
height2 += DIVIDER_GAP;
|
|
395
|
+
height2 += metaEntries.length * META_LINE_HEIGHT;
|
|
697
396
|
const maxMetaWidth = Math.max(
|
|
698
397
|
...metaEntries.map(
|
|
699
|
-
(e) =>
|
|
700
|
-
measureText(`${e.key}: ${e.value}`, META_FONT_SIZE) + CARD_H_PAD * 2
|
|
398
|
+
(e) => measureText(`${e.key}: ${e.value}`, META_FONT_SIZE) + CARD_H_PAD * 2
|
|
701
399
|
)
|
|
702
400
|
);
|
|
703
401
|
if (maxMetaWidth > width) width = Math.min(MAX_NODE_WIDTH, maxMetaWidth);
|
|
704
402
|
}
|
|
705
|
-
|
|
706
|
-
height
|
|
707
|
-
return { width, height };
|
|
403
|
+
height2 += CARD_V_PAD;
|
|
404
|
+
return { width, height: height2 };
|
|
708
405
|
}
|
|
709
|
-
|
|
710
|
-
// Context card layout: type + name | divider | description
|
|
711
406
|
let height = CARD_V_PAD + TYPE_LABEL_HEIGHT + DIVIDER_GAP + NAME_HEIGHT;
|
|
712
|
-
|
|
713
|
-
const desc = el.description?.join('\n');
|
|
407
|
+
const desc = el.description?.join("\n");
|
|
714
408
|
if (desc) {
|
|
715
409
|
const contentWidth = width - CARD_H_PAD * 2;
|
|
716
410
|
const lines = wrapTextToWidth(desc, DESC_FONT_SIZE, contentWidth);
|
|
717
411
|
height += lines.length * DESC_LINE_HEIGHT;
|
|
718
412
|
}
|
|
719
|
-
|
|
720
413
|
height += CARD_V_PAD;
|
|
721
|
-
|
|
722
414
|
return { width, height };
|
|
723
415
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
// Legend Helpers
|
|
727
|
-
// ============================================================
|
|
728
|
-
|
|
729
|
-
function computeLegendGroups(
|
|
730
|
-
tagGroups: readonly TagGroup[]
|
|
731
|
-
): Writable<C4LegendGroup>[] {
|
|
732
|
-
const result: Writable<C4LegendGroup>[] = [];
|
|
733
|
-
|
|
416
|
+
function computeLegendGroups(tagGroups) {
|
|
417
|
+
const result = [];
|
|
734
418
|
for (const group of tagGroups) {
|
|
735
|
-
const entries
|
|
419
|
+
const entries = [];
|
|
736
420
|
for (const entry of group.entries) {
|
|
737
421
|
entries.push({ value: entry.value, color: entry.color });
|
|
738
422
|
}
|
|
739
423
|
if (entries.length === 0) continue;
|
|
740
|
-
|
|
741
|
-
// Compute pill width: group name + entries
|
|
742
|
-
const nameW =
|
|
743
|
-
measureLegendText(group.name, LEGEND_PILL_FONT_SIZE) +
|
|
744
|
-
LEGEND_PILL_PAD * 2;
|
|
424
|
+
const nameW = measureLegendText(group.name, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD * 2;
|
|
745
425
|
let capsuleW = LEGEND_CAPSULE_PAD;
|
|
746
426
|
for (const e of entries) {
|
|
747
|
-
capsuleW +=
|
|
748
|
-
LEGEND_DOT_R * 2 +
|
|
749
|
-
LEGEND_ENTRY_DOT_GAP +
|
|
750
|
-
measureLegendText(e.value, LEGEND_ENTRY_FONT_SIZE) +
|
|
751
|
-
LEGEND_ENTRY_TRAIL;
|
|
427
|
+
capsuleW += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + measureLegendText(e.value, LEGEND_ENTRY_FONT_SIZE) + LEGEND_ENTRY_TRAIL;
|
|
752
428
|
}
|
|
753
429
|
capsuleW += LEGEND_CAPSULE_PAD;
|
|
754
|
-
|
|
755
430
|
result.push({
|
|
756
431
|
name: group.name,
|
|
757
432
|
entries,
|
|
758
433
|
x: 0,
|
|
759
434
|
y: 0,
|
|
760
435
|
width: nameW + capsuleW,
|
|
761
|
-
height: LEGEND_HEIGHT
|
|
436
|
+
height: LEGEND_HEIGHT
|
|
762
437
|
});
|
|
763
438
|
}
|
|
764
|
-
|
|
765
439
|
return result;
|
|
766
440
|
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
// Adaptive Spacing
|
|
770
|
-
// ============================================================
|
|
771
|
-
|
|
772
|
-
/**
|
|
773
|
-
* Compute dagre graph spacing based on edge density.
|
|
774
|
-
* Nodes with high fan-out (many labeled edges) need more inter-rank
|
|
775
|
-
* space so labels don't overlap. Returns ranksep and edgesep.
|
|
776
|
-
*/
|
|
777
|
-
function computeAdaptiveSpacing(
|
|
778
|
-
edges: { sourceName: string; label?: string; technology?: string }[]
|
|
779
|
-
): { nodesep: number; ranksep: number; edgesep: number } {
|
|
780
|
-
// Count max labeled out-degree for any single source node
|
|
781
|
-
const outDegree = new Map<string, number>();
|
|
441
|
+
function computeAdaptiveSpacing(edges) {
|
|
442
|
+
const outDegree = /* @__PURE__ */ new Map();
|
|
782
443
|
for (const edge of edges) {
|
|
783
444
|
if (edge.label || edge.technology) {
|
|
784
445
|
outDegree.set(edge.sourceName, (outDegree.get(edge.sourceName) ?? 0) + 1);
|
|
785
446
|
}
|
|
786
447
|
}
|
|
787
448
|
const maxFanOut = Math.max(0, ...outDegree.values());
|
|
788
|
-
|
|
789
|
-
// Scale spacing: each additional fan-out edge needs more room for its label.
|
|
790
|
-
// nodesep: wider horizontal gaps give fan-out edges distinct angles,
|
|
791
|
-
// making it clear which label belongs to which line.
|
|
792
449
|
const nodesep = Math.max(80, 60 + maxFanOut * 20);
|
|
793
450
|
const ranksep = Math.max(140, 100 + maxFanOut * 35);
|
|
794
451
|
const edgesep = Math.max(30, 20 + maxFanOut * 8);
|
|
795
|
-
|
|
796
452
|
return { nodesep, ranksep, edgesep };
|
|
797
453
|
}
|
|
798
|
-
|
|
799
|
-
// ============================================================
|
|
800
|
-
// Main Layout
|
|
801
|
-
// ============================================================
|
|
802
|
-
|
|
803
|
-
export function layoutC4Context(
|
|
804
|
-
parsed: ParsedC4,
|
|
805
|
-
activeTagGroup?: string | null
|
|
806
|
-
): C4LayoutResult {
|
|
807
|
-
// Filter to person + system elements only
|
|
454
|
+
function layoutC4Context(parsed, activeTagGroup) {
|
|
808
455
|
const contextElements = parsed.elements.filter(
|
|
809
|
-
(el) => el.type ===
|
|
456
|
+
(el) => el.type === "person" || el.type === "system"
|
|
810
457
|
);
|
|
811
|
-
|
|
812
458
|
if (contextElements.length === 0) {
|
|
813
459
|
return {
|
|
814
460
|
nodes: [],
|
|
@@ -816,108 +462,80 @@ export function layoutC4Context(
|
|
|
816
462
|
legend: [],
|
|
817
463
|
groupBoundaries: [],
|
|
818
464
|
width: 0,
|
|
819
|
-
height: 0
|
|
465
|
+
height: 0
|
|
820
466
|
};
|
|
821
467
|
}
|
|
822
|
-
|
|
823
|
-
// Roll up relationships
|
|
824
468
|
const contextRels = rollUpContextRelationships(parsed);
|
|
825
|
-
|
|
826
|
-
// Compute adaptive spacing based on edge density
|
|
827
469
|
const spacing = computeAdaptiveSpacing(contextRels);
|
|
828
|
-
|
|
829
|
-
// Create dagre graph
|
|
830
470
|
const g = new dagre.graphlib.Graph();
|
|
831
471
|
g.setGraph({
|
|
832
|
-
rankdir:
|
|
472
|
+
rankdir: "TB",
|
|
833
473
|
nodesep: spacing.nodesep,
|
|
834
474
|
ranksep: spacing.ranksep,
|
|
835
|
-
edgesep: spacing.edgesep
|
|
475
|
+
edgesep: spacing.edgesep
|
|
836
476
|
});
|
|
837
477
|
g.setDefaultEdgeLabel(() => ({}));
|
|
838
|
-
|
|
839
|
-
// Add nodes
|
|
840
|
-
const nameToElement = new Map<string, C4Element>();
|
|
478
|
+
const nameToElement = /* @__PURE__ */ new Map();
|
|
841
479
|
for (const el of contextElements) {
|
|
842
480
|
nameToElement.set(el.name, el);
|
|
843
481
|
const dims = computeC4NodeDimensions(el);
|
|
844
482
|
g.setNode(el.name, { width: dims.width, height: dims.height });
|
|
845
483
|
}
|
|
846
|
-
|
|
847
|
-
// Add edges — only between known nodes
|
|
848
|
-
const validRels: ContextRelationship[] = [];
|
|
484
|
+
const validRels = [];
|
|
849
485
|
for (const rel of contextRels) {
|
|
850
|
-
if (
|
|
851
|
-
nameToElement.has(rel.sourceName) &&
|
|
852
|
-
nameToElement.has(rel.targetName)
|
|
853
|
-
) {
|
|
486
|
+
if (nameToElement.has(rel.sourceName) && nameToElement.has(rel.targetName)) {
|
|
854
487
|
validRels.push(rel);
|
|
855
|
-
g.setEdge(rel.sourceName, rel.targetName, { label: rel.label ??
|
|
488
|
+
g.setEdge(rel.sourceName, rel.targetName, { label: rel.label ?? "" });
|
|
856
489
|
}
|
|
857
490
|
}
|
|
858
|
-
|
|
859
|
-
// Run layout
|
|
860
491
|
dagre.layout(g);
|
|
861
|
-
|
|
862
|
-
// Post-dagre crossing reduction
|
|
863
492
|
reduceCrossings(
|
|
864
493
|
g,
|
|
865
494
|
validRels.map((r) => ({ source: r.sourceName, target: r.targetName }))
|
|
866
495
|
);
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
const nodes: Writable<C4LayoutNode>[] = contextElements.map(
|
|
870
|
-
(el): Writable<C4LayoutNode> => {
|
|
496
|
+
const nodes = contextElements.map(
|
|
497
|
+
(el) => {
|
|
871
498
|
const pos = g.node(el.name);
|
|
872
499
|
const color = resolveNodeColor(
|
|
873
500
|
el,
|
|
874
501
|
parsed.tagGroups,
|
|
875
502
|
activeTagGroup ?? null
|
|
876
503
|
);
|
|
877
|
-
const hasContainers =
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
const description = el.description?.join('\n');
|
|
881
|
-
const drillable = hasContainers || el.importPath ? true : undefined;
|
|
504
|
+
const hasContainers = el.children.some((c) => c.type === "container") || el.groups.some((g2) => g2.children.some((c) => c.type === "container"));
|
|
505
|
+
const description = el.description?.join("\n");
|
|
506
|
+
const drillable = hasContainers || el.importPath ? true : void 0;
|
|
882
507
|
return {
|
|
883
508
|
id: el.name,
|
|
884
509
|
name: el.name,
|
|
885
|
-
type: el.type
|
|
886
|
-
...
|
|
510
|
+
type: el.type,
|
|
511
|
+
...description !== void 0 && { description },
|
|
887
512
|
metadata: el.metadata,
|
|
888
513
|
lineNumber: el.lineNumber,
|
|
889
|
-
...
|
|
890
|
-
...
|
|
891
|
-
...
|
|
514
|
+
...color !== void 0 && { color },
|
|
515
|
+
...drillable !== void 0 && { drillable },
|
|
516
|
+
...el.importPath !== void 0 && { importPath: el.importPath },
|
|
892
517
|
x: pos.x,
|
|
893
518
|
y: pos.y,
|
|
894
519
|
width: pos.width,
|
|
895
|
-
height: pos.height
|
|
520
|
+
height: pos.height
|
|
896
521
|
};
|
|
897
522
|
}
|
|
898
523
|
);
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
const edges: MutableC4LayoutEdge[] = validRels.map(
|
|
902
|
-
(rel): MutableC4LayoutEdge => {
|
|
524
|
+
const edges = validRels.map(
|
|
525
|
+
(rel) => {
|
|
903
526
|
const edgeData = g.edge(rel.sourceName, rel.targetName);
|
|
904
527
|
return {
|
|
905
528
|
source: rel.sourceName,
|
|
906
529
|
target: rel.targetName,
|
|
907
530
|
arrowType: rel.arrowType,
|
|
908
|
-
...
|
|
909
|
-
...
|
|
531
|
+
...rel.label !== void 0 && { label: rel.label },
|
|
532
|
+
...rel.technology !== void 0 && { technology: rel.technology },
|
|
910
533
|
lineNumber: rel.lineNumber,
|
|
911
|
-
points: edgeData?.points ?? []
|
|
534
|
+
points: edgeData?.points ?? []
|
|
912
535
|
};
|
|
913
536
|
}
|
|
914
537
|
);
|
|
915
|
-
|
|
916
|
-
// Compute bounding box of all content (nodes + edge points)
|
|
917
|
-
let minX = Infinity,
|
|
918
|
-
minY = Infinity,
|
|
919
|
-
maxX = -Infinity,
|
|
920
|
-
maxY = -Infinity;
|
|
538
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
921
539
|
for (const node of nodes) {
|
|
922
540
|
const left = node.x - node.width / 2;
|
|
923
541
|
const top = node.y - node.height / 2;
|
|
@@ -936,8 +554,6 @@ export function layoutC4Context(
|
|
|
936
554
|
if (pt.y > maxY) maxY = pt.y;
|
|
937
555
|
}
|
|
938
556
|
}
|
|
939
|
-
|
|
940
|
-
// Shift everything so content starts at (MARGIN, MARGIN)
|
|
941
557
|
if (nodes.length > 0) {
|
|
942
558
|
const shiftX = MARGIN - minX;
|
|
943
559
|
const shiftY = MARGIN - minY;
|
|
@@ -952,15 +568,9 @@ export function layoutC4Context(
|
|
|
952
568
|
}
|
|
953
569
|
}
|
|
954
570
|
}
|
|
955
|
-
|
|
956
571
|
let totalWidth = nodes.length > 0 ? maxX - minX + MARGIN * 2 : 0;
|
|
957
572
|
let totalHeight = nodes.length > 0 ? maxY - minY + MARGIN * 2 : 0;
|
|
958
|
-
|
|
959
|
-
// Legend: show all defined tag groups and entries so users see the full
|
|
960
|
-
// tag vocabulary regardless of which elements are visible at this view level.
|
|
961
573
|
const legendGroups = computeLegendGroups(parsed.tagGroups);
|
|
962
|
-
|
|
963
|
-
// Position legend below diagram
|
|
964
574
|
if (legendGroups.length > 0) {
|
|
965
575
|
const legendY = totalHeight + MARGIN;
|
|
966
576
|
let legendX = MARGIN;
|
|
@@ -974,31 +584,16 @@ export function layoutC4Context(
|
|
|
974
584
|
if (legendRight > totalWidth) totalWidth = legendRight;
|
|
975
585
|
if (legendBottom > totalHeight) totalHeight = legendBottom;
|
|
976
586
|
}
|
|
977
|
-
|
|
978
587
|
return {
|
|
979
588
|
nodes,
|
|
980
589
|
edges,
|
|
981
590
|
legend: legendGroups,
|
|
982
591
|
groupBoundaries: [],
|
|
983
592
|
width: totalWidth,
|
|
984
|
-
height: totalHeight
|
|
593
|
+
height: totalHeight
|
|
985
594
|
};
|
|
986
595
|
}
|
|
987
|
-
|
|
988
|
-
// ============================================================
|
|
989
|
-
// Container-Level Layout
|
|
990
|
-
// ============================================================
|
|
991
|
-
|
|
992
|
-
/**
|
|
993
|
-
* Layout containers within a specific system, plus external elements
|
|
994
|
-
* that have relationships with those containers.
|
|
995
|
-
*/
|
|
996
|
-
export function layoutC4Containers(
|
|
997
|
-
parsed: ParsedC4,
|
|
998
|
-
systemName: string,
|
|
999
|
-
activeTagGroup?: string | null
|
|
1000
|
-
): C4LayoutResult {
|
|
1001
|
-
// Find the system element by name
|
|
596
|
+
function layoutC4Containers(parsed, systemName, activeTagGroup) {
|
|
1002
597
|
const system = parsed.elements.find(
|
|
1003
598
|
(el) => el.name.toLowerCase() === systemName.toLowerCase()
|
|
1004
599
|
);
|
|
@@ -1009,21 +604,18 @@ export function layoutC4Containers(
|
|
|
1009
604
|
legend: [],
|
|
1010
605
|
groupBoundaries: [],
|
|
1011
606
|
width: 0,
|
|
1012
|
-
height: 0
|
|
607
|
+
height: 0
|
|
1013
608
|
};
|
|
1014
609
|
}
|
|
1015
|
-
|
|
1016
|
-
// Collect all containers: direct children + group children
|
|
1017
|
-
const containers: C4Element[] = [];
|
|
610
|
+
const containers = [];
|
|
1018
611
|
for (const child of system.children) {
|
|
1019
|
-
if (child.type ===
|
|
612
|
+
if (child.type === "container") containers.push(child);
|
|
1020
613
|
}
|
|
1021
614
|
for (const group of system.groups) {
|
|
1022
615
|
for (const child of group.children) {
|
|
1023
|
-
if (child.type ===
|
|
616
|
+
if (child.type === "container") containers.push(child);
|
|
1024
617
|
}
|
|
1025
618
|
}
|
|
1026
|
-
|
|
1027
619
|
if (containers.length === 0) {
|
|
1028
620
|
return {
|
|
1029
621
|
nodes: [],
|
|
@@ -1031,23 +623,15 @@ export function layoutC4Containers(
|
|
|
1031
623
|
legend: [],
|
|
1032
624
|
groupBoundaries: [],
|
|
1033
625
|
width: 0,
|
|
1034
|
-
height: 0
|
|
626
|
+
height: 0
|
|
1035
627
|
};
|
|
1036
628
|
}
|
|
1037
|
-
|
|
1038
629
|
const containerNames = new Set(containers.map((c) => c.name.toLowerCase()));
|
|
1039
|
-
|
|
1040
|
-
// Build name → element map for all top-level elements
|
|
1041
|
-
const topElementsByName = new Map<string, C4Element>();
|
|
630
|
+
const topElementsByName = /* @__PURE__ */ new Map();
|
|
1042
631
|
for (const el of parsed.elements) {
|
|
1043
632
|
topElementsByName.set(el.name.toLowerCase(), el);
|
|
1044
633
|
}
|
|
1045
|
-
|
|
1046
|
-
// Identify external elements: targets of container relationships that aren't
|
|
1047
|
-
// in this system, OR top-level elements that target containers in this system
|
|
1048
|
-
const externalNames = new Set<string>();
|
|
1049
|
-
|
|
1050
|
-
// Forward: container → target outside this system
|
|
634
|
+
const externalNames = /* @__PURE__ */ new Set();
|
|
1051
635
|
for (const container of containers) {
|
|
1052
636
|
for (const rel of container.relationships) {
|
|
1053
637
|
const targetLower = rel.target.toLowerCase();
|
|
@@ -1056,175 +640,120 @@ export function layoutC4Containers(
|
|
|
1056
640
|
}
|
|
1057
641
|
}
|
|
1058
642
|
}
|
|
1059
|
-
|
|
1060
|
-
// Reverse: top-level elements (and their children) that target containers in this system
|
|
1061
643
|
const ownerMap = buildOwnershipMap(parsed.elements);
|
|
1062
644
|
const allRels = collectAllRelationships(parsed.elements, ownerMap);
|
|
1063
645
|
for (const { sourceName, rel } of allRels) {
|
|
1064
646
|
const sourceAncestor = ownerMap.get(sourceName) ?? sourceName;
|
|
1065
|
-
// Skip relationships from within this system
|
|
1066
647
|
if (sourceAncestor.toLowerCase() === system.name.toLowerCase()) continue;
|
|
1067
|
-
// Check if target is a container in this system
|
|
1068
648
|
if (containerNames.has(rel.target.toLowerCase())) {
|
|
1069
649
|
externalNames.add(sourceAncestor.toLowerCase());
|
|
1070
650
|
}
|
|
1071
651
|
}
|
|
1072
|
-
|
|
1073
|
-
// Resolve external elements
|
|
1074
|
-
const externals: C4Element[] = [];
|
|
652
|
+
const externals = [];
|
|
1075
653
|
for (const name of externalNames) {
|
|
1076
654
|
const el = topElementsByName.get(name);
|
|
1077
655
|
if (el) externals.push(el);
|
|
1078
656
|
}
|
|
1079
|
-
|
|
1080
|
-
// Build element-to-group mapping for compound graph
|
|
1081
|
-
const elementToGroup = new Map<
|
|
1082
|
-
string,
|
|
1083
|
-
{ name: string; lineNumber: number }
|
|
1084
|
-
>();
|
|
657
|
+
const elementToGroup = /* @__PURE__ */ new Map();
|
|
1085
658
|
for (const group of system.groups) {
|
|
1086
659
|
for (const child of group.children) {
|
|
1087
|
-
if (child.type ===
|
|
660
|
+
if (child.type === "container") {
|
|
1088
661
|
elementToGroup.set(child.name, {
|
|
1089
662
|
name: group.name,
|
|
1090
|
-
lineNumber: group.lineNumber
|
|
663
|
+
lineNumber: group.lineNumber
|
|
1091
664
|
});
|
|
1092
665
|
}
|
|
1093
666
|
}
|
|
1094
667
|
}
|
|
1095
668
|
const hasGroups = elementToGroup.size > 0;
|
|
1096
|
-
|
|
1097
|
-
// Create dagre graph — use compound when groups exist
|
|
1098
|
-
const g = hasGroups
|
|
1099
|
-
? new dagre.graphlib.Graph({ compound: true })
|
|
1100
|
-
: new dagre.graphlib.Graph();
|
|
669
|
+
const g = hasGroups ? new dagre.graphlib.Graph({ compound: true }) : new dagre.graphlib.Graph();
|
|
1101
670
|
g.setDefaultEdgeLabel(() => ({}));
|
|
1102
|
-
|
|
1103
|
-
// Add virtual group parent nodes
|
|
1104
671
|
if (hasGroups) {
|
|
1105
|
-
const seenGroups = new Set
|
|
672
|
+
const seenGroups = /* @__PURE__ */ new Set();
|
|
1106
673
|
for (const { name } of elementToGroup.values()) {
|
|
1107
674
|
if (!seenGroups.has(name)) {
|
|
1108
675
|
seenGroups.add(name);
|
|
1109
|
-
g.setNode(
|
|
676
|
+
g.setNode("__group_" + name, {});
|
|
1110
677
|
}
|
|
1111
678
|
}
|
|
1112
679
|
}
|
|
1113
|
-
|
|
1114
|
-
// Add container nodes
|
|
1115
|
-
const nameToElement = new Map<string, C4Element>();
|
|
680
|
+
const nameToElement = /* @__PURE__ */ new Map();
|
|
1116
681
|
for (const el of containers) {
|
|
1117
682
|
nameToElement.set(el.name, el);
|
|
1118
683
|
const dims = computeC4NodeDimensions(el, { showTechnology: true });
|
|
1119
684
|
g.setNode(el.name, { width: dims.width, height: dims.height });
|
|
1120
|
-
// Set group parent
|
|
1121
685
|
const grp = elementToGroup.get(el.name);
|
|
1122
686
|
if (grp) {
|
|
1123
|
-
g.setParent(el.name,
|
|
687
|
+
g.setParent(el.name, "__group_" + grp.name);
|
|
1124
688
|
}
|
|
1125
689
|
}
|
|
1126
|
-
|
|
1127
|
-
// Add external nodes
|
|
1128
690
|
for (const el of externals) {
|
|
1129
691
|
nameToElement.set(el.name, el);
|
|
1130
692
|
const dims = computeC4NodeDimensions(el);
|
|
1131
693
|
g.setNode(el.name, { width: dims.width, height: dims.height });
|
|
1132
694
|
}
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
interface ContainerRel {
|
|
1136
|
-
sourceName: string;
|
|
1137
|
-
targetName: string;
|
|
1138
|
-
label?: string;
|
|
1139
|
-
technology?: string;
|
|
1140
|
-
arrowType: C4ArrowType;
|
|
1141
|
-
lineNumber: number;
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
const containerRels: ContainerRel[] = [];
|
|
1145
|
-
const seenEdgeKeys = new Set<string>();
|
|
1146
|
-
|
|
1147
|
-
// Forward: container → target
|
|
695
|
+
const containerRels = [];
|
|
696
|
+
const seenEdgeKeys = /* @__PURE__ */ new Set();
|
|
1148
697
|
for (const container of containers) {
|
|
1149
698
|
for (const rel of container.relationships) {
|
|
1150
699
|
const targetEl = nameToElement.get(rel.target);
|
|
1151
700
|
if (!targetEl) continue;
|
|
1152
|
-
const key = `${container.name}
|
|
701
|
+
const key = `${container.name}\u2192${rel.target}`;
|
|
1153
702
|
if (!seenEdgeKeys.has(key)) {
|
|
1154
703
|
seenEdgeKeys.add(key);
|
|
1155
704
|
containerRels.push({
|
|
1156
705
|
sourceName: container.name,
|
|
1157
706
|
targetName: rel.target,
|
|
1158
|
-
...
|
|
1159
|
-
...
|
|
707
|
+
...rel.label !== void 0 && { label: rel.label },
|
|
708
|
+
...rel.technology !== void 0 && { technology: rel.technology },
|
|
1160
709
|
arrowType: rel.arrowType,
|
|
1161
|
-
lineNumber: rel.lineNumber
|
|
710
|
+
lineNumber: rel.lineNumber
|
|
1162
711
|
});
|
|
1163
712
|
}
|
|
1164
713
|
}
|
|
1165
714
|
}
|
|
1166
|
-
|
|
1167
|
-
// Reverse: external → container (find specific container-level relationships)
|
|
1168
715
|
for (const { sourceName, rel } of allRels) {
|
|
1169
716
|
const sourceAncestor = ownerMap.get(sourceName) ?? sourceName;
|
|
1170
717
|
if (sourceAncestor.toLowerCase() === system.name.toLowerCase()) continue;
|
|
1171
718
|
if (containerNames.has(rel.target.toLowerCase())) {
|
|
1172
719
|
const externalEl = nameToElement.get(sourceAncestor);
|
|
1173
720
|
if (!externalEl) continue;
|
|
1174
|
-
const key = `${sourceAncestor}
|
|
721
|
+
const key = `${sourceAncestor}\u2192${rel.target}`;
|
|
1175
722
|
if (!seenEdgeKeys.has(key)) {
|
|
1176
723
|
seenEdgeKeys.add(key);
|
|
1177
724
|
containerRels.push({
|
|
1178
725
|
sourceName: sourceAncestor,
|
|
1179
726
|
targetName: rel.target,
|
|
1180
|
-
...
|
|
1181
|
-
...
|
|
727
|
+
...rel.label !== void 0 && { label: rel.label },
|
|
728
|
+
...rel.technology !== void 0 && { technology: rel.technology },
|
|
1182
729
|
arrowType: rel.arrowType,
|
|
1183
|
-
lineNumber: rel.lineNumber
|
|
730
|
+
lineNumber: rel.lineNumber
|
|
1184
731
|
});
|
|
1185
732
|
}
|
|
1186
733
|
}
|
|
1187
734
|
}
|
|
1188
|
-
|
|
1189
|
-
// Compute adaptive spacing based on edge fan-out
|
|
1190
735
|
const spacing = computeAdaptiveSpacing(containerRels);
|
|
1191
736
|
g.setGraph({
|
|
1192
|
-
rankdir:
|
|
737
|
+
rankdir: "TB",
|
|
1193
738
|
nodesep: spacing.nodesep,
|
|
1194
739
|
ranksep: spacing.ranksep,
|
|
1195
|
-
edgesep: spacing.edgesep
|
|
740
|
+
edgesep: spacing.edgesep
|
|
1196
741
|
});
|
|
1197
|
-
|
|
1198
|
-
// Add edges to dagre
|
|
1199
742
|
for (const rel of containerRels) {
|
|
1200
|
-
if (
|
|
1201
|
-
|
|
1202
|
-
nameToElement.has(rel.targetName)
|
|
1203
|
-
) {
|
|
1204
|
-
g.setEdge(rel.sourceName, rel.targetName, { label: rel.label ?? '' });
|
|
743
|
+
if (nameToElement.has(rel.sourceName) && nameToElement.has(rel.targetName)) {
|
|
744
|
+
g.setEdge(rel.sourceName, rel.targetName, { label: rel.label ?? "" });
|
|
1205
745
|
}
|
|
1206
746
|
}
|
|
1207
|
-
|
|
1208
|
-
// Run layout
|
|
1209
747
|
dagre.layout(g);
|
|
1210
|
-
|
|
1211
|
-
// Post-dagre crossing reduction (with group-aware partitioning)
|
|
1212
|
-
const nodeGroupMap = hasGroups
|
|
1213
|
-
? new Map([...elementToGroup.entries()].map(([k, v]) => [k, v.name]))
|
|
1214
|
-
: undefined;
|
|
748
|
+
const nodeGroupMap = hasGroups ? new Map([...elementToGroup.entries()].map(([k, v]) => [k, v.name])) : void 0;
|
|
1215
749
|
reduceCrossings(
|
|
1216
750
|
g,
|
|
1217
|
-
containerRels
|
|
1218
|
-
.
|
|
1219
|
-
|
|
1220
|
-
nameToElement.has(r.sourceName) && nameToElement.has(r.targetName)
|
|
1221
|
-
)
|
|
1222
|
-
.map((r) => ({ source: r.sourceName, target: r.targetName })),
|
|
751
|
+
containerRels.filter(
|
|
752
|
+
(r) => nameToElement.has(r.sourceName) && nameToElement.has(r.targetName)
|
|
753
|
+
).map((r) => ({ source: r.sourceName, target: r.targetName })),
|
|
1223
754
|
nodeGroupMap
|
|
1224
755
|
);
|
|
1225
|
-
|
|
1226
|
-
// Extract positioned nodes
|
|
1227
|
-
const nodes: Writable<C4LayoutNode>[] = [];
|
|
756
|
+
const nodes = [];
|
|
1228
757
|
for (const el of containers) {
|
|
1229
758
|
const pos = g.node(el.name);
|
|
1230
759
|
const color = resolveNodeColor(
|
|
@@ -1232,31 +761,28 @@ export function layoutC4Containers(
|
|
|
1232
761
|
parsed.tagGroups,
|
|
1233
762
|
activeTagGroup ?? null
|
|
1234
763
|
);
|
|
1235
|
-
const tech = el.metadata[
|
|
1236
|
-
const hasComponents =
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
const description = el.description?.join('\n');
|
|
1240
|
-
const drillable = hasComponents || el.importPath ? true : undefined;
|
|
764
|
+
const tech = el.metadata["tech"] ?? el.metadata["technology"];
|
|
765
|
+
const hasComponents = el.children.some((c) => c.type === "component") || el.groups.some((grp) => grp.children.some((c) => c.type === "component"));
|
|
766
|
+
const description = el.description?.join("\n");
|
|
767
|
+
const drillable = hasComponents || el.importPath ? true : void 0;
|
|
1241
768
|
nodes.push({
|
|
1242
769
|
id: el.name,
|
|
1243
770
|
name: el.name,
|
|
1244
|
-
type:
|
|
1245
|
-
...
|
|
771
|
+
type: "container",
|
|
772
|
+
...description !== void 0 && { description },
|
|
1246
773
|
metadata: el.metadata,
|
|
1247
774
|
lineNumber: el.lineNumber,
|
|
1248
|
-
...
|
|
775
|
+
...color !== void 0 && { color },
|
|
1249
776
|
shape: el.shape,
|
|
1250
|
-
...
|
|
1251
|
-
...
|
|
1252
|
-
...
|
|
777
|
+
...tech !== void 0 && { technology: tech },
|
|
778
|
+
...drillable !== void 0 && { drillable },
|
|
779
|
+
...el.importPath !== void 0 && { importPath: el.importPath },
|
|
1253
780
|
x: pos.x,
|
|
1254
781
|
y: pos.y,
|
|
1255
782
|
width: pos.width,
|
|
1256
|
-
height: pos.height
|
|
783
|
+
height: pos.height
|
|
1257
784
|
});
|
|
1258
785
|
}
|
|
1259
|
-
|
|
1260
786
|
for (const el of externals) {
|
|
1261
787
|
const pos = g.node(el.name);
|
|
1262
788
|
const color = resolveNodeColor(
|
|
@@ -1264,47 +790,37 @@ export function layoutC4Containers(
|
|
|
1264
790
|
parsed.tagGroups,
|
|
1265
791
|
activeTagGroup ?? null
|
|
1266
792
|
);
|
|
1267
|
-
const description = el.description?.join(
|
|
793
|
+
const description = el.description?.join("\n");
|
|
1268
794
|
nodes.push({
|
|
1269
795
|
id: el.name,
|
|
1270
796
|
name: el.name,
|
|
1271
|
-
type: el.type
|
|
1272
|
-
...
|
|
797
|
+
type: el.type,
|
|
798
|
+
...description !== void 0 && { description },
|
|
1273
799
|
metadata: el.metadata,
|
|
1274
800
|
lineNumber: el.lineNumber,
|
|
1275
|
-
...
|
|
801
|
+
...color !== void 0 && { color },
|
|
1276
802
|
x: pos.x,
|
|
1277
803
|
y: pos.y,
|
|
1278
804
|
width: pos.width,
|
|
1279
|
-
height: pos.height
|
|
805
|
+
height: pos.height
|
|
1280
806
|
});
|
|
1281
807
|
}
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
.
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
points: edgeData?.points ?? [],
|
|
1299
|
-
};
|
|
1300
|
-
});
|
|
1301
|
-
|
|
1302
|
-
// Compute boundary box from container nodes only
|
|
1303
|
-
const containerNodes = nodes.filter((n) => n.type === 'container');
|
|
1304
|
-
let bMinX = Infinity,
|
|
1305
|
-
bMinY = Infinity,
|
|
1306
|
-
bMaxX = -Infinity,
|
|
1307
|
-
bMaxY = -Infinity;
|
|
808
|
+
const edges = containerRels.filter(
|
|
809
|
+
(rel) => nameToElement.has(rel.sourceName) && nameToElement.has(rel.targetName)
|
|
810
|
+
).map((rel) => {
|
|
811
|
+
const edgeData = g.edge(rel.sourceName, rel.targetName);
|
|
812
|
+
return {
|
|
813
|
+
source: rel.sourceName,
|
|
814
|
+
target: rel.targetName,
|
|
815
|
+
arrowType: rel.arrowType,
|
|
816
|
+
...rel.label !== void 0 && { label: rel.label },
|
|
817
|
+
...rel.technology !== void 0 && { technology: rel.technology },
|
|
818
|
+
lineNumber: rel.lineNumber,
|
|
819
|
+
points: edgeData?.points ?? []
|
|
820
|
+
};
|
|
821
|
+
});
|
|
822
|
+
const containerNodes = nodes.filter((n) => n.type === "container");
|
|
823
|
+
let bMinX = Infinity, bMinY = Infinity, bMaxX = -Infinity, bMaxY = -Infinity;
|
|
1308
824
|
for (const n of containerNodes) {
|
|
1309
825
|
const left = n.x - n.width / 2;
|
|
1310
826
|
const top = n.y - n.height / 2;
|
|
@@ -1315,39 +831,30 @@ export function layoutC4Containers(
|
|
|
1315
831
|
if (right > bMaxX) bMaxX = right;
|
|
1316
832
|
if (bottom > bMaxY) bMaxY = bottom;
|
|
1317
833
|
}
|
|
1318
|
-
|
|
1319
|
-
const boundary: Writable<C4LayoutBoundary> = {
|
|
834
|
+
const boundary = {
|
|
1320
835
|
label: system.name,
|
|
1321
|
-
typeLabel:
|
|
836
|
+
typeLabel: "system",
|
|
1322
837
|
lineNumber: system.lineNumber,
|
|
1323
838
|
x: bMinX - BOUNDARY_PAD,
|
|
1324
839
|
y: bMinY - BOUNDARY_PAD,
|
|
1325
840
|
width: bMaxX - bMinX + BOUNDARY_PAD * 2,
|
|
1326
|
-
height: bMaxY - bMinY + BOUNDARY_PAD * 2
|
|
841
|
+
height: bMaxY - bMinY + BOUNDARY_PAD * 2
|
|
1327
842
|
};
|
|
1328
|
-
|
|
1329
|
-
// Compute group boundaries from member node positions
|
|
1330
|
-
const groupBoundaries: Writable<C4LayoutBoundary>[] = [];
|
|
843
|
+
const groupBoundaries = [];
|
|
1331
844
|
if (hasGroups) {
|
|
1332
845
|
const nodeMap = new Map(containerNodes.map((n) => [n.name, n]));
|
|
1333
|
-
const seenGroups = new Map
|
|
1334
|
-
string,
|
|
1335
|
-
{ lineNumber: number; members: Writable<C4LayoutNode>[] }
|
|
1336
|
-
>();
|
|
846
|
+
const seenGroups = /* @__PURE__ */ new Map();
|
|
1337
847
|
for (const [elName, grp] of elementToGroup) {
|
|
1338
848
|
const node = nodeMap.get(elName);
|
|
1339
849
|
if (!node) continue;
|
|
1340
850
|
if (!seenGroups.has(grp.name)) {
|
|
1341
851
|
seenGroups.set(grp.name, { lineNumber: grp.lineNumber, members: [] });
|
|
1342
852
|
}
|
|
1343
|
-
seenGroups.get(grp.name)
|
|
853
|
+
seenGroups.get(grp.name).members.push(node);
|
|
1344
854
|
}
|
|
1345
855
|
for (const [groupName, { lineNumber, members }] of seenGroups) {
|
|
1346
856
|
if (members.length === 0) continue;
|
|
1347
|
-
let gMinX = Infinity,
|
|
1348
|
-
gMinY = Infinity,
|
|
1349
|
-
gMaxX = -Infinity,
|
|
1350
|
-
gMaxY = -Infinity;
|
|
857
|
+
let gMinX = Infinity, gMinY = Infinity, gMaxX = -Infinity, gMaxY = -Infinity;
|
|
1351
858
|
for (const m of members) {
|
|
1352
859
|
const left = m.x - m.width / 2;
|
|
1353
860
|
const top = m.y - m.height / 2;
|
|
@@ -1360,21 +867,16 @@ export function layoutC4Containers(
|
|
|
1360
867
|
}
|
|
1361
868
|
groupBoundaries.push({
|
|
1362
869
|
label: groupName,
|
|
1363
|
-
typeLabel:
|
|
870
|
+
typeLabel: "group",
|
|
1364
871
|
lineNumber,
|
|
1365
872
|
x: gMinX - GROUP_BOUNDARY_PAD,
|
|
1366
873
|
y: gMinY - GROUP_BOUNDARY_PAD,
|
|
1367
874
|
width: gMaxX - gMinX + GROUP_BOUNDARY_PAD * 2,
|
|
1368
|
-
height: gMaxY - gMinY + GROUP_BOUNDARY_PAD * 2
|
|
875
|
+
height: gMaxY - gMinY + GROUP_BOUNDARY_PAD * 2
|
|
1369
876
|
});
|
|
1370
877
|
}
|
|
1371
878
|
}
|
|
1372
|
-
|
|
1373
|
-
// Compute bounding box of all content (nodes + boundary + group boundaries + edge points)
|
|
1374
|
-
let minX = Infinity,
|
|
1375
|
-
minY = Infinity,
|
|
1376
|
-
maxX = -Infinity,
|
|
1377
|
-
maxY = -Infinity;
|
|
879
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
1378
880
|
for (const node of nodes) {
|
|
1379
881
|
const left = node.x - node.width / 2;
|
|
1380
882
|
const top = node.y - node.height / 2;
|
|
@@ -1403,8 +905,6 @@ export function layoutC4Containers(
|
|
|
1403
905
|
if (pt.y > maxY) maxY = pt.y;
|
|
1404
906
|
}
|
|
1405
907
|
}
|
|
1406
|
-
|
|
1407
|
-
// Shift everything so content starts at (MARGIN, MARGIN)
|
|
1408
908
|
const shiftX = MARGIN - minX;
|
|
1409
909
|
const shiftY = MARGIN - minY;
|
|
1410
910
|
for (const node of nodes) {
|
|
@@ -1423,13 +923,9 @@ export function layoutC4Containers(
|
|
|
1423
923
|
pt.y += shiftY;
|
|
1424
924
|
}
|
|
1425
925
|
}
|
|
1426
|
-
|
|
1427
926
|
let totalWidth = maxX - minX + MARGIN * 2;
|
|
1428
927
|
let totalHeight = maxY - minY + MARGIN * 2;
|
|
1429
|
-
|
|
1430
928
|
const legendGroups = computeLegendGroups(parsed.tagGroups);
|
|
1431
|
-
|
|
1432
|
-
// Position legend below diagram
|
|
1433
929
|
if (legendGroups.length > 0) {
|
|
1434
930
|
const legendY = totalHeight + MARGIN;
|
|
1435
931
|
let legendX = MARGIN;
|
|
@@ -1443,7 +939,6 @@ export function layoutC4Containers(
|
|
|
1443
939
|
if (legendRight > totalWidth) totalWidth = legendRight;
|
|
1444
940
|
if (legendBottom > totalHeight) totalHeight = legendBottom;
|
|
1445
941
|
}
|
|
1446
|
-
|
|
1447
942
|
return {
|
|
1448
943
|
nodes,
|
|
1449
944
|
edges,
|
|
@@ -1451,25 +946,10 @@ export function layoutC4Containers(
|
|
|
1451
946
|
boundary,
|
|
1452
947
|
groupBoundaries,
|
|
1453
948
|
width: totalWidth,
|
|
1454
|
-
height: totalHeight
|
|
949
|
+
height: totalHeight
|
|
1455
950
|
};
|
|
1456
951
|
}
|
|
1457
|
-
|
|
1458
|
-
// ============================================================
|
|
1459
|
-
// Component-Level Layout
|
|
1460
|
-
// ============================================================
|
|
1461
|
-
|
|
1462
|
-
/**
|
|
1463
|
-
* Layout components within a specific container, plus external elements
|
|
1464
|
-
* that have relationships with those components.
|
|
1465
|
-
*/
|
|
1466
|
-
export function layoutC4Components(
|
|
1467
|
-
parsed: ParsedC4,
|
|
1468
|
-
systemName: string,
|
|
1469
|
-
containerName: string,
|
|
1470
|
-
activeTagGroup?: string | null
|
|
1471
|
-
): C4LayoutResult {
|
|
1472
|
-
// Find the system element by name
|
|
952
|
+
function layoutC4Components(parsed, systemName, containerName, activeTagGroup) {
|
|
1473
953
|
const system = parsed.elements.find(
|
|
1474
954
|
(el) => el.name.toLowerCase() === systemName.toLowerCase()
|
|
1475
955
|
);
|
|
@@ -1480,17 +960,12 @@ export function layoutC4Components(
|
|
|
1480
960
|
legend: [],
|
|
1481
961
|
groupBoundaries: [],
|
|
1482
962
|
width: 0,
|
|
1483
|
-
height: 0
|
|
963
|
+
height: 0
|
|
1484
964
|
};
|
|
1485
965
|
}
|
|
1486
|
-
|
|
1487
|
-
// Find the container within the system (direct children + group children)
|
|
1488
|
-
let targetContainer: C4Element | undefined;
|
|
966
|
+
let targetContainer;
|
|
1489
967
|
for (const child of system.children) {
|
|
1490
|
-
if (
|
|
1491
|
-
child.type === 'container' &&
|
|
1492
|
-
child.name.toLowerCase() === containerName.toLowerCase()
|
|
1493
|
-
) {
|
|
968
|
+
if (child.type === "container" && child.name.toLowerCase() === containerName.toLowerCase()) {
|
|
1494
969
|
targetContainer = child;
|
|
1495
970
|
break;
|
|
1496
971
|
}
|
|
@@ -1498,10 +973,7 @@ export function layoutC4Components(
|
|
|
1498
973
|
if (!targetContainer) {
|
|
1499
974
|
for (const group of system.groups) {
|
|
1500
975
|
for (const child of group.children) {
|
|
1501
|
-
if (
|
|
1502
|
-
child.type === 'container' &&
|
|
1503
|
-
child.name.toLowerCase() === containerName.toLowerCase()
|
|
1504
|
-
) {
|
|
976
|
+
if (child.type === "container" && child.name.toLowerCase() === containerName.toLowerCase()) {
|
|
1505
977
|
targetContainer = child;
|
|
1506
978
|
break;
|
|
1507
979
|
}
|
|
@@ -1516,21 +988,18 @@ export function layoutC4Components(
|
|
|
1516
988
|
legend: [],
|
|
1517
989
|
groupBoundaries: [],
|
|
1518
990
|
width: 0,
|
|
1519
|
-
height: 0
|
|
991
|
+
height: 0
|
|
1520
992
|
};
|
|
1521
993
|
}
|
|
1522
|
-
|
|
1523
|
-
// Collect all components: direct children + group children
|
|
1524
|
-
const components: C4Element[] = [];
|
|
994
|
+
const components = [];
|
|
1525
995
|
for (const child of targetContainer.children) {
|
|
1526
|
-
if (child.type ===
|
|
996
|
+
if (child.type === "component") components.push(child);
|
|
1527
997
|
}
|
|
1528
998
|
for (const group of targetContainer.groups) {
|
|
1529
999
|
for (const child of group.children) {
|
|
1530
|
-
if (child.type ===
|
|
1000
|
+
if (child.type === "component") components.push(child);
|
|
1531
1001
|
}
|
|
1532
1002
|
}
|
|
1533
|
-
|
|
1534
1003
|
if (components.length === 0) {
|
|
1535
1004
|
return {
|
|
1536
1005
|
nodes: [],
|
|
@@ -1538,36 +1007,27 @@ export function layoutC4Components(
|
|
|
1538
1007
|
legend: [],
|
|
1539
1008
|
groupBoundaries: [],
|
|
1540
1009
|
width: 0,
|
|
1541
|
-
height: 0
|
|
1010
|
+
height: 0
|
|
1542
1011
|
};
|
|
1543
1012
|
}
|
|
1544
|
-
|
|
1545
1013
|
const componentNames = new Set(components.map((c) => c.name.toLowerCase()));
|
|
1546
|
-
|
|
1547
|
-
// Build name → element map for all top-level elements and containers in this system
|
|
1548
|
-
const topElementsByName = new Map<string, C4Element>();
|
|
1014
|
+
const topElementsByName = /* @__PURE__ */ new Map();
|
|
1549
1015
|
for (const el of parsed.elements) {
|
|
1550
1016
|
topElementsByName.set(el.name.toLowerCase(), el);
|
|
1551
|
-
// Also index containers in every system for external container resolution
|
|
1552
1017
|
for (const child of el.children) {
|
|
1553
|
-
if (child.type ===
|
|
1018
|
+
if (child.type === "container") {
|
|
1554
1019
|
topElementsByName.set(child.name.toLowerCase(), child);
|
|
1555
1020
|
}
|
|
1556
1021
|
}
|
|
1557
1022
|
for (const group of el.groups) {
|
|
1558
1023
|
for (const child of group.children) {
|
|
1559
|
-
if (child.type ===
|
|
1024
|
+
if (child.type === "container") {
|
|
1560
1025
|
topElementsByName.set(child.name.toLowerCase(), child);
|
|
1561
1026
|
}
|
|
1562
1027
|
}
|
|
1563
1028
|
}
|
|
1564
1029
|
}
|
|
1565
|
-
|
|
1566
|
-
// Identify external elements: targets of component relationships not in this container,
|
|
1567
|
-
// or elements whose relationships target components in this container
|
|
1568
|
-
const externalNames = new Set<string>();
|
|
1569
|
-
|
|
1570
|
-
// Forward: component → target outside this container
|
|
1030
|
+
const externalNames = /* @__PURE__ */ new Set();
|
|
1571
1031
|
for (const component of components) {
|
|
1572
1032
|
for (const rel of component.relationships) {
|
|
1573
1033
|
const targetLower = rel.target.toLowerCase();
|
|
@@ -1576,23 +1036,15 @@ export function layoutC4Components(
|
|
|
1576
1036
|
}
|
|
1577
1037
|
}
|
|
1578
1038
|
}
|
|
1579
|
-
|
|
1580
|
-
// Reverse: any element that targets a component in this container
|
|
1581
1039
|
const ownerMap = buildOwnershipMap(parsed.elements);
|
|
1582
1040
|
const allRels = collectAllRelationships(parsed.elements, ownerMap);
|
|
1583
1041
|
for (const { sourceName, rel } of allRels) {
|
|
1584
|
-
// Skip relationships from within this container
|
|
1585
1042
|
if (componentNames.has(sourceName.toLowerCase())) continue;
|
|
1586
|
-
// Check if target is a component in this container
|
|
1587
1043
|
if (componentNames.has(rel.target.toLowerCase())) {
|
|
1588
|
-
// Resolve to the most specific known element: if source is a container, use it;
|
|
1589
|
-
// otherwise roll up to system ancestor
|
|
1590
1044
|
const sourceAncestor = ownerMap.get(sourceName) ?? sourceName;
|
|
1591
|
-
// If source is inside the same container, skip
|
|
1592
1045
|
if (sourceAncestor.toLowerCase() === targetContainer.name.toLowerCase())
|
|
1593
1046
|
continue;
|
|
1594
1047
|
if (sourceAncestor.toLowerCase() === system.name.toLowerCase()) {
|
|
1595
|
-
// Source is in same system — try to resolve to container level
|
|
1596
1048
|
const sourceLower = sourceName.toLowerCase();
|
|
1597
1049
|
if (topElementsByName.has(sourceLower)) {
|
|
1598
1050
|
externalNames.add(sourceLower);
|
|
@@ -1604,175 +1056,118 @@ export function layoutC4Components(
|
|
|
1604
1056
|
}
|
|
1605
1057
|
}
|
|
1606
1058
|
}
|
|
1607
|
-
|
|
1608
|
-
// Resolve external elements
|
|
1609
|
-
const externals: C4Element[] = [];
|
|
1059
|
+
const externals = [];
|
|
1610
1060
|
for (const name of externalNames) {
|
|
1611
1061
|
const el = topElementsByName.get(name);
|
|
1612
1062
|
if (el) externals.push(el);
|
|
1613
1063
|
}
|
|
1614
|
-
|
|
1615
|
-
// Build element-to-group mapping for compound graph
|
|
1616
|
-
const elementToGroup = new Map<
|
|
1617
|
-
string,
|
|
1618
|
-
{ name: string; lineNumber: number }
|
|
1619
|
-
>();
|
|
1064
|
+
const elementToGroup = /* @__PURE__ */ new Map();
|
|
1620
1065
|
for (const group of targetContainer.groups) {
|
|
1621
1066
|
for (const child of group.children) {
|
|
1622
|
-
if (child.type ===
|
|
1067
|
+
if (child.type === "component") {
|
|
1623
1068
|
elementToGroup.set(child.name, {
|
|
1624
1069
|
name: group.name,
|
|
1625
|
-
lineNumber: group.lineNumber
|
|
1070
|
+
lineNumber: group.lineNumber
|
|
1626
1071
|
});
|
|
1627
1072
|
}
|
|
1628
1073
|
}
|
|
1629
1074
|
}
|
|
1630
1075
|
const hasGroups = elementToGroup.size > 0;
|
|
1631
|
-
|
|
1632
|
-
// Create dagre graph — use compound when groups exist
|
|
1633
|
-
const g = hasGroups
|
|
1634
|
-
? new dagre.graphlib.Graph({ compound: true })
|
|
1635
|
-
: new dagre.graphlib.Graph();
|
|
1076
|
+
const g = hasGroups ? new dagre.graphlib.Graph({ compound: true }) : new dagre.graphlib.Graph();
|
|
1636
1077
|
g.setDefaultEdgeLabel(() => ({}));
|
|
1637
|
-
|
|
1638
|
-
// Add virtual group parent nodes
|
|
1639
1078
|
if (hasGroups) {
|
|
1640
|
-
const seenGroups = new Set
|
|
1079
|
+
const seenGroups = /* @__PURE__ */ new Set();
|
|
1641
1080
|
for (const { name } of elementToGroup.values()) {
|
|
1642
1081
|
if (!seenGroups.has(name)) {
|
|
1643
1082
|
seenGroups.add(name);
|
|
1644
|
-
g.setNode(
|
|
1083
|
+
g.setNode("__group_" + name, {});
|
|
1645
1084
|
}
|
|
1646
1085
|
}
|
|
1647
1086
|
}
|
|
1648
|
-
|
|
1649
|
-
// Add component nodes
|
|
1650
|
-
const nameToElement = new Map<string, C4Element>();
|
|
1087
|
+
const nameToElement = /* @__PURE__ */ new Map();
|
|
1651
1088
|
for (const el of components) {
|
|
1652
1089
|
nameToElement.set(el.name, el);
|
|
1653
1090
|
const dims = computeC4NodeDimensions(el, { showTechnology: true });
|
|
1654
1091
|
g.setNode(el.name, { width: dims.width, height: dims.height });
|
|
1655
|
-
// Set group parent
|
|
1656
1092
|
const grp = elementToGroup.get(el.name);
|
|
1657
1093
|
if (grp) {
|
|
1658
|
-
g.setParent(el.name,
|
|
1094
|
+
g.setParent(el.name, "__group_" + grp.name);
|
|
1659
1095
|
}
|
|
1660
1096
|
}
|
|
1661
|
-
|
|
1662
|
-
// Add external nodes
|
|
1663
1097
|
for (const el of externals) {
|
|
1664
1098
|
nameToElement.set(el.name, el);
|
|
1665
1099
|
const dims = computeC4NodeDimensions(el);
|
|
1666
1100
|
g.setNode(el.name, { width: dims.width, height: dims.height });
|
|
1667
1101
|
}
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
interface ComponentRel {
|
|
1671
|
-
sourceName: string;
|
|
1672
|
-
targetName: string;
|
|
1673
|
-
label?: string;
|
|
1674
|
-
technology?: string;
|
|
1675
|
-
arrowType: C4ArrowType;
|
|
1676
|
-
lineNumber: number;
|
|
1677
|
-
}
|
|
1678
|
-
|
|
1679
|
-
const componentRels: ComponentRel[] = [];
|
|
1680
|
-
const seenEdgeKeys = new Set<string>();
|
|
1681
|
-
|
|
1682
|
-
// Forward: component → target
|
|
1102
|
+
const componentRels = [];
|
|
1103
|
+
const seenEdgeKeys = /* @__PURE__ */ new Set();
|
|
1683
1104
|
for (const component of components) {
|
|
1684
1105
|
for (const rel of component.relationships) {
|
|
1685
1106
|
const targetEl = nameToElement.get(rel.target);
|
|
1686
1107
|
if (!targetEl) continue;
|
|
1687
|
-
const key = `${component.name}
|
|
1108
|
+
const key = `${component.name}\u2192${rel.target}`;
|
|
1688
1109
|
if (!seenEdgeKeys.has(key)) {
|
|
1689
1110
|
seenEdgeKeys.add(key);
|
|
1690
1111
|
componentRels.push({
|
|
1691
1112
|
sourceName: component.name,
|
|
1692
1113
|
targetName: rel.target,
|
|
1693
|
-
...
|
|
1694
|
-
...
|
|
1114
|
+
...rel.label !== void 0 && { label: rel.label },
|
|
1115
|
+
...rel.technology !== void 0 && { technology: rel.technology },
|
|
1695
1116
|
arrowType: rel.arrowType,
|
|
1696
|
-
lineNumber: rel.lineNumber
|
|
1117
|
+
lineNumber: rel.lineNumber
|
|
1697
1118
|
});
|
|
1698
1119
|
}
|
|
1699
1120
|
}
|
|
1700
1121
|
}
|
|
1701
|
-
|
|
1702
|
-
// Reverse: external → component
|
|
1703
1122
|
for (const { sourceName, rel } of allRels) {
|
|
1704
1123
|
if (componentNames.has(sourceName.toLowerCase())) continue;
|
|
1705
1124
|
if (!componentNames.has(rel.target.toLowerCase())) continue;
|
|
1706
|
-
|
|
1707
1125
|
const sourceAncestor = ownerMap.get(sourceName) ?? sourceName;
|
|
1708
1126
|
if (sourceAncestor.toLowerCase() === targetContainer.name.toLowerCase())
|
|
1709
1127
|
continue;
|
|
1710
|
-
|
|
1711
|
-
// Resolve source to the external element we added
|
|
1712
|
-
let resolvedSource: string | undefined;
|
|
1128
|
+
let resolvedSource;
|
|
1713
1129
|
if (nameToElement.has(sourceName)) {
|
|
1714
1130
|
resolvedSource = sourceName;
|
|
1715
1131
|
} else if (nameToElement.has(sourceAncestor)) {
|
|
1716
1132
|
resolvedSource = sourceAncestor;
|
|
1717
1133
|
}
|
|
1718
1134
|
if (!resolvedSource) continue;
|
|
1719
|
-
|
|
1720
|
-
const key = `${resolvedSource}→${rel.target}`;
|
|
1135
|
+
const key = `${resolvedSource}\u2192${rel.target}`;
|
|
1721
1136
|
if (!seenEdgeKeys.has(key)) {
|
|
1722
1137
|
seenEdgeKeys.add(key);
|
|
1723
1138
|
componentRels.push({
|
|
1724
1139
|
sourceName: resolvedSource,
|
|
1725
1140
|
targetName: rel.target,
|
|
1726
|
-
...
|
|
1727
|
-
...
|
|
1141
|
+
...rel.label !== void 0 && { label: rel.label },
|
|
1142
|
+
...rel.technology !== void 0 && { technology: rel.technology },
|
|
1728
1143
|
arrowType: rel.arrowType,
|
|
1729
|
-
lineNumber: rel.lineNumber
|
|
1144
|
+
lineNumber: rel.lineNumber
|
|
1730
1145
|
});
|
|
1731
1146
|
}
|
|
1732
1147
|
}
|
|
1733
|
-
|
|
1734
|
-
// Compute adaptive spacing based on edge fan-out
|
|
1735
1148
|
const spacing = computeAdaptiveSpacing(componentRels);
|
|
1736
1149
|
g.setGraph({
|
|
1737
|
-
rankdir:
|
|
1150
|
+
rankdir: "TB",
|
|
1738
1151
|
nodesep: spacing.nodesep,
|
|
1739
1152
|
ranksep: spacing.ranksep,
|
|
1740
|
-
edgesep: spacing.edgesep
|
|
1153
|
+
edgesep: spacing.edgesep
|
|
1741
1154
|
});
|
|
1742
|
-
|
|
1743
|
-
// Add edges to dagre
|
|
1744
1155
|
for (const rel of componentRels) {
|
|
1745
|
-
if (
|
|
1746
|
-
|
|
1747
|
-
nameToElement.has(rel.targetName)
|
|
1748
|
-
) {
|
|
1749
|
-
g.setEdge(rel.sourceName, rel.targetName, { label: rel.label ?? '' });
|
|
1156
|
+
if (nameToElement.has(rel.sourceName) && nameToElement.has(rel.targetName)) {
|
|
1157
|
+
g.setEdge(rel.sourceName, rel.targetName, { label: rel.label ?? "" });
|
|
1750
1158
|
}
|
|
1751
1159
|
}
|
|
1752
|
-
|
|
1753
|
-
// Run layout
|
|
1754
1160
|
dagre.layout(g);
|
|
1755
|
-
|
|
1756
|
-
// Post-dagre crossing reduction (with group-aware partitioning)
|
|
1757
|
-
const nodeGroupMap = hasGroups
|
|
1758
|
-
? new Map([...elementToGroup.entries()].map(([k, v]) => [k, v.name]))
|
|
1759
|
-
: undefined;
|
|
1161
|
+
const nodeGroupMap = hasGroups ? new Map([...elementToGroup.entries()].map(([k, v]) => [k, v.name])) : void 0;
|
|
1760
1162
|
reduceCrossings(
|
|
1761
1163
|
g,
|
|
1762
|
-
componentRels
|
|
1763
|
-
.
|
|
1764
|
-
|
|
1765
|
-
nameToElement.has(r.sourceName) && nameToElement.has(r.targetName)
|
|
1766
|
-
)
|
|
1767
|
-
.map((r) => ({ source: r.sourceName, target: r.targetName })),
|
|
1164
|
+
componentRels.filter(
|
|
1165
|
+
(r) => nameToElement.has(r.sourceName) && nameToElement.has(r.targetName)
|
|
1166
|
+
).map((r) => ({ source: r.sourceName, target: r.targetName })),
|
|
1768
1167
|
nodeGroupMap
|
|
1769
1168
|
);
|
|
1770
|
-
|
|
1771
|
-
// Tag inheritance ancestors: container → system
|
|
1772
1169
|
const ancestors = [targetContainer, system];
|
|
1773
|
-
|
|
1774
|
-
// Extract positioned nodes
|
|
1775
|
-
const nodes: Writable<C4LayoutNode>[] = [];
|
|
1170
|
+
const nodes = [];
|
|
1776
1171
|
for (const el of components) {
|
|
1777
1172
|
const pos = g.node(el.name);
|
|
1778
1173
|
const color = resolveNodeColor(
|
|
@@ -1781,31 +1176,28 @@ export function layoutC4Components(
|
|
|
1781
1176
|
activeTagGroup ?? null,
|
|
1782
1177
|
ancestors
|
|
1783
1178
|
);
|
|
1784
|
-
const tech = el.metadata[
|
|
1785
|
-
const hasComponents =
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
const description = el.description?.join('\n');
|
|
1789
|
-
const drillable = hasComponents || el.importPath ? true : undefined;
|
|
1179
|
+
const tech = el.metadata["tech"] ?? el.metadata["technology"];
|
|
1180
|
+
const hasComponents = el.children.some((c) => c.type === "component") || el.groups.some((grp) => grp.children.some((c) => c.type === "component"));
|
|
1181
|
+
const description = el.description?.join("\n");
|
|
1182
|
+
const drillable = hasComponents || el.importPath ? true : void 0;
|
|
1790
1183
|
nodes.push({
|
|
1791
1184
|
id: el.name,
|
|
1792
1185
|
name: el.name,
|
|
1793
|
-
type:
|
|
1794
|
-
...
|
|
1186
|
+
type: "component",
|
|
1187
|
+
...description !== void 0 && { description },
|
|
1795
1188
|
metadata: el.metadata,
|
|
1796
1189
|
lineNumber: el.lineNumber,
|
|
1797
|
-
...
|
|
1190
|
+
...color !== void 0 && { color },
|
|
1798
1191
|
shape: el.shape,
|
|
1799
|
-
...
|
|
1800
|
-
...
|
|
1801
|
-
...
|
|
1192
|
+
...tech !== void 0 && { technology: tech },
|
|
1193
|
+
...drillable !== void 0 && { drillable },
|
|
1194
|
+
...el.importPath !== void 0 && { importPath: el.importPath },
|
|
1802
1195
|
x: pos.x,
|
|
1803
1196
|
y: pos.y,
|
|
1804
1197
|
width: pos.width,
|
|
1805
|
-
height: pos.height
|
|
1198
|
+
height: pos.height
|
|
1806
1199
|
});
|
|
1807
1200
|
}
|
|
1808
|
-
|
|
1809
1201
|
for (const el of externals) {
|
|
1810
1202
|
const pos = g.node(el.name);
|
|
1811
1203
|
const color = resolveNodeColor(
|
|
@@ -1813,47 +1205,37 @@ export function layoutC4Components(
|
|
|
1813
1205
|
parsed.tagGroups,
|
|
1814
1206
|
activeTagGroup ?? null
|
|
1815
1207
|
);
|
|
1816
|
-
const description = el.description?.join(
|
|
1208
|
+
const description = el.description?.join("\n");
|
|
1817
1209
|
nodes.push({
|
|
1818
1210
|
id: el.name,
|
|
1819
1211
|
name: el.name,
|
|
1820
|
-
type: el.type
|
|
1821
|
-
...
|
|
1212
|
+
type: el.type,
|
|
1213
|
+
...description !== void 0 && { description },
|
|
1822
1214
|
metadata: el.metadata,
|
|
1823
1215
|
lineNumber: el.lineNumber,
|
|
1824
|
-
...
|
|
1216
|
+
...color !== void 0 && { color },
|
|
1825
1217
|
x: pos.x,
|
|
1826
1218
|
y: pos.y,
|
|
1827
1219
|
width: pos.width,
|
|
1828
|
-
height: pos.height
|
|
1220
|
+
height: pos.height
|
|
1829
1221
|
});
|
|
1830
1222
|
}
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
.
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
points: edgeData?.points ?? [],
|
|
1848
|
-
};
|
|
1849
|
-
});
|
|
1850
|
-
|
|
1851
|
-
// Compute boundary box from component nodes only
|
|
1852
|
-
const componentNodes = nodes.filter((n) => n.type === 'component');
|
|
1853
|
-
let bMinX = Infinity,
|
|
1854
|
-
bMinY = Infinity,
|
|
1855
|
-
bMaxX = -Infinity,
|
|
1856
|
-
bMaxY = -Infinity;
|
|
1223
|
+
const edges = componentRels.filter(
|
|
1224
|
+
(rel) => nameToElement.has(rel.sourceName) && nameToElement.has(rel.targetName)
|
|
1225
|
+
).map((rel) => {
|
|
1226
|
+
const edgeData = g.edge(rel.sourceName, rel.targetName);
|
|
1227
|
+
return {
|
|
1228
|
+
source: rel.sourceName,
|
|
1229
|
+
target: rel.targetName,
|
|
1230
|
+
arrowType: rel.arrowType,
|
|
1231
|
+
...rel.label !== void 0 && { label: rel.label },
|
|
1232
|
+
...rel.technology !== void 0 && { technology: rel.technology },
|
|
1233
|
+
lineNumber: rel.lineNumber,
|
|
1234
|
+
points: edgeData?.points ?? []
|
|
1235
|
+
};
|
|
1236
|
+
});
|
|
1237
|
+
const componentNodes = nodes.filter((n) => n.type === "component");
|
|
1238
|
+
let bMinX = Infinity, bMinY = Infinity, bMaxX = -Infinity, bMaxY = -Infinity;
|
|
1857
1239
|
for (const n of componentNodes) {
|
|
1858
1240
|
const left = n.x - n.width / 2;
|
|
1859
1241
|
const top = n.y - n.height / 2;
|
|
@@ -1864,39 +1246,30 @@ export function layoutC4Components(
|
|
|
1864
1246
|
if (right > bMaxX) bMaxX = right;
|
|
1865
1247
|
if (bottom > bMaxY) bMaxY = bottom;
|
|
1866
1248
|
}
|
|
1867
|
-
|
|
1868
|
-
const boundary: Writable<C4LayoutBoundary> = {
|
|
1249
|
+
const boundary = {
|
|
1869
1250
|
label: targetContainer.name,
|
|
1870
|
-
typeLabel:
|
|
1251
|
+
typeLabel: "container",
|
|
1871
1252
|
lineNumber: targetContainer.lineNumber,
|
|
1872
1253
|
x: bMinX - BOUNDARY_PAD,
|
|
1873
1254
|
y: bMinY - BOUNDARY_PAD,
|
|
1874
1255
|
width: bMaxX - bMinX + BOUNDARY_PAD * 2,
|
|
1875
|
-
height: bMaxY - bMinY + BOUNDARY_PAD * 2
|
|
1256
|
+
height: bMaxY - bMinY + BOUNDARY_PAD * 2
|
|
1876
1257
|
};
|
|
1877
|
-
|
|
1878
|
-
// Compute group boundaries from member node positions
|
|
1879
|
-
const groupBoundaries: Writable<C4LayoutBoundary>[] = [];
|
|
1258
|
+
const groupBoundaries = [];
|
|
1880
1259
|
if (hasGroups) {
|
|
1881
1260
|
const nodeMap = new Map(componentNodes.map((n) => [n.name, n]));
|
|
1882
|
-
const seenGroups = new Map
|
|
1883
|
-
string,
|
|
1884
|
-
{ lineNumber: number; members: Writable<C4LayoutNode>[] }
|
|
1885
|
-
>();
|
|
1261
|
+
const seenGroups = /* @__PURE__ */ new Map();
|
|
1886
1262
|
for (const [elName, grp] of elementToGroup) {
|
|
1887
1263
|
const node = nodeMap.get(elName);
|
|
1888
1264
|
if (!node) continue;
|
|
1889
1265
|
if (!seenGroups.has(grp.name)) {
|
|
1890
1266
|
seenGroups.set(grp.name, { lineNumber: grp.lineNumber, members: [] });
|
|
1891
1267
|
}
|
|
1892
|
-
seenGroups.get(grp.name)
|
|
1268
|
+
seenGroups.get(grp.name).members.push(node);
|
|
1893
1269
|
}
|
|
1894
1270
|
for (const [groupName, { lineNumber, members }] of seenGroups) {
|
|
1895
1271
|
if (members.length === 0) continue;
|
|
1896
|
-
let gMinX = Infinity,
|
|
1897
|
-
gMinY = Infinity,
|
|
1898
|
-
gMaxX = -Infinity,
|
|
1899
|
-
gMaxY = -Infinity;
|
|
1272
|
+
let gMinX = Infinity, gMinY = Infinity, gMaxX = -Infinity, gMaxY = -Infinity;
|
|
1900
1273
|
for (const m of members) {
|
|
1901
1274
|
const left = m.x - m.width / 2;
|
|
1902
1275
|
const top = m.y - m.height / 2;
|
|
@@ -1909,21 +1282,16 @@ export function layoutC4Components(
|
|
|
1909
1282
|
}
|
|
1910
1283
|
groupBoundaries.push({
|
|
1911
1284
|
label: groupName,
|
|
1912
|
-
typeLabel:
|
|
1285
|
+
typeLabel: "group",
|
|
1913
1286
|
lineNumber,
|
|
1914
1287
|
x: gMinX - GROUP_BOUNDARY_PAD,
|
|
1915
1288
|
y: gMinY - GROUP_BOUNDARY_PAD,
|
|
1916
1289
|
width: gMaxX - gMinX + GROUP_BOUNDARY_PAD * 2,
|
|
1917
|
-
height: gMaxY - gMinY + GROUP_BOUNDARY_PAD * 2
|
|
1290
|
+
height: gMaxY - gMinY + GROUP_BOUNDARY_PAD * 2
|
|
1918
1291
|
});
|
|
1919
1292
|
}
|
|
1920
1293
|
}
|
|
1921
|
-
|
|
1922
|
-
// Compute bounding box of all content (nodes + boundary + group boundaries + edge points)
|
|
1923
|
-
let minX = Infinity,
|
|
1924
|
-
minY = Infinity,
|
|
1925
|
-
maxX = -Infinity,
|
|
1926
|
-
maxY = -Infinity;
|
|
1294
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
1927
1295
|
for (const node of nodes) {
|
|
1928
1296
|
const left = node.x - node.width / 2;
|
|
1929
1297
|
const top = node.y - node.height / 2;
|
|
@@ -1952,8 +1320,6 @@ export function layoutC4Components(
|
|
|
1952
1320
|
if (pt.y > maxY) maxY = pt.y;
|
|
1953
1321
|
}
|
|
1954
1322
|
}
|
|
1955
|
-
|
|
1956
|
-
// Shift everything so content starts at (MARGIN, MARGIN)
|
|
1957
1323
|
const shiftX = MARGIN - minX;
|
|
1958
1324
|
const shiftY = MARGIN - minY;
|
|
1959
1325
|
for (const node of nodes) {
|
|
@@ -1972,13 +1338,9 @@ export function layoutC4Components(
|
|
|
1972
1338
|
pt.y += shiftY;
|
|
1973
1339
|
}
|
|
1974
1340
|
}
|
|
1975
|
-
|
|
1976
1341
|
let totalWidth = maxX - minX + MARGIN * 2;
|
|
1977
1342
|
let totalHeight = maxY - minY + MARGIN * 2;
|
|
1978
|
-
|
|
1979
1343
|
const legendGroups = computeLegendGroups(parsed.tagGroups);
|
|
1980
|
-
|
|
1981
|
-
// Position legend below diagram
|
|
1982
1344
|
if (legendGroups.length > 0) {
|
|
1983
1345
|
const legendY = totalHeight + MARGIN;
|
|
1984
1346
|
let legendX = MARGIN;
|
|
@@ -1992,7 +1354,6 @@ export function layoutC4Components(
|
|
|
1992
1354
|
if (legendRight > totalWidth) totalWidth = legendRight;
|
|
1993
1355
|
if (legendBottom > totalHeight) totalHeight = legendBottom;
|
|
1994
1356
|
}
|
|
1995
|
-
|
|
1996
1357
|
return {
|
|
1997
1358
|
nodes,
|
|
1998
1359
|
edges,
|
|
@@ -2000,63 +1361,30 @@ export function layoutC4Components(
|
|
|
2000
1361
|
boundary,
|
|
2001
1362
|
groupBoundaries,
|
|
2002
1363
|
width: totalWidth,
|
|
2003
|
-
height: totalHeight
|
|
1364
|
+
height: totalHeight
|
|
2004
1365
|
};
|
|
2005
1366
|
}
|
|
2006
|
-
|
|
2007
|
-
// ============================================================
|
|
2008
|
-
// Deployment Diagram Layout
|
|
2009
|
-
// ============================================================
|
|
2010
|
-
|
|
2011
|
-
/**
|
|
2012
|
-
* Resolve a container reference name to its C4Element by walking the parsed
|
|
2013
|
-
* element tree. Matches container names case-insensitively.
|
|
2014
|
-
*/
|
|
2015
|
-
function resolveContainerRef(
|
|
2016
|
-
parsed: ParsedC4,
|
|
2017
|
-
refName: string
|
|
2018
|
-
): C4Element | undefined {
|
|
1367
|
+
function resolveContainerRef(parsed, refName) {
|
|
2019
1368
|
const lower = refName.toLowerCase();
|
|
2020
1369
|
for (const el of parsed.elements) {
|
|
2021
1370
|
for (const child of el.children) {
|
|
2022
|
-
if (child.type ===
|
|
1371
|
+
if (child.type === "container" && child.name.toLowerCase() === lower)
|
|
2023
1372
|
return child;
|
|
2024
1373
|
}
|
|
2025
1374
|
for (const group of el.groups) {
|
|
2026
1375
|
for (const child of group.children) {
|
|
2027
|
-
if (child.type ===
|
|
1376
|
+
if (child.type === "container" && child.name.toLowerCase() === lower)
|
|
2028
1377
|
return child;
|
|
2029
1378
|
}
|
|
2030
1379
|
}
|
|
2031
1380
|
}
|
|
2032
|
-
return
|
|
1381
|
+
return void 0;
|
|
2033
1382
|
}
|
|
2034
|
-
|
|
2035
|
-
/**
|
|
2036
|
-
* Collect all container ref nodes from the deployment tree, flattened with
|
|
2037
|
-
* their parent infra node ID for compound graph assignment.
|
|
2038
|
-
*/
|
|
2039
|
-
interface DeploymentRefEntry {
|
|
2040
|
-
refName: string;
|
|
2041
|
-
element: C4Element;
|
|
2042
|
-
infraId: string;
|
|
2043
|
-
/** Line number of the infra node containing this ref. */
|
|
2044
|
-
deployLineNumber: number;
|
|
2045
|
-
}
|
|
2046
|
-
|
|
2047
|
-
function collectDeploymentRefs(
|
|
2048
|
-
nodes: readonly C4DeploymentNode[],
|
|
2049
|
-
parsed: ParsedC4,
|
|
2050
|
-
parentId: string | null,
|
|
2051
|
-
refs: DeploymentRefEntry[],
|
|
2052
|
-
infraIds: Map<string, C4DeploymentNode>,
|
|
2053
|
-
infraParents: Map<string, string | null>
|
|
2054
|
-
): void {
|
|
1383
|
+
function collectDeploymentRefs(nodes, parsed, parentId, refs, infraIds, infraParents) {
|
|
2055
1384
|
for (const node of nodes) {
|
|
2056
1385
|
const infraId = `__infra_${node.name}`;
|
|
2057
1386
|
infraIds.set(infraId, node);
|
|
2058
1387
|
infraParents.set(infraId, parentId);
|
|
2059
|
-
|
|
2060
1388
|
for (const ref of node.containerRefs) {
|
|
2061
1389
|
const el = resolveContainerRef(parsed, ref);
|
|
2062
1390
|
if (el) {
|
|
@@ -2064,11 +1392,10 @@ function collectDeploymentRefs(
|
|
|
2064
1392
|
refName: ref,
|
|
2065
1393
|
element: el,
|
|
2066
1394
|
infraId,
|
|
2067
|
-
deployLineNumber: node.lineNumber
|
|
1395
|
+
deployLineNumber: node.lineNumber
|
|
2068
1396
|
});
|
|
2069
1397
|
}
|
|
2070
1398
|
}
|
|
2071
|
-
|
|
2072
1399
|
collectDeploymentRefs(
|
|
2073
1400
|
node.children,
|
|
2074
1401
|
parsed,
|
|
@@ -2079,18 +1406,7 @@ function collectDeploymentRefs(
|
|
|
2079
1406
|
);
|
|
2080
1407
|
}
|
|
2081
1408
|
}
|
|
2082
|
-
|
|
2083
|
-
/**
|
|
2084
|
-
* Layout a C4 deployment diagram.
|
|
2085
|
-
*
|
|
2086
|
-
* Infrastructure nodes become boundary boxes (nested).
|
|
2087
|
-
* Container refs inside them become cards.
|
|
2088
|
-
* Edges are drawn between referenced containers that have relationships.
|
|
2089
|
-
*/
|
|
2090
|
-
export function layoutC4Deployment(
|
|
2091
|
-
parsed: ParsedC4,
|
|
2092
|
-
activeTagGroup?: string | null
|
|
2093
|
-
): C4LayoutResult {
|
|
1409
|
+
function layoutC4Deployment(parsed, activeTagGroup) {
|
|
2094
1410
|
if (parsed.deployment.length === 0) {
|
|
2095
1411
|
return {
|
|
2096
1412
|
nodes: [],
|
|
@@ -2098,14 +1414,12 @@ export function layoutC4Deployment(
|
|
|
2098
1414
|
legend: [],
|
|
2099
1415
|
groupBoundaries: [],
|
|
2100
1416
|
width: 0,
|
|
2101
|
-
height: 0
|
|
1417
|
+
height: 0
|
|
2102
1418
|
};
|
|
2103
1419
|
}
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
const
|
|
2107
|
-
const infraIds = new Map<string, C4DeploymentNode>();
|
|
2108
|
-
const infraParents = new Map<string, string | null>();
|
|
1420
|
+
const refs = [];
|
|
1421
|
+
const infraIds = /* @__PURE__ */ new Map();
|
|
1422
|
+
const infraParents = /* @__PURE__ */ new Map();
|
|
2109
1423
|
collectDeploymentRefs(
|
|
2110
1424
|
parsed.deployment,
|
|
2111
1425
|
parsed,
|
|
@@ -2114,7 +1428,6 @@ export function layoutC4Deployment(
|
|
|
2114
1428
|
infraIds,
|
|
2115
1429
|
infraParents
|
|
2116
1430
|
);
|
|
2117
|
-
|
|
2118
1431
|
if (refs.length === 0) {
|
|
2119
1432
|
return {
|
|
2120
1433
|
nodes: [],
|
|
@@ -2122,56 +1435,34 @@ export function layoutC4Deployment(
|
|
|
2122
1435
|
legend: [],
|
|
2123
1436
|
groupBoundaries: [],
|
|
2124
1437
|
width: 0,
|
|
2125
|
-
height: 0
|
|
1438
|
+
height: 0
|
|
2126
1439
|
};
|
|
2127
1440
|
}
|
|
2128
|
-
|
|
2129
|
-
// Deduplicate refs by element name (a container can appear in multiple infra
|
|
2130
|
-
// nodes — keep first occurrence). Track which container names are in scope.
|
|
2131
|
-
const seenRefs = new Map<string, DeploymentRefEntry>();
|
|
1441
|
+
const seenRefs = /* @__PURE__ */ new Map();
|
|
2132
1442
|
for (const ref of refs) {
|
|
2133
1443
|
const key = ref.element.name.toLowerCase();
|
|
2134
1444
|
if (!seenRefs.has(key)) seenRefs.set(key, ref);
|
|
2135
1445
|
}
|
|
2136
1446
|
const refEntries = [...seenRefs.values()];
|
|
2137
1447
|
const refNames = new Set(refEntries.map((r) => r.element.name.toLowerCase()));
|
|
2138
|
-
|
|
2139
|
-
// Build a name→element map for resolved containers
|
|
2140
|
-
const nameToElement = new Map<string, C4Element>();
|
|
1448
|
+
const nameToElement = /* @__PURE__ */ new Map();
|
|
2141
1449
|
for (const r of refEntries) {
|
|
2142
1450
|
nameToElement.set(r.element.name, r.element);
|
|
2143
1451
|
}
|
|
2144
|
-
|
|
2145
|
-
// Build compound dagre graph: infra nodes as parents, container refs as leaf nodes
|
|
2146
1452
|
const g = new dagre.graphlib.Graph({ compound: true });
|
|
2147
1453
|
g.setDefaultEdgeLabel(() => ({}));
|
|
2148
|
-
|
|
2149
|
-
// Add virtual parent nodes for each infra node
|
|
2150
1454
|
for (const [infraId] of infraIds) {
|
|
2151
1455
|
g.setNode(infraId, {});
|
|
2152
1456
|
const parentId = infraParents.get(infraId);
|
|
2153
1457
|
if (parentId) g.setParent(infraId, parentId);
|
|
2154
1458
|
}
|
|
2155
|
-
|
|
2156
|
-
// Add container ref nodes
|
|
2157
1459
|
for (const r of refEntries) {
|
|
2158
1460
|
const dims = computeC4NodeDimensions(r.element, { showTechnology: true });
|
|
2159
1461
|
g.setNode(r.element.name, { width: dims.width, height: dims.height });
|
|
2160
1462
|
g.setParent(r.element.name, r.infraId);
|
|
2161
1463
|
}
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
interface DeployRel {
|
|
2165
|
-
sourceName: string;
|
|
2166
|
-
targetName: string;
|
|
2167
|
-
label?: string;
|
|
2168
|
-
technology?: string;
|
|
2169
|
-
arrowType: C4ArrowType;
|
|
2170
|
-
lineNumber: number;
|
|
2171
|
-
}
|
|
2172
|
-
const deployRels: DeployRel[] = [];
|
|
2173
|
-
const seenEdgeKeys = new Set<string>();
|
|
2174
|
-
|
|
1464
|
+
const deployRels = [];
|
|
1465
|
+
const seenEdgeKeys = /* @__PURE__ */ new Set();
|
|
2175
1466
|
for (const r of refEntries) {
|
|
2176
1467
|
for (const rel of r.element.relationships) {
|
|
2177
1468
|
if (refNames.has(rel.target.toLowerCase())) {
|
|
@@ -2181,54 +1472,38 @@ export function layoutC4Deployment(
|
|
|
2181
1472
|
deployRels.push({
|
|
2182
1473
|
sourceName: r.element.name,
|
|
2183
1474
|
targetName: rel.target,
|
|
2184
|
-
...
|
|
2185
|
-
...
|
|
1475
|
+
...rel.label !== void 0 && { label: rel.label },
|
|
1476
|
+
...rel.technology !== void 0 && { technology: rel.technology },
|
|
2186
1477
|
arrowType: rel.arrowType,
|
|
2187
|
-
lineNumber: rel.lineNumber
|
|
1478
|
+
lineNumber: rel.lineNumber
|
|
2188
1479
|
});
|
|
2189
1480
|
}
|
|
2190
1481
|
}
|
|
2191
1482
|
}
|
|
2192
1483
|
}
|
|
2193
|
-
|
|
2194
|
-
// Adaptive spacing and graph config
|
|
2195
1484
|
const spacing = computeAdaptiveSpacing(deployRels);
|
|
2196
1485
|
g.setGraph({
|
|
2197
|
-
rankdir:
|
|
1486
|
+
rankdir: "TB",
|
|
2198
1487
|
nodesep: spacing.nodesep,
|
|
2199
1488
|
ranksep: spacing.ranksep,
|
|
2200
|
-
edgesep: spacing.edgesep
|
|
1489
|
+
edgesep: spacing.edgesep
|
|
2201
1490
|
});
|
|
2202
|
-
|
|
2203
|
-
// Add edges to dagre
|
|
2204
1491
|
for (const rel of deployRels) {
|
|
2205
|
-
if (
|
|
2206
|
-
|
|
2207
|
-
nameToElement.has(rel.targetName)
|
|
2208
|
-
) {
|
|
2209
|
-
g.setEdge(rel.sourceName, rel.targetName, { label: rel.label ?? '' });
|
|
1492
|
+
if (nameToElement.has(rel.sourceName) && nameToElement.has(rel.targetName)) {
|
|
1493
|
+
g.setEdge(rel.sourceName, rel.targetName, { label: rel.label ?? "" });
|
|
2210
1494
|
}
|
|
2211
1495
|
}
|
|
2212
|
-
|
|
2213
|
-
// Run layout
|
|
2214
1496
|
dagre.layout(g);
|
|
2215
|
-
|
|
2216
|
-
// Post-dagre crossing reduction
|
|
2217
|
-
const nodeInfraMap = new Map<string, string>();
|
|
1497
|
+
const nodeInfraMap = /* @__PURE__ */ new Map();
|
|
2218
1498
|
for (const r of refEntries) nodeInfraMap.set(r.element.name, r.infraId);
|
|
2219
1499
|
reduceCrossings(
|
|
2220
1500
|
g,
|
|
2221
|
-
deployRels
|
|
2222
|
-
.
|
|
2223
|
-
|
|
2224
|
-
nameToElement.has(r.sourceName) && nameToElement.has(r.targetName)
|
|
2225
|
-
)
|
|
2226
|
-
.map((r) => ({ source: r.sourceName, target: r.targetName })),
|
|
1501
|
+
deployRels.filter(
|
|
1502
|
+
(r) => nameToElement.has(r.sourceName) && nameToElement.has(r.targetName)
|
|
1503
|
+
).map((r) => ({ source: r.sourceName, target: r.targetName })),
|
|
2227
1504
|
nodeInfraMap
|
|
2228
1505
|
);
|
|
2229
|
-
|
|
2230
|
-
// Extract positioned nodes
|
|
2231
|
-
const nodes: Writable<C4LayoutNode>[] = [];
|
|
1506
|
+
const nodes = [];
|
|
2232
1507
|
for (const r of refEntries) {
|
|
2233
1508
|
const pos = g.node(r.element.name);
|
|
2234
1509
|
const color = resolveNodeColor(
|
|
@@ -2236,75 +1511,52 @@ export function layoutC4Deployment(
|
|
|
2236
1511
|
parsed.tagGroups,
|
|
2237
1512
|
activeTagGroup ?? null
|
|
2238
1513
|
);
|
|
2239
|
-
const tech = r.element.metadata[
|
|
2240
|
-
const description = r.element.description?.join(
|
|
1514
|
+
const tech = r.element.metadata["tech"] ?? r.element.metadata["technology"];
|
|
1515
|
+
const description = r.element.description?.join("\n");
|
|
2241
1516
|
nodes.push({
|
|
2242
1517
|
id: r.element.name,
|
|
2243
1518
|
name: r.element.name,
|
|
2244
|
-
type:
|
|
2245
|
-
...
|
|
1519
|
+
type: "container",
|
|
1520
|
+
...description !== void 0 && { description },
|
|
2246
1521
|
metadata: r.element.metadata,
|
|
2247
1522
|
lineNumber: r.element.lineNumber,
|
|
2248
|
-
...
|
|
1523
|
+
...color !== void 0 && { color },
|
|
2249
1524
|
shape: r.element.shape,
|
|
2250
|
-
...
|
|
1525
|
+
...tech !== void 0 && { technology: tech },
|
|
2251
1526
|
x: pos.x,
|
|
2252
1527
|
y: pos.y,
|
|
2253
1528
|
width: pos.width,
|
|
2254
|
-
height: pos.height
|
|
1529
|
+
height: pos.height
|
|
2255
1530
|
});
|
|
2256
1531
|
}
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
.
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
lineNumber: rel.lineNumber,
|
|
2273
|
-
points: edgeData?.points ?? [],
|
|
2274
|
-
};
|
|
2275
|
-
});
|
|
2276
|
-
|
|
2277
|
-
// Compute infrastructure boundary boxes from member node positions
|
|
2278
|
-
const groupBoundaries: Writable<C4LayoutBoundary>[] = [];
|
|
1532
|
+
const edges = deployRels.filter(
|
|
1533
|
+
(rel) => nameToElement.has(rel.sourceName) && nameToElement.has(rel.targetName)
|
|
1534
|
+
).map((rel) => {
|
|
1535
|
+
const edgeData = g.edge(rel.sourceName, rel.targetName);
|
|
1536
|
+
return {
|
|
1537
|
+
source: rel.sourceName,
|
|
1538
|
+
target: rel.targetName,
|
|
1539
|
+
arrowType: rel.arrowType,
|
|
1540
|
+
...rel.label !== void 0 && { label: rel.label },
|
|
1541
|
+
...rel.technology !== void 0 && { technology: rel.technology },
|
|
1542
|
+
lineNumber: rel.lineNumber,
|
|
1543
|
+
points: edgeData?.points ?? []
|
|
1544
|
+
};
|
|
1545
|
+
});
|
|
1546
|
+
const groupBoundaries = [];
|
|
2279
1547
|
const nodeMap = new Map(nodes.map((n) => [n.name, n]));
|
|
2280
|
-
|
|
2281
|
-
// Collect members for each infra node (containers directly inside it)
|
|
2282
|
-
const infraMembers = new Map<string, Writable<C4LayoutNode>[]>();
|
|
1548
|
+
const infraMembers = /* @__PURE__ */ new Map();
|
|
2283
1549
|
for (const r of refEntries) {
|
|
2284
1550
|
const members = infraMembers.get(r.infraId) ?? [];
|
|
2285
1551
|
const node = nodeMap.get(r.element.name);
|
|
2286
1552
|
if (node) members.push(node);
|
|
2287
1553
|
infraMembers.set(r.infraId, members);
|
|
2288
1554
|
}
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
{ x: number; y: number; width: number; height: number }
|
|
2294
|
-
>();
|
|
2295
|
-
|
|
2296
|
-
function computeInfraBounds(
|
|
2297
|
-
infraId: string
|
|
2298
|
-
): { x: number; y: number; width: number; height: number } | null {
|
|
2299
|
-
if (infraBounds.has(infraId)) return infraBounds.get(infraId)!;
|
|
2300
|
-
|
|
2301
|
-
let bMinX = Infinity,
|
|
2302
|
-
bMinY = Infinity,
|
|
2303
|
-
bMaxX = -Infinity,
|
|
2304
|
-
bMaxY = -Infinity;
|
|
1555
|
+
const infraBounds = /* @__PURE__ */ new Map();
|
|
1556
|
+
function computeInfraBounds(infraId) {
|
|
1557
|
+
if (infraBounds.has(infraId)) return infraBounds.get(infraId);
|
|
1558
|
+
let bMinX = Infinity, bMinY = Infinity, bMaxX = -Infinity, bMaxY = -Infinity;
|
|
2305
1559
|
let hasContent = false;
|
|
2306
|
-
|
|
2307
|
-
// Direct container ref members
|
|
2308
1560
|
const members = infraMembers.get(infraId) ?? [];
|
|
2309
1561
|
for (const m of members) {
|
|
2310
1562
|
hasContent = true;
|
|
@@ -2317,8 +1569,6 @@ export function layoutC4Deployment(
|
|
|
2317
1569
|
if (right > bMaxX) bMaxX = right;
|
|
2318
1570
|
if (bottom > bMaxY) bMaxY = bottom;
|
|
2319
1571
|
}
|
|
2320
|
-
|
|
2321
|
-
// Child infra node boundaries
|
|
2322
1572
|
for (const [childId, parentId] of infraParents) {
|
|
2323
1573
|
if (parentId === infraId) {
|
|
2324
1574
|
const childBounds = computeInfraBounds(childId);
|
|
@@ -2333,41 +1583,30 @@ export function layoutC4Deployment(
|
|
|
2333
1583
|
}
|
|
2334
1584
|
}
|
|
2335
1585
|
}
|
|
2336
|
-
|
|
2337
1586
|
if (!hasContent) return null;
|
|
2338
|
-
|
|
2339
1587
|
const bounds = {
|
|
2340
1588
|
x: bMinX - BOUNDARY_PAD,
|
|
2341
1589
|
y: bMinY - BOUNDARY_PAD,
|
|
2342
1590
|
width: bMaxX - bMinX + BOUNDARY_PAD * 2,
|
|
2343
|
-
height: bMaxY - bMinY + BOUNDARY_PAD * 2
|
|
1591
|
+
height: bMaxY - bMinY + BOUNDARY_PAD * 2
|
|
2344
1592
|
};
|
|
2345
1593
|
infraBounds.set(infraId, bounds);
|
|
2346
1594
|
return bounds;
|
|
2347
1595
|
}
|
|
2348
|
-
|
|
2349
|
-
// Process all infra nodes (recursion handles ordering)
|
|
2350
1596
|
for (const [infraId, node] of infraIds) {
|
|
2351
1597
|
const bounds = computeInfraBounds(infraId);
|
|
2352
1598
|
if (bounds) {
|
|
2353
|
-
const shapeLabel = node.shape !==
|
|
1599
|
+
const shapeLabel = node.shape !== "default" ? node.shape : "node";
|
|
2354
1600
|
groupBoundaries.push({
|
|
2355
1601
|
label: node.name,
|
|
2356
1602
|
typeLabel: shapeLabel,
|
|
2357
1603
|
lineNumber: node.lineNumber,
|
|
2358
|
-
...bounds
|
|
1604
|
+
...bounds
|
|
2359
1605
|
});
|
|
2360
1606
|
}
|
|
2361
1607
|
}
|
|
2362
|
-
|
|
2363
|
-
// Sort boundaries so outermost (largest area) are first — rendered bottom to top
|
|
2364
1608
|
groupBoundaries.sort((a, b) => b.width * b.height - a.width * a.height);
|
|
2365
|
-
|
|
2366
|
-
// Compute total bounding box
|
|
2367
|
-
let minX = Infinity,
|
|
2368
|
-
minY = Infinity,
|
|
2369
|
-
maxX = -Infinity,
|
|
2370
|
-
maxY = -Infinity;
|
|
1609
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
2371
1610
|
for (const node of nodes) {
|
|
2372
1611
|
const left = node.x - node.width / 2;
|
|
2373
1612
|
const top = node.y - node.height / 2;
|
|
@@ -2392,8 +1631,6 @@ export function layoutC4Deployment(
|
|
|
2392
1631
|
if (pt.y > maxY) maxY = pt.y;
|
|
2393
1632
|
}
|
|
2394
1633
|
}
|
|
2395
|
-
|
|
2396
|
-
// Shift everything so content starts at (MARGIN, MARGIN)
|
|
2397
1634
|
const shiftX = MARGIN - minX;
|
|
2398
1635
|
const shiftY = MARGIN - minY;
|
|
2399
1636
|
for (const node of nodes) {
|
|
@@ -2410,12 +1647,9 @@ export function layoutC4Deployment(
|
|
|
2410
1647
|
pt.y += shiftY;
|
|
2411
1648
|
}
|
|
2412
1649
|
}
|
|
2413
|
-
|
|
2414
1650
|
let totalWidth = maxX - minX + MARGIN * 2;
|
|
2415
1651
|
let totalHeight = maxY - minY + MARGIN * 2;
|
|
2416
|
-
|
|
2417
1652
|
const legendGroups = computeLegendGroups(parsed.tagGroups);
|
|
2418
|
-
|
|
2419
1653
|
if (legendGroups.length > 0) {
|
|
2420
1654
|
const legendY = totalHeight + MARGIN;
|
|
2421
1655
|
let legendX = MARGIN;
|
|
@@ -2429,13 +1663,23 @@ export function layoutC4Deployment(
|
|
|
2429
1663
|
if (legendRight > totalWidth) totalWidth = legendRight;
|
|
2430
1664
|
if (legendBottom > totalHeight) totalHeight = legendBottom;
|
|
2431
1665
|
}
|
|
2432
|
-
|
|
2433
1666
|
return {
|
|
2434
1667
|
nodes,
|
|
2435
1668
|
edges,
|
|
2436
1669
|
legend: legendGroups,
|
|
2437
1670
|
groupBoundaries,
|
|
2438
1671
|
width: totalWidth,
|
|
2439
|
-
height: totalHeight
|
|
1672
|
+
height: totalHeight
|
|
2440
1673
|
};
|
|
2441
1674
|
}
|
|
1675
|
+
|
|
1676
|
+
export {
|
|
1677
|
+
rollUpContextRelationships,
|
|
1678
|
+
collectCardMetadata,
|
|
1679
|
+
computeC4NodeDimensions,
|
|
1680
|
+
layoutC4Context,
|
|
1681
|
+
layoutC4Containers,
|
|
1682
|
+
layoutC4Components,
|
|
1683
|
+
layoutC4Deployment
|
|
1684
|
+
};
|
|
1685
|
+
//# sourceMappingURL=chunk-QEBUY5NY.js.map
|