@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,1239 +0,0 @@
|
|
|
1
|
-
import * as d3Selection from 'd3-selection';
|
|
2
|
-
import { FONT_FAMILY } from '../fonts';
|
|
3
|
-
import { contrastText, mix, shapeFill } from '../palettes/color-utils';
|
|
4
|
-
import type { PaletteColors } from '../palettes';
|
|
5
|
-
import type { D3ExportDimensions } from '../utils/d3-types';
|
|
6
|
-
import type { CompactViewState } from '../sharing';
|
|
7
|
-
import { parseInlineMarkdown } from '../utils/inline-markdown';
|
|
8
|
-
import { safeHref } from '../utils/safe-href';
|
|
9
|
-
import {
|
|
10
|
-
measureText,
|
|
11
|
-
truncateText,
|
|
12
|
-
wrapTextToWidth,
|
|
13
|
-
} from '../utils/text-measure';
|
|
14
|
-
import type {
|
|
15
|
-
ParsedTechRadar,
|
|
16
|
-
QuadrantPosition,
|
|
17
|
-
TechRadarRenderOptions,
|
|
18
|
-
} from './types';
|
|
19
|
-
import {
|
|
20
|
-
computeRadarLayout,
|
|
21
|
-
getRadarGeometry,
|
|
22
|
-
getQuadrantArc,
|
|
23
|
-
POSITION_ORDER,
|
|
24
|
-
} from './layout';
|
|
25
|
-
import {
|
|
26
|
-
resolveQuadrantColor,
|
|
27
|
-
renderTrendIndicator,
|
|
28
|
-
DIM_OPACITY,
|
|
29
|
-
TREND_ITEMS,
|
|
30
|
-
} from './shared';
|
|
31
|
-
import { ScaleContext } from '../utils/scaling';
|
|
32
|
-
import { renderQuadrantFocus } from './interactive';
|
|
33
|
-
import { renderIntegratedLegend } from '../utils/legend-integration';
|
|
34
|
-
import { getMaxLegendReservedHeight } from '../utils/legend-layout';
|
|
35
|
-
import type {
|
|
36
|
-
LegendConfig,
|
|
37
|
-
LegendCallbacks,
|
|
38
|
-
LegendPalette,
|
|
39
|
-
} from '../utils/legend-types';
|
|
40
|
-
|
|
41
|
-
// ============================================================
|
|
42
|
-
// Constants
|
|
43
|
-
// ============================================================
|
|
44
|
-
|
|
45
|
-
const BLIP_RADIUS = 12;
|
|
46
|
-
const BLIP_FONT_SIZE = 9;
|
|
47
|
-
const RING_LABEL_FONT_SIZE = 13;
|
|
48
|
-
const QUADRANT_LABEL_FONT_SIZE = 18;
|
|
49
|
-
const TITLE_FONT_SIZE = 18;
|
|
50
|
-
const LISTING_FONT_SIZE = 12;
|
|
51
|
-
const LISTING_HEADER_FONT_SIZE = 13;
|
|
52
|
-
const LISTING_TOP_MARGIN = 24;
|
|
53
|
-
const LISTING_COL_GAP = 16;
|
|
54
|
-
const LISTING_LINE_HEIGHT = 24;
|
|
55
|
-
|
|
56
|
-
// ============================================================
|
|
57
|
-
// SVG Init (local, matches d3.ts pattern)
|
|
58
|
-
// ============================================================
|
|
59
|
-
|
|
60
|
-
function initRadarSvg(
|
|
61
|
-
container: HTMLDivElement,
|
|
62
|
-
palette: PaletteColors,
|
|
63
|
-
exportDims?: D3ExportDimensions
|
|
64
|
-
): {
|
|
65
|
-
svg: d3Selection.Selection<SVGSVGElement, unknown, null, undefined>;
|
|
66
|
-
width: number;
|
|
67
|
-
height: number;
|
|
68
|
-
textColor: string;
|
|
69
|
-
mutedColor: string;
|
|
70
|
-
bgColor: string;
|
|
71
|
-
} | null {
|
|
72
|
-
d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
|
|
73
|
-
const width = exportDims?.width ?? container.clientWidth;
|
|
74
|
-
const height = exportDims?.height ?? container.clientHeight;
|
|
75
|
-
if (width <= 0 || height <= 0) return null;
|
|
76
|
-
const textColor = palette.text;
|
|
77
|
-
const mutedColor = palette.border;
|
|
78
|
-
const bgColor = palette.bg;
|
|
79
|
-
const svg = d3Selection
|
|
80
|
-
.select(container)
|
|
81
|
-
.append('svg')
|
|
82
|
-
.attr('width', width)
|
|
83
|
-
.attr('height', height)
|
|
84
|
-
.attr('viewBox', `0 0 ${width} ${height}`)
|
|
85
|
-
.attr('preserveAspectRatio', 'xMidYMid meet')
|
|
86
|
-
.style('background', bgColor);
|
|
87
|
-
return { svg, width, height, textColor, mutedColor, bgColor };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// ============================================================
|
|
91
|
-
// Main Renderer
|
|
92
|
-
// ============================================================
|
|
93
|
-
|
|
94
|
-
export function renderTechRadar(
|
|
95
|
-
container: HTMLDivElement,
|
|
96
|
-
parsed: ParsedTechRadar,
|
|
97
|
-
palette: PaletteColors,
|
|
98
|
-
isDark: boolean,
|
|
99
|
-
onClickItem?: (lineNumber: number) => void,
|
|
100
|
-
exportDims?: D3ExportDimensions,
|
|
101
|
-
viewState?: CompactViewState,
|
|
102
|
-
options?: TechRadarRenderOptions
|
|
103
|
-
): void {
|
|
104
|
-
if (parsed.quadrants.length === 0 || parsed.rings.length === 0) return;
|
|
105
|
-
|
|
106
|
-
// If a quadrant is focused, delegate to the interactive module
|
|
107
|
-
// (but NOT for export — always export the full radar with blip legend)
|
|
108
|
-
if (viewState?.rq && !exportDims) {
|
|
109
|
-
renderQuadrantFocus(
|
|
110
|
-
container,
|
|
111
|
-
parsed,
|
|
112
|
-
viewState.rq as QuadrantPosition,
|
|
113
|
-
palette,
|
|
114
|
-
isDark,
|
|
115
|
-
onClickItem,
|
|
116
|
-
exportDims,
|
|
117
|
-
options
|
|
118
|
-
);
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Determine if listing is visible — always show for export (blip legend is essential).
|
|
123
|
-
// Otherwise: runtime option wins; falls back to the `show-blip-legend` directive in source.
|
|
124
|
-
const directiveOn = parsed.options['show-blip-legend'] === 'on';
|
|
125
|
-
const showListing = exportDims ? true : (options?.showListing ?? directiveOn);
|
|
126
|
-
|
|
127
|
-
const init = initRadarSvg(container, palette, exportDims);
|
|
128
|
-
if (!init) return;
|
|
129
|
-
const { svg, width, height, textColor, mutedColor } = init;
|
|
130
|
-
|
|
131
|
-
const colCount = parsed.quadrants.length;
|
|
132
|
-
const listingIdealWidth = colCount * 200 + LISTING_COL_GAP * (colCount + 1);
|
|
133
|
-
const radarIdealWidth = 500;
|
|
134
|
-
const idealWidth = Math.max(listingIdealWidth, radarIdealWidth);
|
|
135
|
-
const ctx = exportDims
|
|
136
|
-
? ScaleContext.identity()
|
|
137
|
-
: ScaleContext.from(width, idealWidth);
|
|
138
|
-
|
|
139
|
-
const sBlipRadius = ctx.structural(BLIP_RADIUS);
|
|
140
|
-
const sBlipFontSize = ctx.text(BLIP_FONT_SIZE);
|
|
141
|
-
const sRingLabelFontSize = ctx.text(RING_LABEL_FONT_SIZE);
|
|
142
|
-
const sQuadrantLabelFontSize = ctx.text(QUADRANT_LABEL_FONT_SIZE);
|
|
143
|
-
const sTitleFontSize = ctx.text(TITLE_FONT_SIZE);
|
|
144
|
-
const sListingFontSize = ctx.text(LISTING_FONT_SIZE);
|
|
145
|
-
const sListingHeaderFontSize = ctx.text(LISTING_HEADER_FONT_SIZE);
|
|
146
|
-
const sListingTopMargin = ctx.aesthetic(LISTING_TOP_MARGIN);
|
|
147
|
-
const sListingColGap = ctx.aesthetic(LISTING_COL_GAP);
|
|
148
|
-
const sListingLineHeight = ctx.structural(LISTING_LINE_HEIGHT);
|
|
149
|
-
const sListingBlipR = ctx.structural(LISTING_BLIP_R);
|
|
150
|
-
|
|
151
|
-
const listingHeight = showListing
|
|
152
|
-
? estimateListingHeight(parsed, sListingLineHeight, sListingTopMargin)
|
|
153
|
-
: 0;
|
|
154
|
-
|
|
155
|
-
const radarHeight = Math.max(
|
|
156
|
-
200,
|
|
157
|
-
height - listingHeight - (showListing ? sListingTopMargin : 0)
|
|
158
|
-
);
|
|
159
|
-
const radarWidth = width;
|
|
160
|
-
|
|
161
|
-
// ── Title ──
|
|
162
|
-
const titleY = 24;
|
|
163
|
-
const showTitle = !!parsed.title && parsed.options['no-title'] !== 'on';
|
|
164
|
-
if (showTitle) {
|
|
165
|
-
svg
|
|
166
|
-
.append('text')
|
|
167
|
-
.attr('x', radarWidth / 2)
|
|
168
|
-
.attr('y', titleY)
|
|
169
|
-
.attr('text-anchor', 'middle')
|
|
170
|
-
.attr('fill', textColor)
|
|
171
|
-
.attr('font-family', FONT_FAMILY)
|
|
172
|
-
.attr('font-size', sTitleFontSize)
|
|
173
|
-
.attr('font-weight', 'bold')
|
|
174
|
-
.text(parsed.title);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// ── Legend controls (centered, standard legend system) ──
|
|
178
|
-
let legendReservedHeight = 0;
|
|
179
|
-
if (!exportDims && options?.onToggleListing) {
|
|
180
|
-
const legendY = showTitle ? titleY + 8 : 4;
|
|
181
|
-
const legendG = svg
|
|
182
|
-
.append('g')
|
|
183
|
-
.attr('transform', `translate(0, ${legendY})`);
|
|
184
|
-
|
|
185
|
-
const legendConfig: LegendConfig = {
|
|
186
|
-
groups: [
|
|
187
|
-
{
|
|
188
|
-
name: 'Trends',
|
|
189
|
-
entries: TREND_ITEMS.map((item) => ({
|
|
190
|
-
value: item.label,
|
|
191
|
-
color: palette.textMuted,
|
|
192
|
-
})),
|
|
193
|
-
},
|
|
194
|
-
],
|
|
195
|
-
position: { placement: 'top-center', titleRelation: 'below-title' },
|
|
196
|
-
mode: options?.exportMode ? 'export' : 'preview',
|
|
197
|
-
controlsGroup: {
|
|
198
|
-
toggles: [
|
|
199
|
-
{
|
|
200
|
-
id: 'blip-legend',
|
|
201
|
-
type: 'toggle',
|
|
202
|
-
label: 'Blip Legend',
|
|
203
|
-
active: showListing,
|
|
204
|
-
onToggle: (active: boolean) => options.onToggleListing!(active),
|
|
205
|
-
},
|
|
206
|
-
],
|
|
207
|
-
},
|
|
208
|
-
...(options.controlsHost !== undefined && {
|
|
209
|
-
controlsHost: options.controlsHost,
|
|
210
|
-
}),
|
|
211
|
-
};
|
|
212
|
-
const legendPalette: LegendPalette = {
|
|
213
|
-
text: palette.text,
|
|
214
|
-
textMuted: palette.textMuted,
|
|
215
|
-
bg: palette.bg,
|
|
216
|
-
surface: palette.surface,
|
|
217
|
-
primary: palette.primary,
|
|
218
|
-
};
|
|
219
|
-
const legendCallbacks: LegendCallbacks = {
|
|
220
|
-
...(options.onLegendGroupToggle !== undefined && {
|
|
221
|
-
onGroupToggle: options.onLegendGroupToggle,
|
|
222
|
-
}),
|
|
223
|
-
...(options.onToggleControlsExpand !== undefined && {
|
|
224
|
-
onControlsExpand: options.onToggleControlsExpand,
|
|
225
|
-
}),
|
|
226
|
-
onControlsToggle: (id, active) => {
|
|
227
|
-
if (id === 'blip-legend' && options.onToggleListing) {
|
|
228
|
-
options.onToggleListing(active);
|
|
229
|
-
}
|
|
230
|
-
},
|
|
231
|
-
onEntryHover: (_groupName, entryValue) => {
|
|
232
|
-
if (!entryValue) {
|
|
233
|
-
// Hover out — restore all
|
|
234
|
-
svg
|
|
235
|
-
.selectAll<SVGElement, unknown>('[data-trend]')
|
|
236
|
-
.style('opacity', '1');
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
// Map entry label back to trend value
|
|
240
|
-
const item = TREND_ITEMS.find((t) => t.label === entryValue);
|
|
241
|
-
if (!item) return;
|
|
242
|
-
const trendVal = item.trend ?? 'stable';
|
|
243
|
-
svg
|
|
244
|
-
.selectAll<SVGElement, unknown>('[data-trend]')
|
|
245
|
-
.style('opacity', function () {
|
|
246
|
-
return this.getAttribute('data-trend') === trendVal
|
|
247
|
-
? '1'
|
|
248
|
-
: String(DIM_OPACITY);
|
|
249
|
-
});
|
|
250
|
-
},
|
|
251
|
-
};
|
|
252
|
-
|
|
253
|
-
renderIntegratedLegend(legendG, {
|
|
254
|
-
...legendConfig,
|
|
255
|
-
palette: legendPalette,
|
|
256
|
-
isDark,
|
|
257
|
-
width,
|
|
258
|
-
activeGroup: options?.activeLegendGroup ?? null,
|
|
259
|
-
...(options.controlsExpanded !== undefined && {
|
|
260
|
-
controlsExpanded: options.controlsExpanded,
|
|
261
|
-
}),
|
|
262
|
-
callbacks: legendCallbacks,
|
|
263
|
-
});
|
|
264
|
-
legendReservedHeight = getMaxLegendReservedHeight(legendConfig, width) + 8;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const radarTop = (showTitle ? titleY + 16 : 8) + legendReservedHeight;
|
|
268
|
-
const radarAreaHeight = radarHeight - radarTop;
|
|
269
|
-
const radarAreaWidth = radarWidth;
|
|
270
|
-
|
|
271
|
-
const { cx, cy, maxRadius, ringBandWidth } = getRadarGeometry(
|
|
272
|
-
radarAreaWidth,
|
|
273
|
-
radarAreaHeight,
|
|
274
|
-
parsed.rings.length
|
|
275
|
-
);
|
|
276
|
-
const offsetY = radarTop;
|
|
277
|
-
|
|
278
|
-
const radarGroup = svg
|
|
279
|
-
.append('g')
|
|
280
|
-
.attr('transform', `translate(0, ${offsetY})`);
|
|
281
|
-
|
|
282
|
-
if (ctx.isBelowFloor) {
|
|
283
|
-
svg.attr('viewBox', `0 0 ${width} ${height}`).attr('width', '100%');
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// ── Ring segments (per quadrant arc slices for hover highlighting) ──
|
|
287
|
-
for (let ri = parsed.rings.length - 1; ri >= 0; ri--) {
|
|
288
|
-
const innerR = ri * ringBandWidth;
|
|
289
|
-
const outerR = (ri + 1) * ringBandWidth;
|
|
290
|
-
const fillColor =
|
|
291
|
-
ri % 2 === 0 ? palette.bg : mix(palette.bg, palette.border, 0.15);
|
|
292
|
-
// In-bounds by loop guard (ri in [0, parsed.rings.length)).
|
|
293
|
-
const ringName = parsed.rings[ri]!.name;
|
|
294
|
-
|
|
295
|
-
for (const quadrant of parsed.quadrants) {
|
|
296
|
-
const { startAngle, endAngle } = getQuadrantArc(quadrant.position);
|
|
297
|
-
const path = buildArcSlicePath(
|
|
298
|
-
cx,
|
|
299
|
-
cy,
|
|
300
|
-
innerR,
|
|
301
|
-
outerR,
|
|
302
|
-
startAngle,
|
|
303
|
-
endAngle
|
|
304
|
-
);
|
|
305
|
-
|
|
306
|
-
radarGroup
|
|
307
|
-
.append('path')
|
|
308
|
-
.attr('d', path)
|
|
309
|
-
.attr('fill', fillColor)
|
|
310
|
-
.attr('stroke', mutedColor)
|
|
311
|
-
.attr('stroke-width', 0.5)
|
|
312
|
-
.attr('data-ring-segment', '')
|
|
313
|
-
.attr('data-quadrant', quadrant.position)
|
|
314
|
-
.attr('data-ring', ringName);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// ── Quadrant divider lines ──
|
|
319
|
-
radarGroup
|
|
320
|
-
.append('line')
|
|
321
|
-
.attr('x1', cx - maxRadius)
|
|
322
|
-
.attr('y1', cy)
|
|
323
|
-
.attr('x2', cx + maxRadius)
|
|
324
|
-
.attr('y2', cy)
|
|
325
|
-
.attr('stroke', mutedColor)
|
|
326
|
-
.attr('stroke-width', 1);
|
|
327
|
-
radarGroup
|
|
328
|
-
.append('line')
|
|
329
|
-
.attr('x1', cx)
|
|
330
|
-
.attr('y1', cy - maxRadius)
|
|
331
|
-
.attr('x2', cx)
|
|
332
|
-
.attr('y2', cy + maxRadius)
|
|
333
|
-
.attr('stroke', mutedColor)
|
|
334
|
-
.attr('stroke-width', 1);
|
|
335
|
-
|
|
336
|
-
// ── Ring labels (along vertical axis, centered — avoids horizontal collision) ──
|
|
337
|
-
for (let ri = 0; ri < parsed.rings.length; ri++) {
|
|
338
|
-
const rCenter = (ri + 0.5) * ringBandWidth;
|
|
339
|
-
|
|
340
|
-
if (ri === 0) {
|
|
341
|
-
// Innermost ring: dead center
|
|
342
|
-
radarGroup
|
|
343
|
-
.append('text')
|
|
344
|
-
.attr('x', cx)
|
|
345
|
-
.attr('y', cy)
|
|
346
|
-
.attr('text-anchor', 'middle')
|
|
347
|
-
.attr('dominant-baseline', 'central')
|
|
348
|
-
.attr('fill', textColor)
|
|
349
|
-
.attr('font-family', FONT_FAMILY)
|
|
350
|
-
.attr('font-size', sRingLabelFontSize)
|
|
351
|
-
.attr('font-weight', '600')
|
|
352
|
-
.attr('opacity', 0.5)
|
|
353
|
-
// In-bounds by loop guard (ri < parsed.rings.length).
|
|
354
|
-
.text(parsed.rings[ri]!.name);
|
|
355
|
-
} else {
|
|
356
|
-
// Above center
|
|
357
|
-
radarGroup
|
|
358
|
-
.append('text')
|
|
359
|
-
.attr('x', cx)
|
|
360
|
-
.attr('y', cy - rCenter)
|
|
361
|
-
.attr('text-anchor', 'middle')
|
|
362
|
-
.attr('dominant-baseline', 'central')
|
|
363
|
-
.attr('fill', textColor)
|
|
364
|
-
.attr('font-family', FONT_FAMILY)
|
|
365
|
-
.attr('font-size', sRingLabelFontSize)
|
|
366
|
-
.attr('font-weight', '600')
|
|
367
|
-
.attr('opacity', 0.5)
|
|
368
|
-
// In-bounds by loop guard (ri < parsed.rings.length).
|
|
369
|
-
.text(parsed.rings[ri]!.name);
|
|
370
|
-
|
|
371
|
-
// Below center (mirrored)
|
|
372
|
-
radarGroup
|
|
373
|
-
.append('text')
|
|
374
|
-
.attr('x', cx)
|
|
375
|
-
.attr('y', cy + rCenter)
|
|
376
|
-
.attr('text-anchor', 'middle')
|
|
377
|
-
.attr('dominant-baseline', 'central')
|
|
378
|
-
.attr('fill', textColor)
|
|
379
|
-
.attr('font-family', FONT_FAMILY)
|
|
380
|
-
.attr('font-size', sRingLabelFontSize)
|
|
381
|
-
.attr('font-weight', '600')
|
|
382
|
-
.attr('opacity', 0.5)
|
|
383
|
-
// In-bounds by loop guard (ri < parsed.rings.length).
|
|
384
|
-
.text(parsed.rings[ri]!.name);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// ── Quadrant labels in corners ──
|
|
389
|
-
for (const quadrant of parsed.quadrants) {
|
|
390
|
-
const qColor = resolveQuadrantColor(
|
|
391
|
-
quadrant.position,
|
|
392
|
-
quadrant.color,
|
|
393
|
-
palette
|
|
394
|
-
);
|
|
395
|
-
const {
|
|
396
|
-
x: labelX,
|
|
397
|
-
y: labelY,
|
|
398
|
-
anchor,
|
|
399
|
-
} = getQuadrantLabelPosition(quadrant.position, cx, cy, maxRadius);
|
|
400
|
-
const labelGroup = radarGroup
|
|
401
|
-
.append('g')
|
|
402
|
-
.attr('data-line-number', quadrant.lineNumber)
|
|
403
|
-
.style('cursor', 'pointer');
|
|
404
|
-
|
|
405
|
-
renderQuadrantLabel(
|
|
406
|
-
labelGroup,
|
|
407
|
-
quadrant.name,
|
|
408
|
-
labelX,
|
|
409
|
-
labelY,
|
|
410
|
-
anchor,
|
|
411
|
-
qColor,
|
|
412
|
-
maxRadius * 0.9,
|
|
413
|
-
sQuadrantLabelFontSize
|
|
414
|
-
);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// ── Interactive ring×quadrant hover hit areas (rendered before blips so blips sit on top) ──
|
|
418
|
-
if (!exportDims) {
|
|
419
|
-
renderRingHoverAreas(
|
|
420
|
-
radarGroup,
|
|
421
|
-
svg,
|
|
422
|
-
parsed,
|
|
423
|
-
palette,
|
|
424
|
-
cx,
|
|
425
|
-
cy,
|
|
426
|
-
ringBandWidth,
|
|
427
|
-
maxRadius
|
|
428
|
-
);
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// ── Compute layout and render blips ──
|
|
432
|
-
const layoutPoints = computeRadarLayout(
|
|
433
|
-
parsed,
|
|
434
|
-
radarAreaWidth,
|
|
435
|
-
radarAreaHeight,
|
|
436
|
-
ctx
|
|
437
|
-
);
|
|
438
|
-
|
|
439
|
-
// Rich popover for blip details
|
|
440
|
-
const popover = createBlipPopover(container, palette, isDark);
|
|
441
|
-
let pinnedLineNum: string | null = null;
|
|
442
|
-
|
|
443
|
-
function showBlipHighlight(
|
|
444
|
-
lineNum: string,
|
|
445
|
-
bx: number,
|
|
446
|
-
by: number,
|
|
447
|
-
blipGroup: d3Selection.Selection<SVGGElement, unknown, null, undefined>
|
|
448
|
-
) {
|
|
449
|
-
blipGroup.attr(
|
|
450
|
-
'transform',
|
|
451
|
-
`translate(${bx},${by}) scale(1.5) translate(${-bx},${-by})`
|
|
452
|
-
);
|
|
453
|
-
svg
|
|
454
|
-
.selectAll<SVGElement, unknown>('[data-line-number]')
|
|
455
|
-
.style('opacity', function () {
|
|
456
|
-
return this.getAttribute('data-line-number') === lineNum
|
|
457
|
-
? '1'
|
|
458
|
-
: String(DIM_OPACITY);
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
function clearBlipHighlight() {
|
|
463
|
-
svg
|
|
464
|
-
.selectAll<SVGElement, unknown>('[data-line-number]')
|
|
465
|
-
.style('opacity', '1')
|
|
466
|
-
.attr('transform', null);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
for (const point of layoutPoints) {
|
|
470
|
-
const quadrant = parsed.quadrants.find((q) =>
|
|
471
|
-
q.blips.includes(point.blip)
|
|
472
|
-
)!;
|
|
473
|
-
const qColor = resolveQuadrantColor(
|
|
474
|
-
quadrant.position,
|
|
475
|
-
quadrant.color,
|
|
476
|
-
palette
|
|
477
|
-
);
|
|
478
|
-
|
|
479
|
-
const blipGroup = radarGroup
|
|
480
|
-
.append('g')
|
|
481
|
-
.attr('data-line-number', point.blip.lineNumber)
|
|
482
|
-
.attr('data-quadrant', quadrant.position)
|
|
483
|
-
.attr('data-ring', point.blip.ring)
|
|
484
|
-
.attr('data-trend', point.blip.trend ?? 'stable')
|
|
485
|
-
.style('cursor', 'pointer');
|
|
486
|
-
|
|
487
|
-
// Angle from blip toward radar center in SVG coords (Y-down)
|
|
488
|
-
const angleToCenter = Math.atan2(cy - point.y, cx - point.x);
|
|
489
|
-
|
|
490
|
-
// Trend indicator + circle
|
|
491
|
-
renderTrendIndicator(
|
|
492
|
-
blipGroup,
|
|
493
|
-
point.blip.trend,
|
|
494
|
-
qColor,
|
|
495
|
-
point.x,
|
|
496
|
-
point.y,
|
|
497
|
-
sBlipRadius,
|
|
498
|
-
angleToCenter
|
|
499
|
-
);
|
|
500
|
-
|
|
501
|
-
// Blip number
|
|
502
|
-
blipGroup
|
|
503
|
-
.append('text')
|
|
504
|
-
.attr('x', point.x)
|
|
505
|
-
.attr('y', point.y + 3)
|
|
506
|
-
.attr('text-anchor', 'middle')
|
|
507
|
-
.attr('fill', isDark ? '#000' : '#fff')
|
|
508
|
-
.attr('font-family', FONT_FAMILY)
|
|
509
|
-
.attr('font-size', sBlipFontSize)
|
|
510
|
-
.attr('font-weight', 'bold')
|
|
511
|
-
.text(point.blip.globalNumber);
|
|
512
|
-
|
|
513
|
-
// Hover: show rich popover + highlight
|
|
514
|
-
const lineNum = String(point.blip.lineNumber);
|
|
515
|
-
const bx = point.x;
|
|
516
|
-
const by = point.y;
|
|
517
|
-
blipGroup
|
|
518
|
-
.on('mouseenter', (event: MouseEvent) => {
|
|
519
|
-
if (pinnedLineNum) return; // don't interfere with pinned popover
|
|
520
|
-
showBlipPopover(
|
|
521
|
-
popover,
|
|
522
|
-
point.blip,
|
|
523
|
-
qColor,
|
|
524
|
-
palette,
|
|
525
|
-
isDark,
|
|
526
|
-
event,
|
|
527
|
-
// Tech-radar opts out of solid-fill: blip popover stays in tinted
|
|
528
|
-
// form so it reads consistently against the radar bg regardless of
|
|
529
|
-
// the diagram's option setting.
|
|
530
|
-
false
|
|
531
|
-
);
|
|
532
|
-
showBlipHighlight(lineNum, bx, by, blipGroup);
|
|
533
|
-
})
|
|
534
|
-
.on('mousemove', (event: MouseEvent) => {
|
|
535
|
-
if (pinnedLineNum) return;
|
|
536
|
-
positionPopover(popover, event);
|
|
537
|
-
})
|
|
538
|
-
.on('mouseleave', () => {
|
|
539
|
-
if (pinnedLineNum) return;
|
|
540
|
-
hideBlipPopover(popover);
|
|
541
|
-
clearBlipHighlight();
|
|
542
|
-
});
|
|
543
|
-
|
|
544
|
-
// Click: pin/unpin the popover (don't stopPropagation so the
|
|
545
|
-
// interactivity hook's document listener can fire for editor navigation)
|
|
546
|
-
blipGroup.on('click', (event: MouseEvent) => {
|
|
547
|
-
(event as MouseEvent & { _blipClick?: boolean })._blipClick = true;
|
|
548
|
-
if (pinnedLineNum === lineNum) {
|
|
549
|
-
// Unpin
|
|
550
|
-
pinnedLineNum = null;
|
|
551
|
-
hideBlipPopover(popover);
|
|
552
|
-
clearBlipHighlight();
|
|
553
|
-
} else {
|
|
554
|
-
// Pin this blip — enable pointer events so links are clickable
|
|
555
|
-
pinnedLineNum = lineNum;
|
|
556
|
-
showBlipPopover(
|
|
557
|
-
popover,
|
|
558
|
-
point.blip,
|
|
559
|
-
qColor,
|
|
560
|
-
palette,
|
|
561
|
-
isDark,
|
|
562
|
-
event,
|
|
563
|
-
// Tech-radar opts out of solid-fill: blip popover stays in tinted
|
|
564
|
-
// form so it reads consistently against the radar bg regardless of
|
|
565
|
-
// the diagram's option setting.
|
|
566
|
-
false
|
|
567
|
-
);
|
|
568
|
-
popover.style.pointerEvents = 'auto';
|
|
569
|
-
showBlipHighlight(lineNum, bx, by, blipGroup);
|
|
570
|
-
}
|
|
571
|
-
});
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
// Click on empty space clears pinned popover (ignore blip clicks)
|
|
575
|
-
svg.on('click', (event: MouseEvent) => {
|
|
576
|
-
if ((event as MouseEvent & { _blipClick?: boolean })._blipClick) return;
|
|
577
|
-
if (pinnedLineNum) {
|
|
578
|
-
pinnedLineNum = null;
|
|
579
|
-
hideBlipPopover(popover);
|
|
580
|
-
clearBlipHighlight();
|
|
581
|
-
}
|
|
582
|
-
});
|
|
583
|
-
|
|
584
|
-
// ── Active line from editor cursor → show popover for that blip ──
|
|
585
|
-
if (options?.activeLine && !pinnedLineNum) {
|
|
586
|
-
const activeLn = options.activeLine;
|
|
587
|
-
// Find the blip that matches this line (or whose description contains this line)
|
|
588
|
-
for (const point of layoutPoints) {
|
|
589
|
-
const blip = point.blip;
|
|
590
|
-
const isOnBlip = blip.lineNumber === activeLn;
|
|
591
|
-
const isOnDesc =
|
|
592
|
-
blip.description.length > 0 &&
|
|
593
|
-
activeLn > blip.lineNumber &&
|
|
594
|
-
activeLn <= blip.lineNumber + blip.description.length;
|
|
595
|
-
|
|
596
|
-
if (isOnBlip || isOnDesc) {
|
|
597
|
-
const quadrant = parsed.quadrants.find((q) => q.blips.includes(blip))!;
|
|
598
|
-
const qColor = resolveQuadrantColor(
|
|
599
|
-
quadrant.position,
|
|
600
|
-
quadrant.color,
|
|
601
|
-
palette
|
|
602
|
-
);
|
|
603
|
-
// Show popover at the blip's position
|
|
604
|
-
const svgRect = (svg.node() as SVGSVGElement)?.getBoundingClientRect();
|
|
605
|
-
if (svgRect) {
|
|
606
|
-
const fakeEvent = {
|
|
607
|
-
clientX: svgRect.left + point.x,
|
|
608
|
-
clientY: svgRect.top + offsetY + point.y,
|
|
609
|
-
} as MouseEvent;
|
|
610
|
-
showBlipPopover(
|
|
611
|
-
popover,
|
|
612
|
-
blip,
|
|
613
|
-
qColor,
|
|
614
|
-
palette,
|
|
615
|
-
isDark,
|
|
616
|
-
fakeEvent,
|
|
617
|
-
// Tech-radar opts out of solid-fill: blip popover stays in tinted
|
|
618
|
-
// form so it reads consistently against the radar bg regardless of
|
|
619
|
-
// the diagram's option setting.
|
|
620
|
-
false
|
|
621
|
-
);
|
|
622
|
-
}
|
|
623
|
-
// Scale up and dim
|
|
624
|
-
const lineNum = String(blip.lineNumber);
|
|
625
|
-
const blipEl = svg.select(`[data-line-number="${lineNum}"]`);
|
|
626
|
-
if (!blipEl.empty()) {
|
|
627
|
-
blipEl.attr(
|
|
628
|
-
'transform',
|
|
629
|
-
`translate(${point.x},${point.y}) scale(1.5) translate(${-point.x},${-point.y})`
|
|
630
|
-
);
|
|
631
|
-
}
|
|
632
|
-
svg
|
|
633
|
-
.selectAll<SVGElement, unknown>('[data-line-number]')
|
|
634
|
-
.style('opacity', function () {
|
|
635
|
-
return this.getAttribute('data-line-number') === lineNum
|
|
636
|
-
? '1'
|
|
637
|
-
: String(DIM_OPACITY);
|
|
638
|
-
});
|
|
639
|
-
break;
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
// ── Four-column blip listing below radar ──
|
|
645
|
-
if (showListing) {
|
|
646
|
-
renderBlipListing(
|
|
647
|
-
svg,
|
|
648
|
-
parsed,
|
|
649
|
-
palette,
|
|
650
|
-
isDark,
|
|
651
|
-
textColor,
|
|
652
|
-
radarHeight + sListingTopMargin,
|
|
653
|
-
width,
|
|
654
|
-
onClickItem,
|
|
655
|
-
sListingFontSize,
|
|
656
|
-
sListingHeaderFontSize,
|
|
657
|
-
sListingColGap,
|
|
658
|
-
sListingLineHeight,
|
|
659
|
-
sListingBlipR
|
|
660
|
-
);
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
// ============================================================
|
|
665
|
-
// Four-Column Listing
|
|
666
|
-
// ============================================================
|
|
667
|
-
|
|
668
|
-
const LISTING_BLIP_R = 11;
|
|
669
|
-
|
|
670
|
-
function renderBlipListing(
|
|
671
|
-
svg: d3Selection.Selection<SVGSVGElement, unknown, null, undefined>,
|
|
672
|
-
parsed: ParsedTechRadar,
|
|
673
|
-
palette: PaletteColors,
|
|
674
|
-
isDark: boolean,
|
|
675
|
-
textColor: string,
|
|
676
|
-
startY: number,
|
|
677
|
-
totalWidth: number,
|
|
678
|
-
onClickItem?: (lineNumber: number) => void,
|
|
679
|
-
sListingFontSize = LISTING_FONT_SIZE,
|
|
680
|
-
sListingHeaderFontSize = LISTING_HEADER_FONT_SIZE,
|
|
681
|
-
sListingColGap = LISTING_COL_GAP,
|
|
682
|
-
sListingLineHeight = LISTING_LINE_HEIGHT,
|
|
683
|
-
sListingBlipR = LISTING_BLIP_R
|
|
684
|
-
): void {
|
|
685
|
-
const colCount = parsed.quadrants.length;
|
|
686
|
-
if (colCount === 0) return;
|
|
687
|
-
|
|
688
|
-
const colWidth = (totalWidth - sListingColGap * (colCount + 1)) / colCount;
|
|
689
|
-
|
|
690
|
-
// Sort quadrants in POSITION_ORDER
|
|
691
|
-
const sortedQuadrants = [...parsed.quadrants].sort(
|
|
692
|
-
(a, b) =>
|
|
693
|
-
POSITION_ORDER.indexOf(a.position) - POSITION_ORDER.indexOf(b.position)
|
|
694
|
-
);
|
|
695
|
-
|
|
696
|
-
for (let ci = 0; ci < sortedQuadrants.length; ci++) {
|
|
697
|
-
// In-bounds by loop guard (ci < sortedQuadrants.length).
|
|
698
|
-
const quadrant = sortedQuadrants[ci]!;
|
|
699
|
-
const qColor = resolveQuadrantColor(
|
|
700
|
-
quadrant.position,
|
|
701
|
-
quadrant.color,
|
|
702
|
-
palette
|
|
703
|
-
);
|
|
704
|
-
const colX = sListingColGap + ci * (colWidth + sListingColGap);
|
|
705
|
-
let y = startY;
|
|
706
|
-
|
|
707
|
-
// Column header — hover highlights entire quadrant on radar
|
|
708
|
-
const headerText = svg
|
|
709
|
-
.append('text')
|
|
710
|
-
.attr('x', colX)
|
|
711
|
-
.attr('y', y)
|
|
712
|
-
.attr('fill', qColor)
|
|
713
|
-
.attr('font-family', FONT_FAMILY)
|
|
714
|
-
.attr('font-size', sListingHeaderFontSize)
|
|
715
|
-
.attr('font-weight', 'bold')
|
|
716
|
-
.style('cursor', 'pointer')
|
|
717
|
-
.text(quadrant.name);
|
|
718
|
-
|
|
719
|
-
const qPos = quadrant.position;
|
|
720
|
-
headerText
|
|
721
|
-
.on('mouseenter', () => {
|
|
722
|
-
// Dim everything except this quadrant's blips + ring segments
|
|
723
|
-
svg
|
|
724
|
-
.selectAll<SVGElement, unknown>('[data-quadrant][data-ring]')
|
|
725
|
-
.style('opacity', function () {
|
|
726
|
-
return this.getAttribute('data-quadrant') === qPos
|
|
727
|
-
? '1'
|
|
728
|
-
: String(DIM_OPACITY);
|
|
729
|
-
});
|
|
730
|
-
})
|
|
731
|
-
.on('mouseleave', () => {
|
|
732
|
-
svg
|
|
733
|
-
.selectAll<SVGElement, unknown>('[data-quadrant][data-ring]')
|
|
734
|
-
.style('opacity', '1');
|
|
735
|
-
});
|
|
736
|
-
|
|
737
|
-
y += sListingLineHeight + 6;
|
|
738
|
-
|
|
739
|
-
// Sort blips by globalNumber
|
|
740
|
-
const sortedBlips = [...quadrant.blips].sort(
|
|
741
|
-
(a, b) => a.globalNumber - b.globalNumber
|
|
742
|
-
);
|
|
743
|
-
|
|
744
|
-
for (const blip of sortedBlips) {
|
|
745
|
-
const itemGroup = svg
|
|
746
|
-
.append('g')
|
|
747
|
-
.attr('data-line-number', blip.lineNumber)
|
|
748
|
-
.attr('data-quadrant', quadrant.position)
|
|
749
|
-
.attr('data-ring', blip.ring)
|
|
750
|
-
.attr('data-trend', blip.trend ?? 'stable')
|
|
751
|
-
.style('cursor', onClickItem ? 'pointer' : 'default');
|
|
752
|
-
|
|
753
|
-
const blipCx = colX + sListingBlipR;
|
|
754
|
-
const blipCy = y - sListingBlipR + 2;
|
|
755
|
-
|
|
756
|
-
const trendAngle = -Math.PI / 2;
|
|
757
|
-
renderTrendIndicator(
|
|
758
|
-
itemGroup,
|
|
759
|
-
blip.trend,
|
|
760
|
-
qColor,
|
|
761
|
-
blipCx,
|
|
762
|
-
blipCy,
|
|
763
|
-
sListingBlipR,
|
|
764
|
-
trendAngle
|
|
765
|
-
);
|
|
766
|
-
|
|
767
|
-
// Number inside the circle
|
|
768
|
-
itemGroup
|
|
769
|
-
.append('text')
|
|
770
|
-
.attr('x', blipCx)
|
|
771
|
-
.attr('y', blipCy + 3)
|
|
772
|
-
.attr('text-anchor', 'middle')
|
|
773
|
-
.attr('fill', isDark ? '#000' : '#fff')
|
|
774
|
-
.attr('font-family', FONT_FAMILY)
|
|
775
|
-
.attr('font-size', 9)
|
|
776
|
-
.attr('font-weight', 'bold')
|
|
777
|
-
.text(blip.globalNumber);
|
|
778
|
-
|
|
779
|
-
// Blip name + ring — truncated to fit column width
|
|
780
|
-
const textX = colX + sListingBlipR * 2 + 6;
|
|
781
|
-
const availableWidth = colWidth - sListingBlipR * 2 - 8;
|
|
782
|
-
const fullLabel = `${blip.name} (${blip.ring})`;
|
|
783
|
-
const label = truncateText(fullLabel, sListingFontSize, availableWidth);
|
|
784
|
-
|
|
785
|
-
itemGroup
|
|
786
|
-
.append('text')
|
|
787
|
-
.attr('x', textX)
|
|
788
|
-
.attr('y', y)
|
|
789
|
-
.attr('fill', textColor)
|
|
790
|
-
.attr('font-family', FONT_FAMILY)
|
|
791
|
-
.attr('font-size', sListingFontSize)
|
|
792
|
-
.text(label);
|
|
793
|
-
|
|
794
|
-
// Cross-highlight: hover listing blip → highlight + scale up radar blip
|
|
795
|
-
const ln = String(blip.lineNumber);
|
|
796
|
-
itemGroup
|
|
797
|
-
.on('mouseenter', () => {
|
|
798
|
-
svg
|
|
799
|
-
.selectAll<SVGElement, unknown>('[data-line-number]')
|
|
800
|
-
.style('opacity', function () {
|
|
801
|
-
const isMatch = this.getAttribute('data-line-number') === ln;
|
|
802
|
-
// Scale up the matching radar blip (not listing items)
|
|
803
|
-
if (
|
|
804
|
-
isMatch &&
|
|
805
|
-
this.getAttribute('data-quadrant') &&
|
|
806
|
-
this.closest('g[transform]')
|
|
807
|
-
) {
|
|
808
|
-
const bbox = (this as SVGGraphicsElement).getBBox?.();
|
|
809
|
-
if (bbox) {
|
|
810
|
-
const bx = bbox.x + bbox.width / 2;
|
|
811
|
-
const by = bbox.y + bbox.height / 2;
|
|
812
|
-
this.setAttribute(
|
|
813
|
-
'transform',
|
|
814
|
-
`translate(${bx},${by}) scale(1.5) translate(${-bx},${-by})`
|
|
815
|
-
);
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
return isMatch ? '1' : String(DIM_OPACITY);
|
|
819
|
-
});
|
|
820
|
-
})
|
|
821
|
-
.on('mouseleave', () => {
|
|
822
|
-
svg
|
|
823
|
-
.selectAll<SVGElement, unknown>('[data-line-number]')
|
|
824
|
-
.style('opacity', '1')
|
|
825
|
-
.attr('transform', null);
|
|
826
|
-
});
|
|
827
|
-
|
|
828
|
-
if (onClickItem) {
|
|
829
|
-
itemGroup.on('click', () => onClickItem(blip.lineNumber));
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
y += sListingLineHeight;
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
// ============================================================
|
|
838
|
-
// Ring×Quadrant Hover Interactivity
|
|
839
|
-
// ============================================================
|
|
840
|
-
|
|
841
|
-
/**
|
|
842
|
-
* Render transparent arc hit areas for each ring×quadrant slice.
|
|
843
|
-
* On hover, dims all blips (radar + listing) except those in the hovered slice.
|
|
844
|
-
*/
|
|
845
|
-
function renderRingHoverAreas(
|
|
846
|
-
radarGroup: d3Selection.Selection<SVGGElement, unknown, null, undefined>,
|
|
847
|
-
svg: d3Selection.Selection<SVGSVGElement, unknown, null, undefined>,
|
|
848
|
-
parsed: ParsedTechRadar,
|
|
849
|
-
palette: PaletteColors,
|
|
850
|
-
cx: number,
|
|
851
|
-
cy: number,
|
|
852
|
-
ringBandWidth: number,
|
|
853
|
-
_maxRadius: number
|
|
854
|
-
): void {
|
|
855
|
-
for (const quadrant of parsed.quadrants) {
|
|
856
|
-
const { startAngle, endAngle } = getQuadrantArc(quadrant.position);
|
|
857
|
-
const qColor = resolveQuadrantColor(
|
|
858
|
-
quadrant.position,
|
|
859
|
-
quadrant.color,
|
|
860
|
-
palette
|
|
861
|
-
);
|
|
862
|
-
|
|
863
|
-
for (let ri = 0; ri < parsed.rings.length; ri++) {
|
|
864
|
-
const innerR = ri * ringBandWidth;
|
|
865
|
-
const outerR = (ri + 1) * ringBandWidth;
|
|
866
|
-
// In-bounds by loop guard (ri < parsed.rings.length).
|
|
867
|
-
const ringName = parsed.rings[ri]!.name;
|
|
868
|
-
|
|
869
|
-
const path = buildArcSlicePath(
|
|
870
|
-
cx,
|
|
871
|
-
cy,
|
|
872
|
-
innerR,
|
|
873
|
-
outerR,
|
|
874
|
-
startAngle,
|
|
875
|
-
endAngle
|
|
876
|
-
);
|
|
877
|
-
|
|
878
|
-
const hitArea = radarGroup
|
|
879
|
-
.append('path')
|
|
880
|
-
.attr('d', path)
|
|
881
|
-
.attr('fill', 'transparent')
|
|
882
|
-
.style('cursor', 'pointer');
|
|
883
|
-
|
|
884
|
-
hitArea
|
|
885
|
-
.on('mouseenter', () => {
|
|
886
|
-
// Tint the hovered slice via the overlay
|
|
887
|
-
hitArea.attr('fill', qColor).attr('opacity', 0.15);
|
|
888
|
-
// Dim all blips/listing except matching ring+quadrant
|
|
889
|
-
svg
|
|
890
|
-
.selectAll<SVGElement, unknown>('[data-quadrant][data-ring]')
|
|
891
|
-
.style('opacity', function () {
|
|
892
|
-
const q = this.getAttribute('data-quadrant');
|
|
893
|
-
const r = this.getAttribute('data-ring');
|
|
894
|
-
return q === quadrant.position && r === ringName
|
|
895
|
-
? '1'
|
|
896
|
-
: String(DIM_OPACITY);
|
|
897
|
-
});
|
|
898
|
-
})
|
|
899
|
-
.on('mouseleave', () => {
|
|
900
|
-
// Remove overlay tint
|
|
901
|
-
hitArea.attr('fill', 'transparent').attr('opacity', 1);
|
|
902
|
-
// Restore all opacities
|
|
903
|
-
svg
|
|
904
|
-
.selectAll<SVGElement, unknown>('[data-quadrant][data-ring]')
|
|
905
|
-
.style('opacity', '1');
|
|
906
|
-
});
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
/** Build an SVG arc-slice path between inner and outer radius for a quadrant arc. */
|
|
912
|
-
function buildArcSlicePath(
|
|
913
|
-
cx: number,
|
|
914
|
-
cy: number,
|
|
915
|
-
innerR: number,
|
|
916
|
-
outerR: number,
|
|
917
|
-
startAngle: number,
|
|
918
|
-
endAngle: number
|
|
919
|
-
): string {
|
|
920
|
-
// Convert math angles to SVG coordinates (negate sin for Y-down)
|
|
921
|
-
const ox1 = cx + outerR * Math.cos(startAngle);
|
|
922
|
-
const oy1 = cy - outerR * Math.sin(startAngle);
|
|
923
|
-
const ox2 = cx + outerR * Math.cos(endAngle);
|
|
924
|
-
const oy2 = cy - outerR * Math.sin(endAngle);
|
|
925
|
-
const ix1 = cx + innerR * Math.cos(endAngle);
|
|
926
|
-
const iy1 = cy - innerR * Math.sin(endAngle);
|
|
927
|
-
const ix2 = cx + innerR * Math.cos(startAngle);
|
|
928
|
-
const iy2 = cy - innerR * Math.sin(startAngle);
|
|
929
|
-
|
|
930
|
-
if (innerR === 0) {
|
|
931
|
-
// Pie wedge from center
|
|
932
|
-
return `M${cx},${cy} L${ox1},${oy1} A${outerR},${outerR} 0 0,0 ${ox2},${oy2} Z`;
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
return `M${ox1},${oy1} A${outerR},${outerR} 0 0,0 ${ox2},${oy2} L${ix1},${iy1} A${innerR},${innerR} 0 0,1 ${ix2},${iy2} Z`;
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
// ============================================================
|
|
939
|
-
// Trend Items (used by legend group entries)
|
|
940
|
-
// ============================================================
|
|
941
|
-
|
|
942
|
-
function estimateListingHeight(
|
|
943
|
-
parsed: ParsedTechRadar,
|
|
944
|
-
lineHeight = LISTING_LINE_HEIGHT,
|
|
945
|
-
topMargin = LISTING_TOP_MARGIN
|
|
946
|
-
): number {
|
|
947
|
-
const maxBlipsInQuadrant = Math.max(
|
|
948
|
-
0,
|
|
949
|
-
...parsed.quadrants.map((q) => q.blips.length)
|
|
950
|
-
);
|
|
951
|
-
return lineHeight * (maxBlipsInQuadrant + 1) + lineHeight + topMargin;
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
// ============================================================
|
|
955
|
-
// Rich Blip Popover (B&L-style node card)
|
|
956
|
-
// ============================================================
|
|
957
|
-
|
|
958
|
-
import type { TechRadarBlip } from './types';
|
|
959
|
-
|
|
960
|
-
function createBlipPopover(
|
|
961
|
-
container: HTMLElement,
|
|
962
|
-
_palette: PaletteColors,
|
|
963
|
-
isDark: boolean
|
|
964
|
-
): HTMLDivElement {
|
|
965
|
-
container.style.position = 'relative';
|
|
966
|
-
const existing = container.querySelector<HTMLDivElement>(
|
|
967
|
-
'[data-blip-popover]'
|
|
968
|
-
);
|
|
969
|
-
if (existing) {
|
|
970
|
-
existing.style.display = 'none';
|
|
971
|
-
return existing;
|
|
972
|
-
}
|
|
973
|
-
const el = document.createElement('div');
|
|
974
|
-
el.setAttribute('data-blip-popover', '');
|
|
975
|
-
el.style.position = 'absolute';
|
|
976
|
-
el.style.display = 'none';
|
|
977
|
-
el.style.pointerEvents = 'none';
|
|
978
|
-
el.style.zIndex = '20';
|
|
979
|
-
el.style.maxWidth = '280px';
|
|
980
|
-
el.style.fontFamily = FONT_FAMILY;
|
|
981
|
-
el.style.fontSize = '12px';
|
|
982
|
-
el.style.lineHeight = '1.5';
|
|
983
|
-
el.style.borderRadius = '6px';
|
|
984
|
-
el.style.overflow = 'hidden';
|
|
985
|
-
el.style.boxShadow = isDark
|
|
986
|
-
? '0 4px 12px rgba(0,0,0,0.4)'
|
|
987
|
-
: '0 4px 12px rgba(0,0,0,0.12)';
|
|
988
|
-
container.appendChild(el);
|
|
989
|
-
return el;
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
function showBlipPopover(
|
|
993
|
-
popover: HTMLDivElement,
|
|
994
|
-
blip: TechRadarBlip,
|
|
995
|
-
qColor: string,
|
|
996
|
-
palette: PaletteColors,
|
|
997
|
-
isDark: boolean,
|
|
998
|
-
event: MouseEvent,
|
|
999
|
-
solid?: boolean
|
|
1000
|
-
): void {
|
|
1001
|
-
const fillColor = shapeFill(palette, qColor, isDark, {
|
|
1002
|
-
...(solid !== undefined && { solid }),
|
|
1003
|
-
});
|
|
1004
|
-
const hasDesc = blip.description.length > 0;
|
|
1005
|
-
const onFillText = contrastText(
|
|
1006
|
-
fillColor,
|
|
1007
|
-
palette.textOnFillLight,
|
|
1008
|
-
palette.textOnFillDark
|
|
1009
|
-
);
|
|
1010
|
-
|
|
1011
|
-
let html = `<div style="background:${fillColor}; border: 1.5px solid ${qColor}; border-radius: 6px; overflow: hidden;">`;
|
|
1012
|
-
html += `<div style="padding: 8px 12px; font-weight: 600; color: ${onFillText};">${escapeHtml(blip.name)}</div>`;
|
|
1013
|
-
|
|
1014
|
-
if (hasDesc) {
|
|
1015
|
-
html += `<div style="border-top: 1px solid ${qColor}; opacity: 0.3;"></div>`;
|
|
1016
|
-
html += `<div style="padding: 6px 12px 8px; color: ${palette.textMuted}; font-size: 11px; line-height: 1.6;">`;
|
|
1017
|
-
// Join consecutive prose lines into paragraphs; bullets stay separate
|
|
1018
|
-
const paragraphs = joinDescriptionParagraphs(blip.description);
|
|
1019
|
-
for (const para of paragraphs) {
|
|
1020
|
-
html += renderDescriptionLine(para, palette);
|
|
1021
|
-
}
|
|
1022
|
-
html += `</div>`;
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
html += `</div>`;
|
|
1026
|
-
|
|
1027
|
-
popover.innerHTML = html;
|
|
1028
|
-
popover.style.display = 'block';
|
|
1029
|
-
positionPopover(popover, event);
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
function positionPopover(popover: HTMLDivElement, event: MouseEvent): void {
|
|
1033
|
-
const container = popover.parentElement!;
|
|
1034
|
-
const rect = container.getBoundingClientRect();
|
|
1035
|
-
const tipW = popover.offsetWidth;
|
|
1036
|
-
const tipH = popover.offsetHeight;
|
|
1037
|
-
const cursorX = event.clientX - rect.left;
|
|
1038
|
-
const cursorY = event.clientY - rect.top;
|
|
1039
|
-
const centerX = rect.width / 2;
|
|
1040
|
-
const centerY = rect.height / 2;
|
|
1041
|
-
|
|
1042
|
-
// Position toward the center of the diagram relative to the blip
|
|
1043
|
-
let left = cursorX < centerX ? cursorX + 16 : cursorX - tipW - 16;
|
|
1044
|
-
let top = cursorY < centerY ? cursorY + 16 : cursorY - tipH - 16;
|
|
1045
|
-
|
|
1046
|
-
// Clamp to container bounds
|
|
1047
|
-
if (left + tipW > rect.width - 4) left = rect.width - tipW - 4;
|
|
1048
|
-
if (left < 4) left = 4;
|
|
1049
|
-
if (top + tipH > rect.height - 4) top = rect.height - tipH - 4;
|
|
1050
|
-
if (top < 4) top = 4;
|
|
1051
|
-
|
|
1052
|
-
popover.style.left = `${left}px`;
|
|
1053
|
-
popover.style.top = `${top}px`;
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
function hideBlipPopover(popover: HTMLDivElement): void {
|
|
1057
|
-
popover.style.display = 'none';
|
|
1058
|
-
popover.style.pointerEvents = 'none';
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
/**
|
|
1062
|
-
* Join consecutive prose lines into single paragraphs.
|
|
1063
|
-
* Bullets (lines starting with -, *, •) stay as separate entries.
|
|
1064
|
-
* Blank lines create paragraph breaks.
|
|
1065
|
-
*/
|
|
1066
|
-
function joinDescriptionParagraphs(lines: readonly string[]): string[] {
|
|
1067
|
-
const result: string[] = [];
|
|
1068
|
-
let currentPara = '';
|
|
1069
|
-
|
|
1070
|
-
for (const line of lines) {
|
|
1071
|
-
const trimmed = line.trim();
|
|
1072
|
-
const isBullet = /^[-*•]\s+/.test(trimmed);
|
|
1073
|
-
|
|
1074
|
-
if (isBullet) {
|
|
1075
|
-
// Flush any accumulated paragraph
|
|
1076
|
-
if (currentPara) {
|
|
1077
|
-
result.push(currentPara);
|
|
1078
|
-
currentPara = '';
|
|
1079
|
-
}
|
|
1080
|
-
result.push(trimmed);
|
|
1081
|
-
} else if (!trimmed) {
|
|
1082
|
-
// Blank line — paragraph break
|
|
1083
|
-
if (currentPara) {
|
|
1084
|
-
result.push(currentPara);
|
|
1085
|
-
currentPara = '';
|
|
1086
|
-
}
|
|
1087
|
-
} else {
|
|
1088
|
-
// Prose line — join with previous
|
|
1089
|
-
currentPara = currentPara ? `${currentPara} ${trimmed}` : trimmed;
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
if (currentPara) result.push(currentPara);
|
|
1094
|
-
return result;
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
function renderDescriptionLine(line: string, palette: PaletteColors): string {
|
|
1098
|
-
const trimmed = line.trim();
|
|
1099
|
-
const isBullet = /^[-*•]\s+/.test(trimmed);
|
|
1100
|
-
const content = isBullet ? trimmed.replace(/^[-*•]\s+/, '') : trimmed;
|
|
1101
|
-
|
|
1102
|
-
const spans = parseInlineMarkdown(content);
|
|
1103
|
-
let spanHtml = '';
|
|
1104
|
-
for (const span of spans) {
|
|
1105
|
-
let text = escapeHtml(span.text);
|
|
1106
|
-
if (span.bold) text = `<strong>${text}</strong>`;
|
|
1107
|
-
if (span.italic) text = `<em>${text}</em>`;
|
|
1108
|
-
if (span.code)
|
|
1109
|
-
text = `<code style="background:${palette.surface}; padding: 1px 4px; border-radius: 3px; font-size: 10px;">${text}</code>`;
|
|
1110
|
-
if (span.href) {
|
|
1111
|
-
const safe = safeHref(span.href);
|
|
1112
|
-
if (safe !== null) {
|
|
1113
|
-
text = `<a href="${escapeHtml(safe)}" target="_blank" rel="noopener noreferrer" style="color: ${palette.primary ?? palette.text}; text-decoration: underline;">${text}</a>`;
|
|
1114
|
-
}
|
|
1115
|
-
// else: drop the anchor, render plain text.
|
|
1116
|
-
}
|
|
1117
|
-
spanHtml += text;
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
if (isBullet) {
|
|
1121
|
-
return `<div style="padding-left: 12px; text-indent: -10px; margin: 1px 0;">• ${spanHtml}</div>`;
|
|
1122
|
-
}
|
|
1123
|
-
return `<div style="margin: 2px 0;">${spanHtml}</div>`;
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
function escapeHtml(text: string): string {
|
|
1127
|
-
return text
|
|
1128
|
-
.replace(/&/g, '&')
|
|
1129
|
-
.replace(/</g, '<')
|
|
1130
|
-
.replace(/>/g, '>')
|
|
1131
|
-
.replace(/"/g, '"')
|
|
1132
|
-
.replace(/'/g, ''');
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
// ============================================================
|
|
1136
|
-
// Quadrant Label Positioning
|
|
1137
|
-
// ============================================================
|
|
1138
|
-
|
|
1139
|
-
/**
|
|
1140
|
-
* Render a quadrant label, wrapping to multiple lines if needed and
|
|
1141
|
-
* scaling font down if the text is too wide for the available space.
|
|
1142
|
-
*/
|
|
1143
|
-
function renderQuadrantLabel(
|
|
1144
|
-
g: d3Selection.Selection<SVGGElement, unknown, null, undefined>,
|
|
1145
|
-
name: string,
|
|
1146
|
-
x: number,
|
|
1147
|
-
y: number,
|
|
1148
|
-
anchor: string,
|
|
1149
|
-
color: string,
|
|
1150
|
-
maxWidth: number,
|
|
1151
|
-
baseFontSize = QUADRANT_LABEL_FONT_SIZE
|
|
1152
|
-
): void {
|
|
1153
|
-
// Greedy word-wrap to the available pixel width.
|
|
1154
|
-
const lines = wrapTextToWidth(name, baseFontSize, maxWidth);
|
|
1155
|
-
|
|
1156
|
-
// Scale font down if any line is still too wide
|
|
1157
|
-
const estimatedWidth = Math.max(
|
|
1158
|
-
...lines.map((l) => measureText(l, baseFontSize))
|
|
1159
|
-
);
|
|
1160
|
-
const fontSize =
|
|
1161
|
-
estimatedWidth > maxWidth
|
|
1162
|
-
? Math.max(12, baseFontSize * (maxWidth / estimatedWidth))
|
|
1163
|
-
: baseFontSize;
|
|
1164
|
-
|
|
1165
|
-
const lineHeight = fontSize * 1.2;
|
|
1166
|
-
|
|
1167
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1168
|
-
g.append('text')
|
|
1169
|
-
.attr('x', x)
|
|
1170
|
-
.attr('y', y + i * lineHeight)
|
|
1171
|
-
.attr('text-anchor', anchor)
|
|
1172
|
-
.attr('fill', color)
|
|
1173
|
-
.attr('font-family', FONT_FAMILY)
|
|
1174
|
-
.attr('font-size', fontSize)
|
|
1175
|
-
.attr('font-weight', 'bold')
|
|
1176
|
-
// In-bounds by loop guard (i < lines.length).
|
|
1177
|
-
.text(lines[i]!);
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
function getQuadrantLabelPosition(
|
|
1182
|
-
position: QuadrantPosition,
|
|
1183
|
-
cx: number,
|
|
1184
|
-
cy: number,
|
|
1185
|
-
maxRadius: number
|
|
1186
|
-
): { x: number; y: number; anchor: string } {
|
|
1187
|
-
const margin = 8;
|
|
1188
|
-
switch (position) {
|
|
1189
|
-
case 'top-left':
|
|
1190
|
-
return {
|
|
1191
|
-
x: cx - maxRadius + margin,
|
|
1192
|
-
y: cy - maxRadius + 16,
|
|
1193
|
-
anchor: 'start',
|
|
1194
|
-
};
|
|
1195
|
-
case 'top-right':
|
|
1196
|
-
return {
|
|
1197
|
-
x: cx + maxRadius - margin,
|
|
1198
|
-
y: cy - maxRadius + 16,
|
|
1199
|
-
anchor: 'end',
|
|
1200
|
-
};
|
|
1201
|
-
case 'bottom-left':
|
|
1202
|
-
return {
|
|
1203
|
-
x: cx - maxRadius + margin,
|
|
1204
|
-
y: cy + maxRadius - 8,
|
|
1205
|
-
anchor: 'start',
|
|
1206
|
-
};
|
|
1207
|
-
case 'bottom-right':
|
|
1208
|
-
return {
|
|
1209
|
-
x: cx + maxRadius - margin,
|
|
1210
|
-
y: cy + maxRadius - 8,
|
|
1211
|
-
anchor: 'end',
|
|
1212
|
-
};
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
// ============================================================
|
|
1217
|
-
// Export Renderer (for static SVG/PNG export)
|
|
1218
|
-
// ============================================================
|
|
1219
|
-
|
|
1220
|
-
export function renderTechRadarForExport(
|
|
1221
|
-
container: HTMLDivElement,
|
|
1222
|
-
parsed: ParsedTechRadar,
|
|
1223
|
-
palette: PaletteColors,
|
|
1224
|
-
isDark: boolean,
|
|
1225
|
-
exportDims?: D3ExportDimensions,
|
|
1226
|
-
viewState?: CompactViewState,
|
|
1227
|
-
exportMode?: boolean
|
|
1228
|
-
): void {
|
|
1229
|
-
renderTechRadar(
|
|
1230
|
-
container,
|
|
1231
|
-
parsed,
|
|
1232
|
-
palette,
|
|
1233
|
-
isDark,
|
|
1234
|
-
undefined,
|
|
1235
|
-
exportDims,
|
|
1236
|
-
viewState,
|
|
1237
|
-
{ ...(exportMode !== undefined && { exportMode }) }
|
|
1238
|
-
);
|
|
1239
|
-
}
|