@diagrammo/dgmo 0.46.0 → 0.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (534) hide show
  1. package/dist/advanced.d.ts +3 -855
  2. package/dist/advanced.js +541 -70107
  3. package/dist/analyzer-UYKBXAKR.js +15 -0
  4. package/dist/auto.js +1 -1
  5. package/dist/auto.mjs +1 -1
  6. package/dist/block.js +83 -69417
  7. package/dist/calculator-LDYQCDDY.js +11 -0
  8. package/dist/charts-d3-QOZHKTCT.js +25 -0
  9. package/dist/chunk-2DXMZRWJ.js +800 -0
  10. package/dist/chunk-2LACINWN.js +786 -0
  11. package/dist/chunk-33LCUH4H.js +893 -0
  12. package/dist/chunk-3CIOJ5YJ.js +35 -0
  13. package/dist/chunk-3HDBAXF5.js +86 -0
  14. package/dist/chunk-3JLKYGBQ.js +290 -0
  15. package/dist/chunk-3MQQNXPD.js +415 -0
  16. package/dist/chunk-3R5POCTZ.js +967 -0
  17. package/dist/chunk-3SMGIJDD.js +112 -0
  18. package/dist/chunk-44CM72SQ.js +409 -0
  19. package/dist/chunk-4GRI7MEB.js +1604 -0
  20. package/dist/chunk-4OHACBGX.js +606 -0
  21. package/dist/chunk-5DH3KYFH.js +21 -0
  22. package/dist/chunk-5MLYVTYM.js +76 -0
  23. package/dist/chunk-5QXL4NYS.js +553 -0
  24. package/dist/chunk-5WRI5ZAA.js +31 -0
  25. package/dist/chunk-6EK55ART.js +117 -0
  26. package/dist/chunk-6FZYCKXB.js +2065 -0
  27. package/dist/chunk-6HO5ZTXK.js +277 -0
  28. package/dist/chunk-6KFWC4CO.js +7 -0
  29. package/dist/chunk-6MTWIQVO.js +121 -0
  30. package/dist/chunk-6NYWYNBI.js +329 -0
  31. package/dist/chunk-6SM5NJQH.js +620 -0
  32. package/dist/chunk-725LDASD.js +422 -0
  33. package/dist/chunk-76OWZ3BA.js +130 -0
  34. package/{src/block/parser.ts → dist/chunk-7JEGTRLD.js} +100 -189
  35. package/dist/chunk-A44USUV7.js +923 -0
  36. package/dist/chunk-A4GOY3PE.js +25 -0
  37. package/dist/chunk-A4VFQH5G.js +251 -0
  38. package/dist/chunk-AELOBFW5.js +375 -0
  39. package/dist/chunk-AN2MOO5T.js +522 -0
  40. package/dist/chunk-BMR4CIEO.js +41 -0
  41. package/dist/chunk-BS7SV7C3.js +51 -0
  42. package/dist/chunk-BUPDIHML.js +613 -0
  43. package/dist/chunk-BUVJFRPY.js +887 -0
  44. package/dist/chunk-C3Z4DX3V.js +112 -0
  45. package/dist/chunk-CI2G3OEB.js +1818 -0
  46. package/dist/chunk-CKNVO24D.js +54 -0
  47. package/dist/chunk-CUVHQHZ7.js +391 -0
  48. package/dist/chunk-CXK4DZH3.js +114 -0
  49. package/dist/{editor.cjs → chunk-DKEPS5LH.js} +14 -225
  50. package/dist/chunk-DU7W62N5.js +487 -0
  51. package/dist/chunk-DZAQBW35.js +751 -0
  52. package/dist/chunk-EB6FPQUP.js +316 -0
  53. package/dist/chunk-EMS2SK26.js +376 -0
  54. package/dist/chunk-ERLBPDGU.js +510 -0
  55. package/dist/chunk-EVCVDTXE.js +732 -0
  56. package/dist/chunk-FCIIHT24.js +384 -0
  57. package/dist/chunk-FFUN5D66.js +97 -0
  58. package/dist/chunk-FITOLXSX.js +714 -0
  59. package/dist/chunk-FMDSF3OV.js +68 -0
  60. package/dist/chunk-FNB3PNGF.js +410 -0
  61. package/dist/chunk-G2H74XJZ.js +2104 -0
  62. package/dist/chunk-GMR5MFHJ.js +339 -0
  63. package/dist/chunk-H6PI3KJT.js +305 -0
  64. package/dist/chunk-I46X5VUA.js +59 -0
  65. package/dist/chunk-I5IGB56C.js +255 -0
  66. package/dist/chunk-IH6ZMVQ5.js +3950 -0
  67. package/dist/chunk-IJZFN3HM.js +463 -0
  68. package/dist/chunk-IPWPWM6R.js +3079 -0
  69. package/dist/chunk-IYULMRBE.js +23 -0
  70. package/dist/chunk-IZMRON6C.js +167 -0
  71. package/dist/chunk-J5FIXQRT.js +813 -0
  72. package/dist/chunk-JED6ZDEE.js +73 -0
  73. package/dist/chunk-JEUJOX5X.js +136 -0
  74. package/dist/chunk-JM4LEPY3.js +436 -0
  75. package/dist/chunk-L3UHP6TD.js +101 -0
  76. package/dist/chunk-L66GEFB6.js +524 -0
  77. package/dist/chunk-LIOHW36Z.js +469 -0
  78. package/dist/chunk-LP7EC4MI.js +16 -0
  79. package/dist/chunk-MITI6NOL.js +548 -0
  80. package/dist/chunk-MO5YHHMQ.js +168 -0
  81. package/dist/chunk-MZSBFAPJ.js +885 -0
  82. package/dist/chunk-NNAAJVBU.js +9 -0
  83. package/dist/chunk-NOTA6TEI.js +47 -0
  84. package/dist/chunk-NURGB5Z4.js +335 -0
  85. package/dist/chunk-NX4EQS3U.js +1062 -0
  86. package/dist/chunk-NXMESY4J.js +1 -0
  87. package/dist/chunk-OJR3MQYV.js +1458 -0
  88. package/dist/chunk-OURHMODR.js +454 -0
  89. package/dist/chunk-P235SVAH.js +1057 -0
  90. package/dist/chunk-P4RH4OSP.js +1038 -0
  91. package/dist/chunk-P6T66CCZ.js +226 -0
  92. package/dist/chunk-PBAHEPLQ.js +223 -0
  93. package/dist/chunk-PBHE5UG6.js +1530 -0
  94. package/dist/chunk-PDKUANDZ.js +35 -0
  95. package/dist/chunk-PWIHZ6NJ.js +296 -0
  96. package/{src/c4/layout.ts → dist/chunk-QEBUY5NY.js} +384 -1140
  97. package/dist/chunk-QLCCJEJI.js +253 -0
  98. package/dist/chunk-QOOUC2J5.js +52 -0
  99. package/dist/chunk-QYTPPDAO.js +402 -0
  100. package/dist/chunk-R3XI2VC7.js +801 -0
  101. package/dist/chunk-RCERWOJR.js +154 -0
  102. package/dist/chunk-RJE5JTVK.js +362 -0
  103. package/dist/chunk-RTLBSQ3G.js +1155 -0
  104. package/dist/chunk-RUNNI6VE.js +448 -0
  105. package/dist/chunk-RVMRZAPB.js +354 -0
  106. package/dist/chunk-RYEGFMUO.js +955 -0
  107. package/dist/chunk-RZCF2VVM.js +528 -0
  108. package/dist/chunk-SML67VPV.js +1025 -0
  109. package/dist/chunk-SQ7IY53Q.js +44 -0
  110. package/dist/chunk-SQAG3UXH.js +161 -0
  111. package/dist/chunk-SRTU6HMQ.js +1851 -0
  112. package/dist/chunk-T2OE3YQB.js +550 -0
  113. package/dist/chunk-T3UBLACL.js +299 -0
  114. package/dist/chunk-TLVIHFRO.js +296 -0
  115. package/dist/chunk-TYS6CSS5.js +52 -0
  116. package/dist/chunk-U6EQI3U6.js +130 -0
  117. package/dist/chunk-UJ4FJLBF.js +86 -0
  118. package/dist/chunk-ULOEATBZ.js +347 -0
  119. package/dist/chunk-UNCCZ3NU.js +127 -0
  120. package/dist/chunk-USZW2HHU.js +197 -0
  121. package/dist/chunk-UWN33VNX.js +1610 -0
  122. package/dist/chunk-UZ7EZW3B.js +40 -0
  123. package/dist/chunk-VOKGH627.js +37 -0
  124. package/dist/chunk-WBRZJOEC.js +204 -0
  125. package/dist/chunk-WCSOJFU3.js +380 -0
  126. package/dist/chunk-WCT3NYCV.js +160 -0
  127. package/dist/chunk-WVAJFAIA.js +529 -0
  128. package/dist/chunk-X5LCYCL5.js +394 -0
  129. package/dist/chunk-XAKEUMEJ.js +1256 -0
  130. package/dist/chunk-Y2ZE3MSG.js +590 -0
  131. package/dist/chunk-Y3J7VR52.js +646 -0
  132. package/dist/chunk-Y5QGVTVC.js +33 -0
  133. package/dist/chunk-YAA6IID3.js +548 -0
  134. package/dist/chunk-YGJTDXVR.js +692 -0
  135. package/{src/utils/time-ticks.ts → dist/chunk-YRYKTYMQ.js} +45 -112
  136. package/dist/chunk-YYVTBZZC.js +61 -0
  137. package/dist/chunk-Z5CGSFJB.js +647 -0
  138. package/{src/gantt/calculator.ts → dist/chunk-Z72XRXD4.js} +236 -428
  139. package/dist/chunk-ZGR7ULFJ.js +978 -0
  140. package/dist/chunk-ZJEL4H2R.js +851 -0
  141. package/dist/chunk-ZO27FGJS.js +554 -0
  142. package/dist/collapse-CLPJD2RL.js +11 -0
  143. package/dist/collapse-SLINJQW6.js +9 -0
  144. package/dist/collapse-ZWUFFCK2.js +9 -0
  145. package/dist/compute-G55RG53N.js +8 -0
  146. package/dist/dgmo-router-DZCIDUO5.js +73 -0
  147. package/dist/dimensions-J3FHIMJ6.js +22 -0
  148. package/dist/element.js +1 -1
  149. package/dist/element.mjs +1 -1
  150. package/dist/flowchart-parser-ZNSKIBVR.js +21 -0
  151. package/dist/flowchart-renderer-ZAEOL4DX.js +35 -0
  152. package/dist/index.d.ts +3 -469
  153. package/dist/index.js +137 -69685
  154. package/dist/layout-46N4JJYK.js +17 -0
  155. package/dist/layout-5RA6QWR6.js +26 -0
  156. package/dist/layout-5T6RH5M6.js +14 -0
  157. package/dist/layout-7QXCNOVS.js +13 -0
  158. package/dist/layout-EE6GE46Y.js +17 -0
  159. package/dist/layout-G23F7LDH.js +9 -0
  160. package/dist/layout-HRBZPFCN.js +23 -0
  161. package/dist/layout-HWDSRX5E.js +20 -0
  162. package/dist/layout-HYZPWAK5.js +18 -0
  163. package/dist/layout-N76NR4ZO.js +19 -0
  164. package/dist/layout-OB3RI63S.js +14 -0
  165. package/dist/layout-S2QBYSK2.js +15 -0
  166. package/dist/layout-UM5F4DJB.js +15 -0
  167. package/dist/layout-search-OYV77HHS.js +1655 -0
  168. package/dist/load-data-EHDDZ5ZS.js +8 -0
  169. package/dist/palettes-PRJ2RI6D.js +56 -0
  170. package/dist/parser-2MFP2H2E.js +11 -0
  171. package/dist/parser-3STE62GB.js +21 -0
  172. package/dist/parser-4FL2CMDG.js +18 -0
  173. package/dist/parser-4OGOBJJ4.js +13 -0
  174. package/dist/parser-6BTRLKRZ.js +15 -0
  175. package/dist/parser-AQETEVRW.js +21 -0
  176. package/dist/parser-CFZD5IBL.js +12 -0
  177. package/dist/parser-FUSKFGZN.js +16 -0
  178. package/dist/parser-I6KXEYFK.js +13 -0
  179. package/dist/parser-IXSZZQHY.js +18 -0
  180. package/dist/parser-JAL2HD4I.js +13 -0
  181. package/dist/parser-KNSQSKQO.js +20 -0
  182. package/dist/parser-N5O4IHUN.js +13 -0
  183. package/dist/parser-NGU3ETO6.js +16 -0
  184. package/dist/parser-NRAI6O25.js +19 -0
  185. package/dist/parser-O5J4VBII.js +11 -0
  186. package/dist/parser-OV7NOBW6.js +21 -0
  187. package/dist/parser-PQ3POX45.js +23 -0
  188. package/dist/parser-TFZFBVWN.js +11 -0
  189. package/dist/parser-TRTHDFPP.js +18 -0
  190. package/dist/parser-VQP2UJ6W.js +11 -0
  191. package/dist/parser-XDVELHRK.js +13 -0
  192. package/dist/parser-XKRP7QKP.js +11 -0
  193. package/dist/parser-Y6WTBU76.js +14 -0
  194. package/dist/parser-ZSDPZYQP.js +12 -0
  195. package/dist/renderer-2XQQQ6QR.js +28 -0
  196. package/dist/renderer-5NDVNDAZ.js +26 -0
  197. package/dist/renderer-6EEZHMH4.js +26 -0
  198. package/dist/renderer-6W5462RB.js +43 -0
  199. package/dist/renderer-7MVLWWQD.js +22 -0
  200. package/dist/renderer-AWWAF6XD.js +33 -0
  201. package/dist/renderer-BYCNMBF7.js +34 -0
  202. package/dist/renderer-CLJHQPBJ.js +29 -0
  203. package/dist/renderer-GHPZMFU2.js +15 -0
  204. package/dist/renderer-JGBKUCZD.js +16 -0
  205. package/dist/renderer-KQB7UGDS.js +19 -0
  206. package/dist/renderer-PGX2TKVS.js +24 -0
  207. package/dist/renderer-QIWVHWZO.js +15 -0
  208. package/dist/renderer-RPOA5YPZ.js +35 -0
  209. package/dist/renderer-SMIBCRVB.js +24 -0
  210. package/dist/renderer-T7YKC5VP.js +28 -0
  211. package/dist/renderer-TOCOVLHS.js +37 -0
  212. package/dist/renderer-UWA5XS74.js +39 -0
  213. package/dist/renderer-VACVQYML.js +25 -0
  214. package/dist/renderer-W4M6QLIJ.js +37 -0
  215. package/dist/renderer-WZRREQ2P.js +23 -0
  216. package/dist/renderer-YY2OSF6E.js +27 -0
  217. package/dist/renderer-Z7I3W2PP.js +24 -0
  218. package/dist/renderer-ZGNTUWC4.js +22 -0
  219. package/dist/renderer-ZTR3DER3.js +19 -0
  220. package/dist/renderer-radial-C32GPJQ7.js +21 -0
  221. package/dist/resolver-NOAA7ID7.js +13 -0
  222. package/dist/state-collapse-HCGD6IVJ.js +8 -0
  223. package/dist/state-parser-Z2V23ATR.js +18 -0
  224. package/dist/state-renderer-RFYHSP4K.js +34 -0
  225. package/dist/themes-1-CuKpeH.d.ts +908 -0
  226. package/package.json +19 -70
  227. package/dist/advanced.cjs +0 -71648
  228. package/dist/advanced.d.cts +0 -6571
  229. package/dist/auto.cjs +0 -70648
  230. package/dist/auto.d.cts +0 -54
  231. package/dist/block.cjs +0 -70208
  232. package/dist/block.d.cts +0 -115
  233. package/dist/editor.d.cts +0 -26
  234. package/dist/element.cjs +0 -70530
  235. package/dist/element.d.cts +0 -62
  236. package/dist/highlight.cjs +0 -852
  237. package/dist/highlight.d.cts +0 -49
  238. package/dist/index.cjs +0 -70491
  239. package/dist/index.d.cts +0 -626
  240. package/dist/pert.cjs +0 -325
  241. package/dist/pert.d.cts +0 -559
  242. package/src/advanced.ts +0 -873
  243. package/src/arc/parser.ts +0 -15
  244. package/src/arc/renderer.ts +0 -633
  245. package/src/auto/index.ts +0 -621
  246. package/src/auto/shared.ts +0 -370
  247. package/src/auto/styles.ts +0 -73
  248. package/src/block/layout.ts +0 -198
  249. package/src/block/renderer.ts +0 -484
  250. package/src/block/types.ts +0 -67
  251. package/src/boxes-and-lines/collapse.ts +0 -96
  252. package/src/boxes-and-lines/focus.ts +0 -257
  253. package/src/boxes-and-lines/label-placement.ts +0 -339
  254. package/src/boxes-and-lines/layout-grouped.ts +0 -912
  255. package/src/boxes-and-lines/layout-layered.ts +0 -722
  256. package/src/boxes-and-lines/layout-search.ts +0 -1678
  257. package/src/boxes-and-lines/layout.ts +0 -398
  258. package/src/boxes-and-lines/parser.ts +0 -1181
  259. package/src/boxes-and-lines/renderer.ts +0 -1465
  260. package/src/boxes-and-lines/types.ts +0 -66
  261. package/src/c4/parser.ts +0 -1158
  262. package/src/c4/renderer.ts +0 -2008
  263. package/src/c4/types.ts +0 -89
  264. package/src/chart-type-registry.ts +0 -529
  265. package/src/chart-types.ts +0 -225
  266. package/src/chart.ts +0 -685
  267. package/src/charts-d3/bar.ts +0 -338
  268. package/src/charts-d3/chord.ts +0 -140
  269. package/src/charts-d3/function.ts +0 -147
  270. package/src/charts-d3/funnel.ts +0 -104
  271. package/src/charts-d3/heatmap.ts +0 -145
  272. package/src/charts-d3/index.ts +0 -337
  273. package/src/charts-d3/interactions.ts +0 -477
  274. package/src/charts-d3/line.ts +0 -344
  275. package/src/charts-d3/mount.ts +0 -151
  276. package/src/charts-d3/pie.ts +0 -117
  277. package/src/charts-d3/polar.ts +0 -89
  278. package/src/charts-d3/radar.ts +0 -0
  279. package/src/charts-d3/sankey.ts +0 -0
  280. package/src/charts-d3/scatter.ts +0 -185
  281. package/src/charts-d3/shared.ts +0 -208
  282. package/src/class/layout.ts +0 -326
  283. package/src/class/parser.ts +0 -635
  284. package/src/class/renderer.ts +0 -793
  285. package/src/class/types.ts +0 -58
  286. package/src/cli-banner.ts +0 -106
  287. package/src/cli.ts +0 -1080
  288. package/src/colors-diagnostics.ts +0 -35
  289. package/src/colors.ts +0 -379
  290. package/src/completion-types.ts +0 -27
  291. package/src/completion.ts +0 -2668
  292. package/src/cycle/layout.ts +0 -1026
  293. package/src/cycle/parser.ts +0 -387
  294. package/src/cycle/renderer.ts +0 -653
  295. package/src/cycle/types.ts +0 -101
  296. package/src/d3.ts +0 -1434
  297. package/src/data-chart-parser.ts +0 -1007
  298. package/src/dgmo-router.ts +0 -296
  299. package/src/diagnostics-registry.ts +0 -108
  300. package/src/diagnostics.ts +0 -0
  301. package/src/dimensions.ts +0 -20
  302. package/src/directives-registry.ts +0 -396
  303. package/src/editor/dgmo.grammar +0 -76
  304. package/src/editor/dgmo.grammar.d.ts +0 -2
  305. package/src/editor/dgmo.grammar.js +0 -18
  306. package/src/editor/dgmo.grammar.terms.d.ts +0 -5
  307. package/src/editor/dgmo.grammar.terms.js +0 -35
  308. package/src/editor/highlight-api.ts +0 -596
  309. package/src/editor/highlight.ts +0 -36
  310. package/src/editor/index.ts +0 -27
  311. package/src/editor/keywords.ts +0 -128
  312. package/src/editor/tokens.ts +0 -30
  313. package/src/element/index.ts +0 -432
  314. package/src/embed/css.ts +0 -312
  315. package/src/embed/escape.ts +0 -15
  316. package/src/embed/index.ts +0 -280
  317. package/src/er/classify.ts +0 -228
  318. package/src/er/layout.ts +0 -530
  319. package/src/er/parser.ts +0 -750
  320. package/src/er/renderer.ts +0 -725
  321. package/src/er/types.ts +0 -52
  322. package/src/error-card.ts +0 -269
  323. package/src/event-line/diagnostics.ts +0 -117
  324. package/src/event-line/parser.ts +0 -643
  325. package/src/event-line/renderer.ts +0 -1742
  326. package/src/event-line/types.ts +0 -74
  327. package/src/fonts.ts +0 -3
  328. package/src/gantt/parser.ts +0 -1641
  329. package/src/gantt/renderer.ts +0 -3787
  330. package/src/gantt/resolver.ts +0 -172
  331. package/src/gantt/types.ts +0 -206
  332. package/src/graph/diagnostics.ts +0 -64
  333. package/src/graph/edge-spline.ts +0 -29
  334. package/src/graph/flowchart-parser.ts +0 -759
  335. package/src/graph/flowchart-renderer.ts +0 -817
  336. package/src/graph/layout.ts +0 -472
  337. package/src/graph/notes.ts +0 -21
  338. package/src/graph/state-collapse.ts +0 -78
  339. package/src/graph/state-parser.ts +0 -574
  340. package/src/graph/state-renderer.ts +0 -653
  341. package/src/graph/types.ts +0 -65
  342. package/src/index.ts +0 -261
  343. package/src/infra/compute.ts +0 -1385
  344. package/src/infra/layout.ts +0 -888
  345. package/src/infra/parser.ts +0 -1234
  346. package/src/infra/renderer.ts +0 -2534
  347. package/src/infra/roles.ts +0 -75
  348. package/src/infra/types.ts +0 -216
  349. package/src/infra/validation.ts +0 -201
  350. package/src/journey-map/layout.ts +0 -490
  351. package/src/journey-map/parser.ts +0 -557
  352. package/src/journey-map/renderer.ts +0 -1580
  353. package/src/journey-map/types.ts +0 -47
  354. package/src/kanban/mutations.ts +0 -175
  355. package/src/kanban/parser.ts +0 -509
  356. package/src/kanban/renderer.ts +0 -1449
  357. package/src/kanban/types.ts +0 -39
  358. package/src/label-layout.ts +0 -368
  359. package/src/map/colorize.ts +0 -54
  360. package/src/map/completion.ts +0 -316
  361. package/src/map/context-labels.ts +0 -609
  362. package/src/map/data/PROVENANCE.json +0 -1
  363. package/src/map/data/README.md +0 -40
  364. package/src/map/data/airport-collisions.json +0 -1
  365. package/src/map/data/airports.json +0 -1
  366. package/src/map/data/gazetteer.json +0 -1
  367. package/src/map/data/lakes.json +0 -1
  368. package/src/map/data/mountain-ranges.json +0 -1
  369. package/src/map/data/na-lakes.json +0 -1
  370. package/src/map/data/na-land.json +0 -1
  371. package/src/map/data/region-names.json +0 -1
  372. package/src/map/data/rivers.json +0 -1
  373. package/src/map/data/types.ts +0 -121
  374. package/src/map/data/us-states.json +0 -1
  375. package/src/map/data/water-bodies.json +0 -1
  376. package/src/map/data/world-coarse.json +0 -1
  377. package/src/map/data/world-detail.json +0 -1
  378. package/src/map/diagnostics.ts +0 -134
  379. package/src/map/dimensions.ts +0 -150
  380. package/src/map/geo-query.ts +0 -295
  381. package/src/map/geo.ts +0 -438
  382. package/src/map/invert.ts +0 -111
  383. package/src/map/layout.ts +0 -5069
  384. package/src/map/legend-band.ts +0 -127
  385. package/src/map/load-data.ts +0 -186
  386. package/src/map/parser.ts +0 -783
  387. package/src/map/renderer.ts +0 -1445
  388. package/src/map/resolved-types.ts +0 -160
  389. package/src/map/resolver.ts +0 -1146
  390. package/src/map/types.ts +0 -194
  391. package/src/mindmap/collapse.ts +0 -67
  392. package/src/mindmap/layout.ts +0 -649
  393. package/src/mindmap/parser.ts +0 -430
  394. package/src/mindmap/renderer.ts +0 -637
  395. package/src/mindmap/text-wrap.ts +0 -218
  396. package/src/mindmap/types.ts +0 -56
  397. package/src/org/collapse.ts +0 -151
  398. package/src/org/layout.ts +0 -1262
  399. package/src/org/parser.ts +0 -519
  400. package/src/org/renderer.ts +0 -866
  401. package/src/org/resolver.ts +0 -636
  402. package/src/palettes/atlas.ts +0 -77
  403. package/src/palettes/blueprint.ts +0 -73
  404. package/src/palettes/catppuccin.ts +0 -84
  405. package/src/palettes/color-utils.ts +0 -443
  406. package/src/palettes/index.ts +0 -67
  407. package/src/palettes/nord.ts +0 -67
  408. package/src/palettes/registry.ts +0 -114
  409. package/src/palettes/slate.ts +0 -73
  410. package/src/palettes/tidewater.ts +0 -73
  411. package/src/palettes/tokyo-night.ts +0 -86
  412. package/src/palettes/types.ts +0 -88
  413. package/src/pert/analyzer.ts +0 -1183
  414. package/src/pert/diagnostics.ts +0 -110
  415. package/src/pert/index.ts +0 -43
  416. package/src/pert/internal.ts +0 -252
  417. package/src/pert/layout.ts +0 -971
  418. package/src/pert/monte-carlo.ts +0 -446
  419. package/src/pert/parser.ts +0 -1606
  420. package/src/pert/renderer.ts +0 -4009
  421. package/src/pert/share-normalize.ts +0 -62
  422. package/src/pert/types.ts +0 -604
  423. package/src/pyramid/parser.ts +0 -185
  424. package/src/pyramid/renderer.ts +0 -768
  425. package/src/pyramid/types.ts +0 -28
  426. package/src/quadrant/parser.ts +0 -18
  427. package/src/quadrant/renderer.ts +0 -638
  428. package/src/raci/diagnostics.ts +0 -213
  429. package/src/raci/index.ts +0 -23
  430. package/src/raci/mutations.ts +0 -321
  431. package/src/raci/parser.ts +0 -699
  432. package/src/raci/renderer.ts +0 -1627
  433. package/src/raci/types.ts +0 -82
  434. package/src/raci/variants.ts +0 -320
  435. package/src/render.ts +0 -268
  436. package/src/ring/parser.ts +0 -212
  437. package/src/ring/renderer.ts +0 -490
  438. package/src/ring/types.ts +0 -27
  439. package/src/sequence/collapse.ts +0 -174
  440. package/src/sequence/parser.ts +0 -1596
  441. package/src/sequence/participant-inference.ts +0 -152
  442. package/src/sequence/renderer.ts +0 -3168
  443. package/src/sequence/tag-resolution.ts +0 -164
  444. package/src/sharing.ts +0 -211
  445. package/src/sitemap/collapse.ts +0 -100
  446. package/src/sitemap/layout.ts +0 -925
  447. package/src/sitemap/parser.ts +0 -755
  448. package/src/sitemap/renderer.ts +0 -828
  449. package/src/sitemap/types.ts +0 -42
  450. package/src/slope/parser.ts +0 -18
  451. package/src/slope/renderer.ts +0 -378
  452. package/src/swimlane/diagnostics.ts +0 -94
  453. package/src/swimlane/layout.ts +0 -597
  454. package/src/swimlane/parser.ts +0 -613
  455. package/src/swimlane/renderer.ts +0 -681
  456. package/src/swimlane/types.ts +0 -151
  457. package/src/tech-radar/interactive.ts +0 -1129
  458. package/src/tech-radar/layout.ts +0 -197
  459. package/src/tech-radar/parser.ts +0 -436
  460. package/src/tech-radar/renderer.ts +0 -1239
  461. package/src/tech-radar/shared.ts +0 -151
  462. package/src/tech-radar/types.ts +0 -86
  463. package/src/themes.ts +0 -22
  464. package/src/timeline/parser.ts +0 -472
  465. package/src/timeline/renderer.ts +0 -3024
  466. package/src/timeline/types.ts +0 -33
  467. package/src/timeline/viz-parser.ts +0 -21
  468. package/src/treemap/diagnostics.ts +0 -62
  469. package/src/treemap/layout-radial.ts +0 -233
  470. package/src/treemap/layout.ts +0 -189
  471. package/src/treemap/parser.ts +0 -463
  472. package/src/treemap/renderer-radial.ts +0 -438
  473. package/src/treemap/renderer.ts +0 -572
  474. package/src/treemap/treemap-shared.ts +0 -309
  475. package/src/treemap/types.ts +0 -60
  476. package/src/utils/arrow-markers.ts +0 -51
  477. package/src/utils/arrows-diagnostics.ts +0 -62
  478. package/src/utils/arrows.ts +0 -227
  479. package/src/utils/brand.ts +0 -61
  480. package/src/utils/card.ts +0 -183
  481. package/src/utils/collapse-engine/tree-edges.ts +0 -115
  482. package/src/utils/collapse-engine/tree.ts +0 -117
  483. package/src/utils/d3-helpers.ts +0 -219
  484. package/src/utils/d3-types.ts +0 -9
  485. package/src/utils/description-helpers.ts +0 -38
  486. package/src/utils/duration.ts +0 -312
  487. package/src/utils/export-container.ts +0 -41
  488. package/src/utils/expr-eval.ts +0 -27
  489. package/src/utils/fit-canvas.ts +0 -64
  490. package/src/utils/hover-styles.ts +0 -673
  491. package/src/utils/inline-markdown.ts +0 -96
  492. package/src/utils/legend-constants.ts +0 -48
  493. package/src/utils/legend-d3.ts +0 -618
  494. package/src/utils/legend-integration.ts +0 -107
  495. package/src/utils/legend-layout.ts +0 -776
  496. package/src/utils/legend-svg.ts +0 -214
  497. package/src/utils/legend-types.ts +0 -272
  498. package/src/utils/name-normalize.ts +0 -163
  499. package/src/utils/note-box/constants.ts +0 -25
  500. package/src/utils/note-box/index.ts +0 -11
  501. package/src/utils/note-box/metrics.ts +0 -90
  502. package/src/utils/note-box/svg.ts +0 -331
  503. package/src/utils/notes/bounds.ts +0 -30
  504. package/src/utils/notes/build.ts +0 -131
  505. package/src/utils/notes/index.ts +0 -18
  506. package/src/utils/notes/model.ts +0 -19
  507. package/src/utils/notes/parse.ts +0 -131
  508. package/src/utils/notes/place.ts +0 -177
  509. package/src/utils/notes/resolve.ts +0 -88
  510. package/src/utils/number-format.ts +0 -36
  511. package/src/utils/parsing.ts +0 -953
  512. package/src/utils/reserved-key-registry.ts +0 -239
  513. package/src/utils/safe-href.ts +0 -38
  514. package/src/utils/scaling.ts +0 -97
  515. package/src/utils/svg-embed.ts +0 -251
  516. package/src/utils/tag-groups.ts +0 -607
  517. package/src/utils/text-measure.ts +0 -122
  518. package/src/utils/title-constants.ts +0 -24
  519. package/src/utils/visual-conventions.ts +0 -61
  520. package/src/utils/wrapped-desc.ts +0 -87
  521. package/src/venn/parser.ts +0 -18
  522. package/src/venn/renderer.ts +0 -1047
  523. package/src/version-control/diagnostics.ts +0 -67
  524. package/src/version-control/parser.ts +0 -544
  525. package/src/version-control/renderer.ts +0 -724
  526. package/src/version-control/types.ts +0 -100
  527. package/src/visualizations/parse.ts +0 -1560
  528. package/src/visualizations/types.ts +0 -262
  529. package/src/wireframe/layout.ts +0 -479
  530. package/src/wireframe/parser.ts +0 -1114
  531. package/src/wireframe/renderer.ts +0 -1318
  532. package/src/wireframe/types.ts +0 -110
  533. package/src/wordcloud/parser.ts +0 -18
  534. package/src/wordcloud/renderer.ts +0 -357
@@ -0,0 +1,3079 @@
1
+ import {
2
+ buildAdjacency,
3
+ featureBboxPrimary,
4
+ pointInGeometry
5
+ } from "./chunk-I5IGB56C.js";
6
+ import {
7
+ rectCircleOverlap,
8
+ rectsOverlap,
9
+ segmentRectOverlap,
10
+ segmentsCross
11
+ } from "./chunk-PBAHEPLQ.js";
12
+ import {
13
+ compactNumber,
14
+ computeLegendLayout
15
+ } from "./chunk-YAA6IID3.js";
16
+ import {
17
+ TITLE_FONT_SIZE,
18
+ TITLE_Y
19
+ } from "./chunk-A4GOY3PE.js";
20
+ import {
21
+ measureLegendText
22
+ } from "./chunk-QOOUC2J5.js";
23
+ import {
24
+ contrastRatio,
25
+ mix,
26
+ politicalTints,
27
+ relativeLuminance,
28
+ valueRampColor
29
+ } from "./chunk-P6T66CCZ.js";
30
+ import {
31
+ tagAttrKey
32
+ } from "./chunk-QLCCJEJI.js";
33
+ import {
34
+ resolveColor
35
+ } from "./chunk-TLVIHFRO.js";
36
+
37
+ // src/map/legend-band.ts
38
+ var LEGEND_TOP_GAP = 8;
39
+ var LEGEND_BOTTOM_GAP = 10;
40
+ function mapLegendGroups(legend) {
41
+ const ramp = legend.ramp;
42
+ const scoreGroup = ramp ? {
43
+ name: ramp.metric?.trim() || "Value",
44
+ entries: [],
45
+ gradient: {
46
+ min: ramp.min,
47
+ max: ramp.max,
48
+ low: ramp.low,
49
+ high: ramp.high
50
+ }
51
+ } : null;
52
+ const tagGroups = legend.tagGroups.filter((g) => g.entries.length > 0).map((g) => ({ name: g.name, entries: [...g.entries] }));
53
+ return [...scoreGroup ? [scoreGroup] : [], ...tagGroups];
54
+ }
55
+ function mapLegendConfig(groups, mode) {
56
+ return {
57
+ groups,
58
+ position: { placement: "top-center", titleRelation: "below-title" },
59
+ mode,
60
+ showEmptyGroups: false,
61
+ showInactivePills: true
62
+ };
63
+ }
64
+ function mapLegendTop(hasTitle, hasSubtitle) {
65
+ return (hasTitle ? TITLE_Y + TITLE_FONT_SIZE : 0) + (hasSubtitle ? TITLE_FONT_SIZE : 0) + LEGEND_TOP_GAP;
66
+ }
67
+ function mapLegendBand(legend, opts) {
68
+ if (!legend) return 0;
69
+ const groups = mapLegendGroups(legend);
70
+ if (groups.length === 0) return 0;
71
+ const config = mapLegendConfig(groups, opts.mode);
72
+ const state = { activeGroup: legend.activeGroup };
73
+ const { height } = computeLegendLayout(config, state, opts.width);
74
+ if (height <= 0) return 0;
75
+ return mapLegendTop(opts.hasTitle, opts.hasSubtitle) + height + LEGEND_BOTTOM_GAP;
76
+ }
77
+ function mapLegendBox(legend, opts) {
78
+ if (!legend) return null;
79
+ const groups = mapLegendGroups(legend);
80
+ if (groups.length === 0) return null;
81
+ const config = mapLegendConfig(groups, opts.mode);
82
+ const state = { activeGroup: legend.activeGroup };
83
+ const { width, height } = computeLegendLayout(config, state, opts.width);
84
+ if (height <= 0 || width <= 0) return null;
85
+ return {
86
+ x: (opts.width - width) / 2,
87
+ y: mapLegendTop(opts.hasTitle, opts.hasSubtitle),
88
+ width,
89
+ height
90
+ };
91
+ }
92
+
93
+ // src/map/layout.ts
94
+ import {
95
+ geoPath,
96
+ geoNaturalEarth1,
97
+ geoEqualEarth,
98
+ geoEquirectangular,
99
+ geoConicEqualArea,
100
+ geoMercator,
101
+ geoBounds,
102
+ geoTransform
103
+ } from "d3-geo";
104
+ import { feature } from "topojson-client";
105
+
106
+ // src/map/colorize.ts
107
+ function assignColors(isos, adjacency) {
108
+ const sorted = [...isos].sort();
109
+ const byIso = /* @__PURE__ */ new Map();
110
+ let maxIndex = -1;
111
+ for (const iso of sorted) {
112
+ const taken = /* @__PURE__ */ new Set();
113
+ for (const n of adjacency.get(iso) ?? []) {
114
+ const c = byIso.get(n);
115
+ if (c !== void 0) taken.add(c);
116
+ }
117
+ let h = 0;
118
+ while (taken.has(h)) h++;
119
+ byIso.set(iso, h);
120
+ if (h > maxIndex) maxIndex = h;
121
+ }
122
+ return { byIso, huesNeeded: maxIndex + 1 };
123
+ }
124
+
125
+ // src/map/context-labels.ts
126
+ var FONT = 11;
127
+ var LINE_HEIGHT = FONT + 2;
128
+ var PADX = 4;
129
+ var PADY = 3;
130
+ var WATER_LETTER_SPACING = 1.5;
131
+ var CONTEXT_PAD = 4;
132
+ var EDGE_CLAMP_MARGIN = 8;
133
+ var EDGE_CLAMP_OVERSHOOT = 0.35;
134
+ var COUNTRY_FONT_MAX = 22;
135
+ var COUNTRY_SIZE_FRAC_MIN = 0.06;
136
+ var COUNTRY_SIZE_FRAC_MAX = 0.32;
137
+ var MAX_COUNTRY_POSITIONS = 4;
138
+ var COUNTRY_POS_GRID = 5;
139
+ var COUNTRY_POS_TOPN_MARGIN = 3;
140
+ var KIND_ORDER = {
141
+ ocean: 0,
142
+ sea: 1,
143
+ gulf: 2,
144
+ bay: 3,
145
+ strait: 4,
146
+ channel: 5,
147
+ sound: 6
148
+ };
149
+ function tierBand(maxSpanDeg) {
150
+ if (maxSpanDeg >= 90) return "world";
151
+ if (maxSpanDeg >= 20) return "continental";
152
+ if (maxSpanDeg >= 5) return "regional";
153
+ return "local";
154
+ }
155
+ function labelBudget(width, height, band) {
156
+ const bandCap = {
157
+ world: 10,
158
+ continental: 9,
159
+ regional: 7,
160
+ local: 6
161
+ };
162
+ const area = Math.floor(Math.sqrt(Math.max(0, width * height)) / 105);
163
+ return Math.max(0, Math.min(area, bandCap[band]));
164
+ }
165
+ function waterEligible(tier, kind, band) {
166
+ switch (band) {
167
+ case "world":
168
+ return tier <= 1 && (kind === "ocean" || kind === "sea");
169
+ case "continental":
170
+ return tier <= 2;
171
+ case "regional":
172
+ return tier <= 3;
173
+ case "local":
174
+ return tier <= 4;
175
+ }
176
+ }
177
+ function insideViewport(p, width, height) {
178
+ return !!p && Number.isFinite(p[0]) && Number.isFinite(p[1]) && p[0] >= 0 && p[0] <= width && p[1] >= 0 && p[1] <= height;
179
+ }
180
+ function labelWidth(text, letterSpacing, font = FONT) {
181
+ const spacing = letterSpacing > 0 ? Math.max(0, text.length - 1) * letterSpacing : 0;
182
+ return measureLegendText(text, font) + spacing + 2 * PADX;
183
+ }
184
+ function wrapLabel(text, letterSpacing) {
185
+ const words = text.split(/\s+/).filter(Boolean);
186
+ if (words.length <= 1) return [text];
187
+ const maxLines = words.length >= 4 ? 3 : 2;
188
+ const n = words.length;
189
+ let best = null;
190
+ for (let mask = 0; mask < 1 << n - 1; mask++) {
191
+ const lines = [];
192
+ let cur = [words[0]];
193
+ for (let i = 1; i < n; i++) {
194
+ if (mask & 1 << i - 1) {
195
+ lines.push(cur.join(" "));
196
+ cur = [words[i]];
197
+ } else cur.push(words[i]);
198
+ }
199
+ lines.push(cur.join(" "));
200
+ if (lines.length > maxLines) continue;
201
+ const cost = Math.round(
202
+ Math.max(...lines.map((l) => labelWidth(l, letterSpacing)))
203
+ );
204
+ const head = labelWidth(lines[0], letterSpacing);
205
+ if (!best || cost < best.cost || cost === best.cost && lines.length < best.lines.length || cost === best.cost && lines.length === best.lines.length && head > best.head)
206
+ best = { lines, cost, head };
207
+ }
208
+ return best?.lines ?? [text];
209
+ }
210
+ function rectAround(cx, cy, lines, letterSpacing, font = FONT) {
211
+ const lineHeight = font + 2;
212
+ const w = Math.max(...lines.map((l) => labelWidth(l, letterSpacing, font)));
213
+ const h = (lines.length - 1) * lineHeight + font + 2 * PADY;
214
+ return { x: cx - w / 2, y: cy - h / 2, w, h };
215
+ }
216
+ function rectFits(r, width, height) {
217
+ return r.x >= 0 && r.y >= 0 && r.x + r.w <= width && r.y + r.h <= height;
218
+ }
219
+ function overlapsPadded(a, b, pad) {
220
+ return a.x - pad < b.x + b.w && a.x + a.w + pad > b.x && a.y - pad < b.y + b.h && a.y + a.h + pad > b.y;
221
+ }
222
+ function placeContextLabels(args) {
223
+ const {
224
+ projection,
225
+ dLonSpan,
226
+ dLatSpan,
227
+ width,
228
+ height,
229
+ waterBodies,
230
+ countries,
231
+ palette,
232
+ countryTone,
233
+ waterTone,
234
+ project,
235
+ collides,
236
+ overLand
237
+ } = args;
238
+ void projection;
239
+ const band = tierBand(Math.max(dLonSpan, dLatSpan));
240
+ const budget = labelBudget(width, height, band);
241
+ if (budget <= 0) return [];
242
+ const waterColor = waterTone ?? mix(palette.colors.blue, palette.textMuted, 50);
243
+ const countryColor = countryTone ?? palette.textMuted;
244
+ const haloColor = palette.bg;
245
+ const candidates = [];
246
+ const center = [width / 2, height / 2];
247
+ for (const e of waterBodies?.entries ?? []) {
248
+ const [lat, lon, name, tier, kind, alt] = e;
249
+ if (!waterEligible(tier, kind, band)) continue;
250
+ const wlines = wrapLabel(name, WATER_LETTER_SPACING);
251
+ const anchorsLngLat = [[lon, lat]];
252
+ for (const a of alt ?? []) anchorsLngLat.push([a[1], a[0]]);
253
+ let best = null;
254
+ let bestD = Infinity;
255
+ let nearestProj = null;
256
+ let nearestProjD = Infinity;
257
+ for (const [aLon, aLat] of anchorsLngLat) {
258
+ const p = project(aLon, aLat);
259
+ if (!p || !Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;
260
+ const d = (p[0] - center[0]) ** 2 + (p[1] - center[1]) ** 2;
261
+ if (d < nearestProjD) {
262
+ nearestProjD = d;
263
+ nearestProj = p;
264
+ }
265
+ if (!insideViewport(p, width, height)) continue;
266
+ if (d < bestD) {
267
+ bestD = d;
268
+ best = p;
269
+ }
270
+ }
271
+ if (!best && tier === 0 && nearestProj) {
272
+ const overX = Math.max(0, -nearestProj[0], nearestProj[0] - width);
273
+ const overY = Math.max(0, -nearestProj[1], nearestProj[1] - height);
274
+ if (overX <= width * EDGE_CLAMP_OVERSHOOT && overY <= height * EDGE_CLAMP_OVERSHOOT) {
275
+ const halfW = Math.max(...wlines.map((l) => labelWidth(l, WATER_LETTER_SPACING))) / 2;
276
+ const halfH = ((wlines.length - 1) * LINE_HEIGHT + FONT + 2 * PADY) / 2;
277
+ const m = EDGE_CLAMP_MARGIN;
278
+ best = [
279
+ Math.min(Math.max(nearestProj[0], halfW + m), width - halfW - m),
280
+ Math.min(Math.max(nearestProj[1], halfH + m), height - halfH - m)
281
+ ];
282
+ }
283
+ }
284
+ if (!best) continue;
285
+ candidates.push({
286
+ text: name,
287
+ lines: wlines,
288
+ cx: best[0],
289
+ cy: best[1],
290
+ italic: true,
291
+ letterSpacing: WATER_LETTER_SPACING,
292
+ color: waterColor,
293
+ fontSize: FONT,
294
+ // water names keep the base font (no footprint to scale on)
295
+ // Orientation-value bands (lower = earlier): MAJOR water (oceans + major
296
+ // seas, tier ≤ 1) leads at `tier*10+kind` (0..~16); MINOR water (tier ≥ 2:
297
+ // smaller seas, bays, gulfs, straits) drops to band 2 (2000+) — BELOW the
298
+ // country band (1000+), so a big country outranks a minor basin.
299
+ sort: (tier <= 1 ? 0 : 2e3) + tier * 10 + KIND_ORDER[kind]
300
+ });
301
+ }
302
+ const ranked = countries.map((c) => {
303
+ const [x0, y0, x1, y1] = c.bbox;
304
+ const w = x1 - x0;
305
+ const h = y1 - y0;
306
+ return { c, w, h, area: w * h };
307
+ }).filter((r) => Number.isFinite(r.area) && r.area > 0).sort((a, b) => b.area - a.area);
308
+ const canvasLinear = Math.sqrt(Math.max(1, width * height));
309
+ let ci = 0;
310
+ for (const r of ranked) {
311
+ const { c, w, h, area } = r;
312
+ if (!c.curatedAnchor && w > width * 0.66 && h > height * 0.66) continue;
313
+ if (!insideViewport(c.anchor, width, height)) continue;
314
+ const sizeFrac = Math.sqrt(area) / canvasLinear;
315
+ const t = Math.min(
316
+ 1,
317
+ Math.max(
318
+ 0,
319
+ (sizeFrac - COUNTRY_SIZE_FRAC_MIN) / (COUNTRY_SIZE_FRAC_MAX - COUNTRY_SIZE_FRAC_MIN)
320
+ )
321
+ );
322
+ const fontSize = Math.round(FONT + t * (COUNTRY_FONT_MAX - FONT));
323
+ const color = countryColor;
324
+ const text = c.name;
325
+ const tw = labelWidth(text, 0, fontSize);
326
+ if (tw > w || fontSize + 2 * PADY > h) continue;
327
+ candidates.push({
328
+ text,
329
+ lines: [text],
330
+ cx: c.anchor[0],
331
+ cy: c.anchor[1],
332
+ italic: false,
333
+ letterSpacing: 0,
334
+ color,
335
+ fontSize,
336
+ ...c.bordering ? { bordering: true } : {},
337
+ // Multi-position dodging: carry the ordered interior positions through to the
338
+ // commit loop. Invariant anchor === positions[0], so `cx/cy` is positions[0].
339
+ ...c.positions ? { positions: c.positions } : {},
340
+ // Band 1 (orientation-value ranking): above MINOR water (band 2, 2000+) but
341
+ // below MAJOR water — oceans + major seas (band 0, ≤~16). So a big country
342
+ // (US, Canada, Russia) outranks a minor sea/bay (Sargasso, Bahía de
343
+ // Campeche) yet never displaces an ocean. Larger area = earlier within the
344
+ // band (`ci` is the area-desc rank index).
345
+ sort: 1e3 + ci++
346
+ });
347
+ }
348
+ candidates.sort((a, b) => a.sort - b.sort);
349
+ const placed = [];
350
+ const placedRects = [];
351
+ const stateCount = candidates.reduce(
352
+ (n, c) => n + (!c.italic && !c.bordering ? 1 : 0),
353
+ 0
354
+ );
355
+ const waterCap = budget - Math.min(2, stateCount);
356
+ let waterPlaced = 0;
357
+ let budgetUsed = 0;
358
+ const gateAt = (cx, cy, cand) => {
359
+ const rect = rectAround(
360
+ cx,
361
+ cy,
362
+ cand.lines,
363
+ cand.letterSpacing,
364
+ cand.fontSize
365
+ );
366
+ if (!rectFits(rect, width, height)) return null;
367
+ if (cand.italic && overLand) {
368
+ const inset = 2;
369
+ const top = cy - (cand.lines.length - 1) / 2 * LINE_HEIGHT;
370
+ const touchesLand = cand.lines.some((line, li) => {
371
+ const lw = labelWidth(line, cand.letterSpacing);
372
+ const x0 = cx - lw / 2 + inset;
373
+ const x1 = cx + lw / 2 - inset;
374
+ const xs = [x0, (x0 + cx) / 2, cx, (cx + x1) / 2, x1];
375
+ const base = top + li * LINE_HEIGHT;
376
+ return [base, base - FONT * 0.4, base - FONT * 0.8].some(
377
+ (y) => xs.some((x) => overLand(x, y))
378
+ );
379
+ });
380
+ if (touchesLand) return null;
381
+ }
382
+ if (collides(rect)) return null;
383
+ if (placedRects.some((r) => overlapsPadded(rect, r, CONTEXT_PAD)))
384
+ return null;
385
+ return rect;
386
+ };
387
+ const tryPlace = (cand) => {
388
+ const positions = cand.positions ?? [[cand.cx, cand.cy]];
389
+ for (const [px, py] of positions) {
390
+ const rect = gateAt(px, py, cand);
391
+ if (!rect) continue;
392
+ placedRects.push(rect);
393
+ placed.push({
394
+ x: px,
395
+ y: py,
396
+ text: cand.text,
397
+ anchor: "middle",
398
+ color: cand.color,
399
+ // No halo: the bg-coloured outline reads as a ghost box behind the text
400
+ // over the tinted water/land. Context labels are muted enough to sit
401
+ // cleanly on the basemap without one.
402
+ halo: false,
403
+ haloColor,
404
+ fontSize: cand.fontSize,
405
+ italic: cand.italic,
406
+ letterSpacing: cand.letterSpacing,
407
+ ...cand.lines.length > 1 ? { lines: cand.lines } : {},
408
+ lineNumber: 0
409
+ });
410
+ return true;
411
+ }
412
+ return false;
413
+ };
414
+ for (const cand of candidates) if (cand.bordering) tryPlace(cand);
415
+ for (const cand of candidates) {
416
+ if (cand.bordering) continue;
417
+ if (budgetUsed >= budget) break;
418
+ if (cand.italic && waterPlaced >= waterCap) continue;
419
+ if (tryPlace(cand)) {
420
+ budgetUsed++;
421
+ if (cand.italic) waterPlaced++;
422
+ }
423
+ }
424
+ return placed;
425
+ }
426
+
427
+ // src/map/layout.ts
428
+ var FIT_PAD = 24;
429
+ var PATH_DIGITS = 1;
430
+ var THIN_TOL = 0.6;
431
+ var ALBERS_SKEW_MAX = 1.25;
432
+ function geoThin() {
433
+ const tol2 = THIN_TOL * THIN_TOL;
434
+ return geoTransform({
435
+ lineStart() {
436
+ const t = this;
437
+ t._has = false;
438
+ t._pending = false;
439
+ t.stream.lineStart();
440
+ },
441
+ point(x, y) {
442
+ const t = this;
443
+ t._lx = x;
444
+ t._ly = y;
445
+ if (t._has) {
446
+ const dx = x - t._ex;
447
+ const dy = y - t._ey;
448
+ if (dx * dx + dy * dy < tol2) {
449
+ t._pending = true;
450
+ return;
451
+ }
452
+ }
453
+ t.stream.point(x, y);
454
+ t._ex = x;
455
+ t._ey = y;
456
+ t._has = true;
457
+ t._pending = false;
458
+ },
459
+ lineEnd() {
460
+ const t = this;
461
+ if (t._pending) t.stream.point(t._lx, t._ly);
462
+ t._pending = false;
463
+ t.stream.lineEnd();
464
+ }
465
+ });
466
+ }
467
+ var RAMP_FLOOR = 15;
468
+ var R_DEFAULT = 6;
469
+ var R_MIN = 4;
470
+ var R_MAX = 22;
471
+ var POI_FILL_OPACITY_MAX = 0.92;
472
+ var POI_FILL_OPACITY_MIN = 0.55;
473
+ var W_MIN = 1.25;
474
+ var W_MAX = 8;
475
+ var FONT2 = 11;
476
+ var WORLD_LABEL_ANCHORS = {
477
+ US: [-98.5, 39.5],
478
+ // CONUS geographic centre (near Lebanon, Kansas)
479
+ // Russia crosses the antimeridian (Chukotka at ~170°W), so on a non-global
480
+ // (e.g. Europe) projection its geometry smears across the whole frame and the
481
+ // area-weighted centroid lands mid-map (over Europe) — useless as a label
482
+ // anchor. Pin it to European Russia (~Volga) so a Europe view labels visible
483
+ // western Russia on its eastern margin; on a world view this still sits over
484
+ // Russian land. (See the curated-anchor smear-gate bypass in context-labels.)
485
+ RU: [45, 58]
486
+ };
487
+ var MAX_CLUSTER_EXTENT_FACTOR = 0.18;
488
+ var MAX_COLUMN_ROWS = 7;
489
+ var REGION_LABEL_HALO_RATIO = 4.5;
490
+ var LAND_TINT_LIGHT = 12;
491
+ var LAND_TINT_DARK = 24;
492
+ var TAG_TINT_LIGHT = 60;
493
+ var TAG_TINT_DARK = 68;
494
+ var WATER_TINT_LIGHT = 24;
495
+ var WATER_TINT_DARK = 24;
496
+ var RIVER_WIDTH = 1.3;
497
+ var COMPACT_WIDTH_PX = 480;
498
+ var RELIEF_MIN_AREA = 12;
499
+ var RELIEF_MIN_DIM = 2;
500
+ var RELIEF_HATCH_SPACING = 1.5;
501
+ var RELIEF_HATCH_WIDTH = 0.2;
502
+ var RELIEF_HATCH_STRENGTH = 26;
503
+ var COASTLINE_RING_COUNT = 5;
504
+ var COASTLINE_D0 = 16e-4;
505
+ var COASTLINE_STEP = 28e-4;
506
+ var COASTLINE_THICKNESS = 14e-4;
507
+ var COASTLINE_OPACITY_NEAR = 0.5;
508
+ var COASTLINE_OPACITY_FAR = 0.1;
509
+ var COASTLINE_MIN_EXTENT = 6e-4;
510
+ var COASTLINE_MIN_EXTENT_GLOBAL = 6e-4;
511
+ var COASTLINE_STROKE_MIX = 32;
512
+ var FOREIGN_TINT_LIGHT = 30;
513
+ var FOREIGN_TINT_DARK = 62;
514
+ var MUTED_FOREIGN_LIGHT = 28;
515
+ var MUTED_FOREIGN_DARK = 16;
516
+ var COLO_R = 9;
517
+ var GOLDEN_ANGLE = 2.399963229728653;
518
+ var STACK_OVERLAP = 1;
519
+ var STACK_RING_MAX = 8;
520
+ var STACK_RING_GAP = 4;
521
+ var FAN_STEP = 16;
522
+ var ARC_CURVE_FRAC = 0.18;
523
+ function geomObject(topo) {
524
+ const key = Object.keys(topo.objects)[0];
525
+ return topo.objects[key];
526
+ }
527
+ var decodeCache = /* @__PURE__ */ new WeakMap();
528
+ function mergeFeatures(a, b) {
529
+ const polysOf = (f) => {
530
+ const g = f.geometry;
531
+ if (!g) return null;
532
+ if (g.type === "Polygon") return [g.coordinates];
533
+ if (g.type === "MultiPolygon") return g.coordinates;
534
+ return null;
535
+ };
536
+ const pa = polysOf(a);
537
+ const pb = polysOf(b);
538
+ if (!pa || !pb) return a;
539
+ return {
540
+ ...a,
541
+ geometry: { type: "MultiPolygon", coordinates: [...pa, ...pb] }
542
+ };
543
+ }
544
+ function decodeLayer(topo) {
545
+ const cached = decodeCache.get(topo);
546
+ if (cached) return cached;
547
+ const out = /* @__PURE__ */ new Map();
548
+ for (const g of geomObject(topo).geometries) {
549
+ const f = feature(topo, g);
550
+ if (!f.geometry) continue;
551
+ const tagged = { ...f, id: g.id };
552
+ const existing = out.get(g.id);
553
+ out.set(g.id, existing ? mergeFeatures(existing, tagged) : tagged);
554
+ }
555
+ decodeCache.set(topo, out);
556
+ return out;
557
+ }
558
+ function countryLabelPositions(args) {
559
+ const { geometry, bounds, project, width, height, curated } = args;
560
+ const w0 = bounds[0][0];
561
+ const s0 = bounds[0][1];
562
+ const e0 = bounds[1][0];
563
+ const n0 = bounds[1][1];
564
+ if (![w0, s0, e0, n0].every(Number.isFinite) || e0 <= w0 || n0 <= s0) {
565
+ return mkCurated(curated, project);
566
+ }
567
+ const N = COUNTRY_POS_GRID;
568
+ const onLand = [];
569
+ const kept = [];
570
+ for (let i = 0; i < N; i++) {
571
+ onLand[i] = [];
572
+ const lon = w0 + (i + 0.5) / N * (e0 - w0);
573
+ for (let j = 0; j < N; j++) {
574
+ const lat = s0 + (j + 0.5) / N * (n0 - s0);
575
+ const land = pointInGeometry(geometry, lon, lat);
576
+ onLand[i][j] = land;
577
+ if (!land) continue;
578
+ const p = project(lon, lat);
579
+ if (!p || !Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;
580
+ if (p[0] < 0 || p[0] > width || p[1] < 0 || p[1] > height) continue;
581
+ kept.push({ i, j, lon, lat, sx: p[0], sy: p[1] });
582
+ }
583
+ }
584
+ if (!kept.length) return mkCurated(curated, project);
585
+ const cx = kept.reduce((s, c) => s + c.sx, 0) / kept.length;
586
+ const cy = kept.reduce((s, c) => s + c.sy, 0) / kept.length;
587
+ const interiorness = (c) => {
588
+ let n = 0;
589
+ for (let di = -1; di <= 1; di++)
590
+ for (let dj = -1; dj <= 1; dj++) {
591
+ if (di === 0 && dj === 0) continue;
592
+ const ni = c.i + di;
593
+ const nj = c.j + dj;
594
+ if (ni >= 0 && ni < N && nj >= 0 && nj < N && onLand[ni][nj]) n++;
595
+ }
596
+ return n;
597
+ };
598
+ const dist2ToCentre = (c) => (c.sx - cx) ** 2 + (c.sy - cy) ** 2;
599
+ const centreLon = kept.reduce((s, c) => s + c.lon, 0) / kept.length;
600
+ const centreLat = kept.reduce((s, c) => s + c.lat, 0) / kept.length;
601
+ const centreScreen = project(centreLon, centreLat);
602
+ const lead = pointInGeometry(geometry, centreLon, centreLat) && centreScreen && Number.isFinite(centreScreen[0]) && Number.isFinite(centreScreen[1]) && centreScreen[0] >= 0 && centreScreen[0] <= width && centreScreen[1] >= 0 && centreScreen[1] <= height ? [
603
+ {
604
+ lonLat: [centreLon, centreLat],
605
+ screen: [centreScreen[0], centreScreen[1]]
606
+ }
607
+ ] : [];
608
+ const grid = [...kept].sort(
609
+ (a, b) => interiorness(b) - interiorness(a) || dist2ToCentre(a) - dist2ToCentre(b)
610
+ ).map((c) => ({
611
+ lonLat: [c.lon, c.lat],
612
+ screen: [c.sx, c.sy]
613
+ }));
614
+ const curatedPos = curated ? mkCurated(curated, project) : [];
615
+ return [...curatedPos, ...lead, ...grid].slice(0, MAX_COUNTRY_POSITIONS);
616
+ }
617
+ function mkCurated(curated, project) {
618
+ if (!curated) return [];
619
+ const p = project(curated[0], curated[1]);
620
+ if (!p || !Number.isFinite(p[0]) || !Number.isFinite(p[1])) return [];
621
+ return [{ lonLat: [curated[0], curated[1]], screen: [p[0], p[1]] }];
622
+ }
623
+ var usConusProjection = () => geoConicEqualArea().parallels([29.5, 45.5]).rotate([96, 0]);
624
+ var alaskaProjection = () => geoConicEqualArea().rotate([154, 0]).center([-2, 58.5]).parallels([55, 65]);
625
+ var hawaiiProjection = () => geoMercator();
626
+ function projectionFor(family, extent) {
627
+ switch (family) {
628
+ case "albers-usa":
629
+ return usConusProjection();
630
+ case "conic-equal-area": {
631
+ const s = extent[0][1];
632
+ const n = extent[1][1];
633
+ return geoConicEqualArea().parallels([s + (n - s) / 6, s + (n - s) * 5 / 6]).center([0, (s + n) / 2]);
634
+ }
635
+ case "mercator":
636
+ return geoMercator();
637
+ case "equal-earth":
638
+ return geoEqualEarth();
639
+ case "equirectangular":
640
+ return geoEquirectangular();
641
+ case "natural-earth":
642
+ return geoNaturalEarth1();
643
+ default:
644
+ return geoEquirectangular();
645
+ }
646
+ }
647
+ var INSET_STATES = /* @__PURE__ */ new Set(["US-AK", "US-HI"]);
648
+ var inAlaska = (lon, lat) => lat >= 51 && (lon <= -129 || lon >= 172);
649
+ var inHawaii = (lon, lat) => lat >= 18 && lat <= 23 && lon >= -161 && lon <= -154;
650
+ var FOREIGN_BORDER = {
651
+ CA: [
652
+ "US-AK",
653
+ "US-WA",
654
+ "US-ID",
655
+ "US-MT",
656
+ "US-ND",
657
+ "US-MN",
658
+ "US-MI",
659
+ "US-NY",
660
+ "US-VT",
661
+ "US-NH",
662
+ "US-ME"
663
+ ],
664
+ MX: ["US-CA", "US-AZ", "US-NM", "US-TX"]
665
+ };
666
+ var US_NON_CONUS = /* @__PURE__ */ new Set([
667
+ "US-AK",
668
+ "US-HI",
669
+ "US-AS",
670
+ "US-GU",
671
+ "US-MP",
672
+ "US-PR",
673
+ "US-VI"
674
+ ]);
675
+ function mapBackgroundColor(palette, isDark = false, _dataActive = false) {
676
+ return mix(
677
+ palette.colors.blue,
678
+ palette.bg,
679
+ isDark ? WATER_TINT_DARK : WATER_TINT_LIGHT
680
+ );
681
+ }
682
+ function mapNeutralLandColor(palette, isDark, _dataActive = false) {
683
+ return mix(
684
+ palette.colors.green,
685
+ palette.bg,
686
+ isDark ? LAND_TINT_DARK : LAND_TINT_LIGHT
687
+ );
688
+ }
689
+ function buildMapProjection(resolved, data) {
690
+ const wantsUsStates = resolved.basemaps.subdivisions.includes("us-states");
691
+ const usCrisp = (resolved.projection === "albers-usa" || resolved.projection === "mercator") && wantsUsStates && !!data.naLand;
692
+ const worldTopo = usCrisp ? data.worldDetail : resolved.basemaps.world === "detail" ? data.worldDetail : data.worldCoarse;
693
+ const worldLayer = new Map(decodeLayer(worldTopo));
694
+ if (usCrisp && data.naLand) {
695
+ const [nbW, nbS, nbE, nbN] = [-140, 10, -52, 66];
696
+ const crisp = decodeLayer(data.naLand);
697
+ for (const [iso, cf] of crisp) {
698
+ const base = worldLayer.get(iso);
699
+ if (!base) continue;
700
+ const [[bw, bs], [be, bn]] = geoBounds(base);
701
+ if (bw >= nbW && be <= nbE && bs >= nbS && bn <= nbN)
702
+ worldLayer.set(iso, { ...cf, properties: base.properties });
703
+ }
704
+ }
705
+ const usLayer = wantsUsStates ? decodeLayer(data.usStates) : null;
706
+ const extentOutline = () => {
707
+ const [[w, s], [e, n]] = resolved.extent;
708
+ const N = 16;
709
+ const coords = [];
710
+ for (let i = 0; i <= N; i++) {
711
+ const t = i / N;
712
+ const lon = w + (e - w) * t;
713
+ const lat = s + (n - s) * t;
714
+ coords.push([lon, s], [lon, n], [w, lat], [e, lat]);
715
+ }
716
+ return {
717
+ type: "Feature",
718
+ properties: {},
719
+ geometry: { type: "MultiPoint", coordinates: coords }
720
+ };
721
+ };
722
+ let fitFeatures;
723
+ if (resolved.projection === "albers-usa" && usLayer) {
724
+ fitFeatures = [...usLayer.entries()].filter(([iso]) => !US_NON_CONUS.has(iso)).map(([, f]) => f);
725
+ const neighborPoints = resolved.pois.filter((p) => !inAlaska(p.lon, p.lat) && !inHawaii(p.lon, p.lat)).map((p) => [p.lon, p.lat]);
726
+ if (neighborPoints.length > 0) {
727
+ fitFeatures.push({
728
+ type: "Feature",
729
+ properties: {},
730
+ geometry: { type: "MultiPoint", coordinates: neighborPoints }
731
+ });
732
+ }
733
+ for (const r of resolved.regions) {
734
+ if (r.layer === "country" && (r.iso === "CA" || r.iso === "MX")) {
735
+ const cf = worldLayer.get(r.iso);
736
+ if (cf) fitFeatures.push(cf);
737
+ }
738
+ }
739
+ } else {
740
+ fitFeatures = [extentOutline()];
741
+ }
742
+ const fitTarget = { type: "FeatureCollection", features: fitFeatures };
743
+ const projection = projectionFor(resolved.projection, resolved.extent);
744
+ if (resolved.projection !== "albers-usa") {
745
+ let centerLon = (resolved.extent[0][0] + resolved.extent[1][0]) / 2;
746
+ if (centerLon > 180) centerLon -= 360;
747
+ projection.rotate([-centerLon, 0]);
748
+ }
749
+ const fitGB = geoBounds(fitTarget);
750
+ const fitIsGlobal = fitGB[1][0] - fitGB[0][0] >= 270 || fitGB[1][1] - fitGB[0][1] >= 130;
751
+ return {
752
+ projection,
753
+ fitTarget,
754
+ fitIsGlobal,
755
+ worldLayer,
756
+ usLayer,
757
+ usCrisp,
758
+ wantsUsStates,
759
+ worldTopo
760
+ };
761
+ }
762
+ function parsePathRings(d) {
763
+ const rings = [];
764
+ let cur = [];
765
+ const re = /([MLZ])([^MLZ]*)/g;
766
+ let m;
767
+ while (m = re.exec(d)) {
768
+ if (m[1] === "Z") {
769
+ if (cur.length) rings.push(cur);
770
+ cur = [];
771
+ continue;
772
+ }
773
+ if (m[1] === "M" && cur.length) {
774
+ rings.push(cur);
775
+ cur = [];
776
+ }
777
+ const nums = m[2].split(/[ ,]+/).map(Number);
778
+ for (let i = 0; i + 1 < nums.length; i += 2) {
779
+ const x = nums[i];
780
+ const y = nums[i + 1];
781
+ if (Number.isFinite(x) && Number.isFinite(y)) cur.push([x, y]);
782
+ }
783
+ }
784
+ if (cur.length) rings.push(cur);
785
+ return rings;
786
+ }
787
+ function dropAntimeridianWrapSlivers(d, width, height) {
788
+ const rings = parsePathRings(d);
789
+ if (rings.length <= 1) return d;
790
+ const eps = 0.75;
791
+ const minArea = 3e-3 * width * height;
792
+ const ringArea = (r) => {
793
+ let s = 0;
794
+ for (let i = 0; i < r.length; i++) {
795
+ const a = r[i];
796
+ const b = r[(i + 1) % r.length];
797
+ s += a[0] * b[1] - b[0] * a[1];
798
+ }
799
+ return Math.abs(s) / 2;
800
+ };
801
+ const areas = rings.map(ringArea);
802
+ const maxArea = Math.max(...areas);
803
+ const onVEdge = (a, b) => Math.abs(a[0]) <= eps && Math.abs(b[0]) <= eps || Math.abs(a[0] - width) <= eps && Math.abs(b[0] - width) <= eps;
804
+ let dropped = false;
805
+ const kept = rings.filter((r, idx) => {
806
+ if (areas[idx] >= maxArea || areas[idx] >= minArea) return true;
807
+ const touches = r.some((p, i) => onVEdge(p, r[(i + 1) % r.length]));
808
+ if (touches) {
809
+ dropped = true;
810
+ return false;
811
+ }
812
+ return true;
813
+ });
814
+ if (!dropped) return d;
815
+ return kept.map(
816
+ (r) => r.map((p, i) => (i ? "L" : "M") + p[0] + "," + p[1]).join("") + "Z"
817
+ ).join("");
818
+ }
819
+ function albersSkewFallback(resolved, data, width, height) {
820
+ if (resolved.projection !== "albers-usa") return false;
821
+ if (!resolved.basemaps.subdivisions.includes("us-states")) return false;
822
+ if (!(width > 0) || !(height > 0)) return false;
823
+ const akRef = resolved.regions.some((r) => r.iso === "US-AK") || resolved.pois.some((p) => inAlaska(p.lon, p.lat));
824
+ const hiRef = resolved.regions.some((r) => r.iso === "US-HI") || resolved.pois.some((p) => inHawaii(p.lon, p.lat));
825
+ if (akRef || hiRef) return false;
826
+ const { projection, fitTarget, usLayer } = buildMapProjection(resolved, data);
827
+ if (!usLayer || ![...usLayer.keys()].some((iso) => !US_NON_CONUS.has(iso)))
828
+ return false;
829
+ projection.fitSize([1e3, 1e3], fitTarget);
830
+ const b = geoPath(projection).bounds(fitTarget);
831
+ const conusAspect = (b[1][0] - b[0][0]) / (b[1][1] - b[0][1]);
832
+ if (!(conusAspect > 0)) return false;
833
+ const canvasAspect = width / height;
834
+ const skew = Math.max(canvasAspect / conusAspect, conusAspect / canvasAspect);
835
+ return skew > ALBERS_SKEW_MAX;
836
+ }
837
+ var REGION_GEO_MEMO = /* @__PURE__ */ new WeakMap();
838
+ var REGION_GEO_MAX_FITS = 8;
839
+ function regionGeoBucket(resolved, projKey) {
840
+ let byFit = REGION_GEO_MEMO.get(resolved);
841
+ if (!byFit) {
842
+ byFit = /* @__PURE__ */ new Map();
843
+ REGION_GEO_MEMO.set(resolved, byFit);
844
+ }
845
+ let bucket = byFit.get(projKey);
846
+ if (!bucket) {
847
+ bucket = /* @__PURE__ */ new Map();
848
+ byFit.set(projKey, bucket);
849
+ if (byFit.size > REGION_GEO_MAX_FITS) {
850
+ const oldest = byFit.keys().next().value;
851
+ if (oldest !== void 0) byFit.delete(oldest);
852
+ }
853
+ }
854
+ return bucket;
855
+ }
856
+ function layoutMap(resolvedIn, data, size, opts) {
857
+ const { palette, isDark } = opts;
858
+ const { width, height } = size;
859
+ let resolved = resolvedIn;
860
+ if (albersSkewFallback(resolved, data, width, height)) {
861
+ resolved = { ...resolved, projection: "conic-equal-area" };
862
+ }
863
+ const {
864
+ projection,
865
+ fitTarget,
866
+ fitIsGlobal,
867
+ worldLayer,
868
+ usLayer,
869
+ usCrisp,
870
+ worldTopo
871
+ } = buildMapProjection(resolved, data);
872
+ const usContext = usLayer !== null;
873
+ const regionStroke = isDark ? mix(palette.border, palette.text, 65) : mix(palette.text, palette.bg, 78);
874
+ const values = resolved.regions.filter((r) => r.value !== void 0).map((r) => r.value);
875
+ const rampMin = values.length > 0 ? Math.min(...values) : 0;
876
+ const rampMax = Math.max(...values);
877
+ const rampHue = resolveColor(resolved.directives.regionMetricColor ?? "", palette) ?? palette.colors.red;
878
+ const rampLow = resolved.directives.regionMetricLowColor ? resolveColor(resolved.directives.regionMetricLowColor, palette) ?? void 0 : void 0;
879
+ const hasRamp = values.length > 0;
880
+ const VALUE_NAME = hasRamp ? resolved.directives.regionMetric?.trim() || "Value" : null;
881
+ const matchColorGroup = (v) => {
882
+ const lv = v.trim().toLowerCase();
883
+ if (lv === "none") return null;
884
+ if (lv === VALUE_NAME?.toLowerCase()) return VALUE_NAME;
885
+ const tg = resolved.tagGroups.find((g) => g.name.toLowerCase() === lv);
886
+ return tg ? tg.name : v;
887
+ };
888
+ const fillGroupNames = /* @__PURE__ */ new Set();
889
+ for (const g of resolved.tagGroups) {
890
+ const k = g.name.toLowerCase();
891
+ if (resolved.regions.some((r) => r.tags[k]) || resolved.pois.some((p) => p.tags[k]))
892
+ fillGroupNames.add(g.name);
893
+ }
894
+ const firstFillGroup = resolved.tagGroups.find((g) => fillGroupNames.has(g.name))?.name ?? null;
895
+ const override = opts.activeGroup;
896
+ let activeGroup;
897
+ if (override !== void 0) {
898
+ activeGroup = override === null ? null : matchColorGroup(override);
899
+ } else if (resolved.directives.activeTag !== void 0) {
900
+ activeGroup = matchColorGroup(resolved.directives.activeTag);
901
+ } else {
902
+ activeGroup = VALUE_NAME ?? firstFillGroup ?? resolved.tagGroups[0]?.name ?? null;
903
+ }
904
+ const activeIsScore = VALUE_NAME !== null && activeGroup === VALUE_NAME;
905
+ const mutedBasemap = activeIsScore || activeGroup !== null && fillGroupNames.has(activeGroup);
906
+ const neutralFill = mapNeutralLandColor(palette, isDark, mutedBasemap);
907
+ const water = mapBackgroundColor(palette, isDark, mutedBasemap);
908
+ const lakeStroke = mix(regionStroke, water, 45);
909
+ const foreignFill = mix(
910
+ palette.colors.gray,
911
+ palette.bg,
912
+ mutedBasemap ? isDark ? MUTED_FOREIGN_DARK : MUTED_FOREIGN_LIGHT : isDark ? FOREIGN_TINT_DARK : FOREIGN_TINT_LIGHT
913
+ );
914
+ const contrastFloorTone = (tone, ...substrates) => {
915
+ const clears = (t) => substrates.every((s) => contrastRatio(t, s) >= REGION_LABEL_HALO_RATIO);
916
+ if (clears(tone)) return tone;
917
+ for (let s = 10; s <= 100; s += 10) {
918
+ const t = mix(palette.text, tone, s);
919
+ if (clears(t)) return t;
920
+ }
921
+ return palette.text;
922
+ };
923
+ const backdropLandTone = contrastFloorTone(
924
+ palette.textMuted,
925
+ neutralFill,
926
+ water
927
+ );
928
+ const backdropWaterTone = contrastFloorTone(
929
+ mix(palette.colors.blue, palette.textMuted, 50),
930
+ water,
931
+ neutralFill
932
+ );
933
+ const hasDirectColor = resolved.regions.some((r) => r.color !== void 0);
934
+ const colorizeActive = resolved.directives.noColorize !== true && !hasRamp && !hasDirectColor && fillGroupNames.size === 0;
935
+ const colorByIso = /* @__PURE__ */ new Map();
936
+ if (colorizeActive) {
937
+ const adjacency = /* @__PURE__ */ new Map();
938
+ const addEdges = (src) => {
939
+ for (const [iso, ns] of src) {
940
+ const cur = adjacency.get(iso);
941
+ if (cur) cur.push(...ns);
942
+ else adjacency.set(iso, [...ns]);
943
+ }
944
+ };
945
+ addEdges(buildAdjacency(worldTopo));
946
+ if (usLayer) {
947
+ addEdges(buildAdjacency(data.usStates));
948
+ for (const [country, states] of Object.entries(FOREIGN_BORDER)) {
949
+ const cn = adjacency.get(country);
950
+ if (!cn) continue;
951
+ for (const st of states) {
952
+ const sn = adjacency.get(st);
953
+ if (!sn) continue;
954
+ cn.push(st);
955
+ sn.push(country);
956
+ }
957
+ }
958
+ }
959
+ const { byIso, huesNeeded } = assignColors(
960
+ [...adjacency.keys()],
961
+ adjacency
962
+ );
963
+ const tints = politicalTints(palette, huesNeeded, isDark);
964
+ for (const [iso, idx] of byIso) colorByIso.set(iso, tints[idx]);
965
+ }
966
+ const colorizeStroke = (fill) => mix(fill, palette.text, 35);
967
+ const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
968
+ const rampLowFloor = mix(rampHue, rampBase, RAMP_FLOOR);
969
+ const fillForValue = (s) => {
970
+ const t = rampMax > rampMin ? (s - rampMin) / (rampMax - rampMin) : 1;
971
+ if (rampLow !== void 0)
972
+ return valueRampColor(rampLow, rampHue, t, { isDark });
973
+ const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
974
+ return mix(rampHue, rampBase, pct);
975
+ };
976
+ const tagFill = (tags, groupName) => {
977
+ if (!groupName) return null;
978
+ const group = resolved.tagGroups.find(
979
+ (g) => g.name.toLowerCase() === groupName.toLowerCase()
980
+ );
981
+ if (!group) return null;
982
+ const val = tags[tagAttrKey(group.name)] ?? group.defaultValue;
983
+ if (!val) return null;
984
+ const entry = group.entries.find(
985
+ (e) => e.value.toLowerCase() === val.toLowerCase()
986
+ );
987
+ if (!entry?.color) return null;
988
+ return mix(
989
+ entry.color,
990
+ palette.bg,
991
+ isDark ? TAG_TINT_DARK : TAG_TINT_LIGHT
992
+ );
993
+ };
994
+ const directFill = (name) => {
995
+ const hex = name ? resolveColor(name, palette) : null;
996
+ if (!hex) return null;
997
+ return mix(hex, palette.bg, isDark ? TAG_TINT_DARK : TAG_TINT_LIGHT);
998
+ };
999
+ const regionFill = (r) => {
1000
+ const direct = directFill(r.color);
1001
+ if (direct) return direct;
1002
+ if (activeIsScore) {
1003
+ return r.value !== void 0 ? fillForValue(r.value) : neutralFill;
1004
+ }
1005
+ if (colorizeActive) return (r.iso && colorByIso.get(r.iso)) ?? neutralFill;
1006
+ return tagFill(r.tags, activeGroup) ?? neutralFill;
1007
+ };
1008
+ const regionById = new Map(resolved.regions.map((r) => [r.iso, r]));
1009
+ let legend = null;
1010
+ if (!resolved.directives.noLegend) {
1011
+ const legendTagGroups = resolved.tagGroups.map((g) => ({
1012
+ name: g.name,
1013
+ entries: g.entries.map((e) => ({ value: e.value, color: e.color }))
1014
+ }));
1015
+ if (legendTagGroups.length > 0 || hasRamp) {
1016
+ legend = {
1017
+ tagGroups: legendTagGroups,
1018
+ activeGroup,
1019
+ ...hasRamp && {
1020
+ ramp: {
1021
+ ...resolved.directives.regionMetric !== void 0 && {
1022
+ metric: resolved.directives.regionMetric
1023
+ },
1024
+ min: rampMin,
1025
+ max: rampMax,
1026
+ low: rampLow ?? rampLowFloor,
1027
+ high: rampHue
1028
+ }
1029
+ }
1030
+ };
1031
+ }
1032
+ }
1033
+ const shownTitle = resolved.directives.noTitle ? null : resolved.title;
1034
+ const TITLE_GAP = 16;
1035
+ let topPad = FIT_PAD;
1036
+ if (shownTitle && resolved.pois.length > 0) {
1037
+ const bannerBottom = (resolved.subtitle ? TITLE_Y + TITLE_FONT_SIZE : TITLE_Y) + TITLE_FONT_SIZE / 2;
1038
+ topPad = Math.max(FIT_PAD, bannerBottom + TITLE_GAP);
1039
+ }
1040
+ const legendBand = mapLegendBand(legend, {
1041
+ width,
1042
+ mode: opts.legendMode ?? "preview",
1043
+ hasTitle: Boolean(shownTitle),
1044
+ hasSubtitle: Boolean(resolved.subtitle)
1045
+ });
1046
+ if (legendBand > topPad) topPad = legendBand;
1047
+ const topReserved = [];
1048
+ if (shownTitle) {
1049
+ const lines = resolved.subtitle ? 2 : 1;
1050
+ const tw = Math.max(
1051
+ measureLegendText(shownTitle, TITLE_FONT_SIZE),
1052
+ resolved.subtitle ? measureLegendText(resolved.subtitle, TITLE_FONT_SIZE) : 0
1053
+ ) + 2 * TITLE_FONT_SIZE;
1054
+ topReserved.push({
1055
+ x: width / 2 - tw / 2,
1056
+ y: TITLE_Y - TITLE_FONT_SIZE,
1057
+ w: tw,
1058
+ h: TITLE_FONT_SIZE * (lines + 0.5)
1059
+ });
1060
+ }
1061
+ const legendBox = mapLegendBox(legend, {
1062
+ width,
1063
+ mode: opts.legendMode ?? "preview",
1064
+ hasTitle: Boolean(shownTitle),
1065
+ hasSubtitle: Boolean(resolved.subtitle)
1066
+ });
1067
+ if (legendBox)
1068
+ topReserved.push({
1069
+ x: legendBox.x,
1070
+ y: legendBox.y,
1071
+ w: legendBox.width,
1072
+ h: legendBox.height
1073
+ });
1074
+ const reserve = opts._calloutReserve;
1075
+ const fitLeft = FIT_PAD + (reserve?.left ?? 0);
1076
+ const fitRight = width - FIT_PAD - (reserve?.right ?? 0);
1077
+ const fitTop = topPad + (reserve?.top ?? 0);
1078
+ const fitBottom = height - FIT_PAD - (reserve?.bottom ?? 0);
1079
+ const fitBox = [
1080
+ [fitLeft, fitTop],
1081
+ [Math.max(fitLeft + 1, fitRight), Math.max(fitTop + 1, fitBottom)]
1082
+ ];
1083
+ projection.fitExtent(fitBox, fitTarget);
1084
+ if (!fitIsGlobal && resolved.projection !== "albers-usa" && resolved.regions.length > 0) {
1085
+ let yMin = Infinity;
1086
+ let yMax = -Infinity;
1087
+ for (const r of resolved.regions) {
1088
+ const bb = r.iso ? featureBboxPrimary(data.worldCoarse, r.iso) : null;
1089
+ if (!bb) continue;
1090
+ for (const lon of [bb[0][0], bb[1][0]]) {
1091
+ for (const lat of [bb[0][1], bb[1][1]]) {
1092
+ const p = projection([lon, lat]);
1093
+ if (p && Number.isFinite(p[1])) {
1094
+ if (p[1] < yMin) yMin = p[1];
1095
+ if (p[1] > yMax) yMax = p[1];
1096
+ }
1097
+ }
1098
+ }
1099
+ }
1100
+ if (yMin < yMax) {
1101
+ const boxTop = fitTop;
1102
+ const boxBottom = fitBottom;
1103
+ let dy = (boxTop + boxBottom) / 2 - (yMin + yMax) / 2;
1104
+ const minDy = boxTop - yMin;
1105
+ const maxDy = boxBottom - yMax;
1106
+ if (minDy <= maxDy) dy = Math.max(minDy, Math.min(maxDy, dy));
1107
+ const [tx, ty] = projection.translate();
1108
+ projection.translate([tx, ty + dy]);
1109
+ }
1110
+ }
1111
+ if (!fitIsGlobal && resolved.projection === "albers-usa") {
1112
+ const cb = geoPath(projection).bounds(fitTarget);
1113
+ if (Number.isFinite(cb[0][1])) {
1114
+ const slack = fitBottom - fitTop - (cb[1][1] - cb[0][1]);
1115
+ if (slack > 0) {
1116
+ const currentTopGap = cb[0][1] - fitTop;
1117
+ const dy = slack * 0.3 - currentTopGap;
1118
+ const [tx, ty] = projection.translate();
1119
+ projection.translate([tx, ty + dy]);
1120
+ }
1121
+ }
1122
+ }
1123
+ let path;
1124
+ let project;
1125
+ let stretchParams = null;
1126
+ if (fitIsGlobal && !opts.preferContain) {
1127
+ const cb = geoPath(projection).bounds(fitTarget);
1128
+ const bx0 = cb[0][0];
1129
+ const by0 = cb[0][1];
1130
+ const cw = cb[1][0] - bx0;
1131
+ const ch = cb[1][1] - by0;
1132
+ const topReserve = resolved.title && resolved.pois.length > 0 || legendBand > 0 ? topPad : 0;
1133
+ const ox = 0;
1134
+ const oy = topReserve;
1135
+ const sx = cw > 0 ? width / cw : 1;
1136
+ const sy = ch > 0 ? (height - topReserve) / ch : 1;
1137
+ stretchParams = { sx, sy, ox, oy, bx0, by0 };
1138
+ const stretch = (x, y) => [
1139
+ ox + (x - bx0) * sx,
1140
+ oy + (y - by0) * sy
1141
+ ];
1142
+ const baseProjection = projection;
1143
+ const tx = geoTransform({
1144
+ point(x, y) {
1145
+ const [px, py] = stretch(x, y);
1146
+ this.stream.point(px, py);
1147
+ }
1148
+ });
1149
+ const thin = geoThin();
1150
+ path = geoPath({
1151
+ stream: (s) => baseProjection.stream(
1152
+ tx.stream(
1153
+ thin.stream(s)
1154
+ )
1155
+ )
1156
+ }).digits(PATH_DIGITS);
1157
+ project = (lon, lat) => {
1158
+ const p = baseProjection([lon, lat]);
1159
+ return p ? stretch(p[0], p[1]) : null;
1160
+ };
1161
+ } else {
1162
+ projection.clipExtent([
1163
+ [0, 0],
1164
+ [width, height]
1165
+ ]);
1166
+ const thin = geoThin();
1167
+ path = geoPath({
1168
+ stream: (s) => projection.stream(
1169
+ thin.stream(s)
1170
+ )
1171
+ }).digits(PATH_DIGITS);
1172
+ project = (lon, lat) => projection([lon, lat]) ?? null;
1173
+ }
1174
+ const regionGeo = regionGeoBucket(
1175
+ resolvedIn,
1176
+ JSON.stringify([
1177
+ width,
1178
+ height,
1179
+ [
1180
+ [-100, 40],
1181
+ [-120, 37],
1182
+ [-80, 40],
1183
+ [0, 0],
1184
+ [60, 30],
1185
+ [-60, -30],
1186
+ [150, 60],
1187
+ [-150, -60],
1188
+ [100, -40],
1189
+ [-30, 50]
1190
+ ].map(([lo, la]) => project(lo, la))
1191
+ ])
1192
+ );
1193
+ const insets = [];
1194
+ const insetRegions = [];
1195
+ const insetLabelSeeds = [];
1196
+ const akRef = resolved.regions.some((r) => r.iso === "US-AK") || resolved.pois.some((p) => inAlaska(p.lon, p.lat));
1197
+ const hiRef = resolved.regions.some((r) => r.iso === "US-HI") || resolved.pois.some((p) => inHawaii(p.lon, p.lat));
1198
+ if (resolved.projection === "albers-usa" && usLayer && (akRef || hiRef)) {
1199
+ const PAD = 8;
1200
+ const GAP = 12;
1201
+ const yB = height - FIT_PAD;
1202
+ const BW = 8;
1203
+ const coast = /* @__PURE__ */ new Map();
1204
+ const addPt = (lon, lat) => {
1205
+ const p = projection([lon, lat]);
1206
+ if (!p) return;
1207
+ const bi = Math.floor(p[0] / BW);
1208
+ const cur = coast.get(bi);
1209
+ if (cur === void 0 || p[1] > cur) coast.set(bi, p[1]);
1210
+ };
1211
+ const walk = (co) => {
1212
+ if (Array.isArray(co) && typeof co[0] === "number")
1213
+ addPt(co[0], co[1]);
1214
+ else if (Array.isArray(co)) for (const c of co) walk(c);
1215
+ };
1216
+ for (const [iso, f] of usLayer) {
1217
+ if (US_NON_CONUS.has(iso)) continue;
1218
+ walk(f.geometry.coordinates);
1219
+ }
1220
+ const at = (x) => {
1221
+ const bi = Math.floor(x / BW);
1222
+ let y = -Infinity;
1223
+ for (let k = bi - 1; k <= bi + 1; k++) {
1224
+ const v = coast.get(k);
1225
+ if (v !== void 0 && v > y) y = v;
1226
+ }
1227
+ return y;
1228
+ };
1229
+ const coastFloor = (x0, xr) => {
1230
+ const n = 24;
1231
+ let maxY = -Infinity;
1232
+ for (let i = 0; i <= n; i++) {
1233
+ const y = at(x0 + (xr - x0) * i / n);
1234
+ if (y > maxY) maxY = y;
1235
+ }
1236
+ return maxY;
1237
+ };
1238
+ const placeInset = (iso, proj, boxX, iwReq) => {
1239
+ const f = usLayer.get(iso);
1240
+ if (!f) return boxX;
1241
+ const x0 = boxX;
1242
+ const iw = Math.min(iwReq, width - FIT_PAD - x0 - 2 * PAD);
1243
+ if (iw < 24) return boxX;
1244
+ const xr = x0 + iw + 2 * PAD;
1245
+ const floor = coastFloor(x0, xr);
1246
+ const topGuess = floor > -Infinity ? floor + GAP : yB - height * 0.42;
1247
+ proj.fitWidth(iw, f);
1248
+ const bb = geoPath(proj).bounds(f);
1249
+ const sh = Number.isFinite(bb[0][0]) ? bb[1][1] - bb[0][1] : iw;
1250
+ const needH = sh + 2 * PAD;
1251
+ let topFit = topGuess;
1252
+ const bottom = Math.min(topFit + needH, yB);
1253
+ if (bottom - topFit < needH) topFit = bottom - needH;
1254
+ proj.fitExtent(
1255
+ [
1256
+ [x0 + PAD, topFit + PAD],
1257
+ [xr - PAD, bottom - PAD]
1258
+ ],
1259
+ f
1260
+ );
1261
+ const insetPath = geoPath(proj).digits(PATH_DIGITS);
1262
+ const d = insetPath(f) ?? "";
1263
+ if (!d) return xr;
1264
+ let contextLand;
1265
+ if (iso === "US-AK") {
1266
+ const can = worldLayer.get("CA");
1267
+ const cd = can ? insetPath(can) ?? "" : "";
1268
+ if (cd)
1269
+ contextLand = {
1270
+ d: cd,
1271
+ fill: colorizeActive ? colorByIso.get("CA") ?? foreignFill : foreignFill
1272
+ };
1273
+ }
1274
+ const r = regionById.get(iso);
1275
+ let fill = colorizeActive ? colorByIso.get(iso) ?? neutralFill : neutralFill;
1276
+ let lineNumber = -1;
1277
+ if (r?.layer === "us-state") {
1278
+ fill = regionFill(r);
1279
+ lineNumber = r.lineNumber;
1280
+ }
1281
+ insets.push({
1282
+ x: x0,
1283
+ y: topFit,
1284
+ w: xr - x0,
1285
+ h: bottom - topFit,
1286
+ points: [
1287
+ [x0, topFit],
1288
+ [xr, topFit],
1289
+ [xr, bottom],
1290
+ [x0, bottom]
1291
+ ],
1292
+ // The FITTED inset projection (just fit to this box) — captured so the
1293
+ // geo-query can invert pixels inside the frame back to AK/HI coords.
1294
+ projection: proj,
1295
+ ...contextLand && { contextLand }
1296
+ });
1297
+ insetRegions.push({
1298
+ id: iso,
1299
+ d,
1300
+ fill,
1301
+ stroke: colorizeActive ? colorizeStroke(fill) : regionStroke,
1302
+ lineNumber,
1303
+ layer: "us-state",
1304
+ ...r?.value !== void 0 && { value: r.value },
1305
+ ...r && Object.keys(r.tags).length > 0 && { tags: r.tags }
1306
+ });
1307
+ const ctr = geoPath(proj).centroid(f);
1308
+ if (Number.isFinite(ctr[0])) {
1309
+ const name = f.properties?.name ?? iso;
1310
+ insetLabelSeeds.push({ x: ctr[0], y: ctr[1], iso, name, lineNumber });
1311
+ }
1312
+ return xr;
1313
+ };
1314
+ let akRight = FIT_PAD;
1315
+ if (akRef)
1316
+ akRight = placeInset("US-AK", alaskaProjection(), FIT_PAD, width * 0.18);
1317
+ if (hiRef)
1318
+ placeInset(
1319
+ "US-HI",
1320
+ hawaiiProjection(),
1321
+ akRef ? akRight + 24 : FIT_PAD,
1322
+ width * 0.12
1323
+ );
1324
+ }
1325
+ const conusFit = resolved.projection === "albers-usa" && !!usLayer;
1326
+ const classifyExtent = conusFit ? geoBounds(fitTarget) : resolved.extent;
1327
+ const dLonSpan = classifyExtent[1][0] - classifyExtent[0][0];
1328
+ const dLatSpan = classifyExtent[1][1] - classifyExtent[0][1];
1329
+ const isGlobalView = dLonSpan >= 270 || dLatSpan >= 130;
1330
+ const vLonCenter = (classifyExtent[0][0] + classifyExtent[1][0]) / 2;
1331
+ const normLon = (lon) => {
1332
+ let L = lon;
1333
+ while (L < vLonCenter - 180) L += 360;
1334
+ while (L > vLonCenter + 180) L -= 360;
1335
+ return L;
1336
+ };
1337
+ const ringOverlapsView = (ring) => {
1338
+ let loMin = Infinity, loMax = -Infinity, rawMin = Infinity, rawMax = -Infinity;
1339
+ const lons = [];
1340
+ for (const [rawLon] of ring) {
1341
+ const lon = normLon(rawLon);
1342
+ lons.push(lon);
1343
+ if (lon < loMin) loMin = lon;
1344
+ if (lon > loMax) loMax = lon;
1345
+ if (rawLon < rawMin) rawMin = rawLon;
1346
+ if (rawLon > rawMax) rawMax = rawLon;
1347
+ }
1348
+ lons.sort((a, b) => a - b);
1349
+ let maxGap = 0;
1350
+ for (let i = 1; i < lons.length; i++)
1351
+ maxGap = Math.max(maxGap, lons[i] - lons[i - 1]);
1352
+ if (lons.length > 1)
1353
+ maxGap = Math.max(maxGap, lons[0] + 360 - lons[lons.length - 1]);
1354
+ const occupiedArc = 360 - maxGap;
1355
+ if (occupiedArc > 270) return false;
1356
+ if (rawMax - rawMin > 180 && occupiedArc < 90) return false;
1357
+ let px0 = Infinity, py0 = Infinity, px1 = -Infinity, py1 = -Infinity, anyFinite = false;
1358
+ for (const [lon, lat] of ring) {
1359
+ const p = project(lon, lat);
1360
+ if (!p || !Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;
1361
+ anyFinite = true;
1362
+ if (p[0] < px0) px0 = p[0];
1363
+ if (p[0] > px1) px1 = p[0];
1364
+ if (p[1] < py0) py0 = p[1];
1365
+ if (p[1] > py1) py1 = p[1];
1366
+ }
1367
+ if (!anyFinite) return false;
1368
+ return !(px1 < 0 || px0 > width || py1 < 0 || py0 > height);
1369
+ };
1370
+ const cullFeatureToView = (f) => {
1371
+ if (isGlobalView) return f;
1372
+ const g = f.geometry;
1373
+ if (!g) return f;
1374
+ if (g.type === "Polygon") {
1375
+ const ring = g.coordinates[0];
1376
+ return ringOverlapsView(ring) ? f : null;
1377
+ }
1378
+ if (g.type === "MultiPolygon") {
1379
+ const polys = g.coordinates;
1380
+ const keep = polys.filter(
1381
+ (p) => ringOverlapsView(p[0])
1382
+ );
1383
+ if (!keep.length) return null;
1384
+ if (keep.length === polys.length) return f;
1385
+ return { ...f, geometry: { ...g, coordinates: keep } };
1386
+ }
1387
+ return f;
1388
+ };
1389
+ const SEAM_SLIVER_MAX_SPAN = 100;
1390
+ const ringIsFrameFiller = (ring) => {
1391
+ const lons = ring.map(([lon]) => lon).sort((a, b) => a - b);
1392
+ if (lons.length < 2) return false;
1393
+ let maxGap = -1;
1394
+ let gapIdx = 0;
1395
+ for (let i = 1; i < lons.length; i++) {
1396
+ const g = lons[i] - lons[i - 1];
1397
+ if (g > maxGap) {
1398
+ maxGap = g;
1399
+ gapIdx = i;
1400
+ }
1401
+ }
1402
+ const wrapGap = lons[0] + 360 - lons[lons.length - 1];
1403
+ if (wrapGap >= maxGap) return false;
1404
+ const span = 360 - maxGap;
1405
+ const east = lons[gapIdx - 1] + 360;
1406
+ return east > 180 && span < SEAM_SLIVER_MAX_SPAN;
1407
+ };
1408
+ const dropFrameFillers = (f) => {
1409
+ const g = f.geometry;
1410
+ if (!g) return f;
1411
+ if (g.type === "Polygon") {
1412
+ const ring = g.coordinates[0];
1413
+ return ringIsFrameFiller(ring) ? null : f;
1414
+ }
1415
+ if (g.type === "MultiPolygon") {
1416
+ const polys = g.coordinates;
1417
+ const keep = polys.filter(
1418
+ (p) => !ringIsFrameFiller(p[0])
1419
+ );
1420
+ if (!keep.length) return null;
1421
+ if (keep.length === polys.length) return f;
1422
+ return { ...f, geometry: { ...g, coordinates: keep } };
1423
+ }
1424
+ return f;
1425
+ };
1426
+ const regions = [];
1427
+ const pushRegionLayer = (layerFeatures, layerKind, shouldCull) => {
1428
+ for (const [iso, f] of layerFeatures) {
1429
+ if (layerKind === "us-state" && usContext && resolved.projection === "albers-usa" && INSET_STATES.has(iso))
1430
+ continue;
1431
+ if (layerKind === "country" && usContext && iso === "US") continue;
1432
+ if (layerKind === "country" && iso === "AQ" && !regionById.has("AQ"))
1433
+ continue;
1434
+ const r = regionById.get(iso);
1435
+ const geoK = layerKind + ":" + iso;
1436
+ let geo = regionGeo.get(geoK);
1437
+ if (geo === void 0) {
1438
+ const viewF = shouldCull ? cullFeatureToView(f) : dropFrameFillers(f);
1439
+ if (!viewF) {
1440
+ regionGeo.set(geoK, null);
1441
+ continue;
1442
+ }
1443
+ const raw = path(viewF) ?? "";
1444
+ const d0 = fitIsGlobal ? dropAntimeridianWrapSlivers(raw, width, height) : raw;
1445
+ if (!d0) {
1446
+ regionGeo.set(geoK, null);
1447
+ continue;
1448
+ }
1449
+ const anchor = WORLD_LABEL_ANCHORS[iso];
1450
+ const cc = anchor ? project(anchor[0], anchor[1]) : path.centroid(viewF);
1451
+ const ok = cc != null && Number.isFinite(cc[0]) && Number.isFinite(cc[1]);
1452
+ geo = { d: d0, cx: ok ? cc[0] : void 0, cy: ok ? cc[1] : void 0 };
1453
+ regionGeo.set(geoK, geo);
1454
+ } else if (geo === null) {
1455
+ continue;
1456
+ }
1457
+ const d = geo.d;
1458
+ const hasCentroid = geo.cx !== void 0 && geo.cy !== void 0;
1459
+ const isThisLayer = r?.layer === layerKind;
1460
+ const isForeign = layerKind === "country" && usContext && iso !== "US";
1461
+ const baseFill = isForeign ? foreignFill : neutralFill;
1462
+ let fill = colorizeActive ? colorByIso.get(iso) ?? baseFill : baseFill;
1463
+ let label;
1464
+ let lineNumber = -1;
1465
+ let layer = "base";
1466
+ if (isThisLayer) {
1467
+ fill = regionFill(r);
1468
+ lineNumber = r.lineNumber;
1469
+ layer = layerKind;
1470
+ label = r.name;
1471
+ } else {
1472
+ label = f.properties?.name;
1473
+ }
1474
+ regions.push({
1475
+ id: iso,
1476
+ d,
1477
+ fill,
1478
+ stroke: colorizeActive ? colorizeStroke(fill) : regionStroke,
1479
+ lineNumber,
1480
+ layer,
1481
+ ...label !== void 0 && { label },
1482
+ ...hasCentroid && { labelX: geo.cx, labelY: geo.cy },
1483
+ ...isThisLayer && r.value !== void 0 && { value: r.value },
1484
+ ...isThisLayer && Object.keys(r.tags).length > 0 && { tags: r.tags }
1485
+ });
1486
+ }
1487
+ };
1488
+ pushRegionLayer(worldLayer, "country", !isGlobalView);
1489
+ if (usLayer) pushRegionLayer(usLayer, "us-state", !conusFit && !isGlobalView);
1490
+ const lakesTopo = usCrisp && data.naLakes ? data.naLakes : data.lakes;
1491
+ if (lakesTopo) {
1492
+ for (const [, f] of decodeLayer(lakesTopo)) {
1493
+ const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
1494
+ if (!viewF) continue;
1495
+ const d = path(viewF) ?? "";
1496
+ if (!d) continue;
1497
+ regions.push({
1498
+ id: "lake",
1499
+ d,
1500
+ fill: water,
1501
+ stroke: lakeStroke,
1502
+ lineNumber: -1,
1503
+ layer: "base"
1504
+ });
1505
+ }
1506
+ }
1507
+ const pointInRings = (px, py, rings) => {
1508
+ let inside = false;
1509
+ for (const ring of rings) {
1510
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
1511
+ const [xi, yi] = ring[i];
1512
+ const [xj, yj] = ring[j];
1513
+ if (yi > py !== yj > py && px < (xj - xi) * (py - yi) / (yj - yi) + xi)
1514
+ inside = !inside;
1515
+ }
1516
+ }
1517
+ return inside;
1518
+ };
1519
+ const fillHitTargets = [...regions, ...insetRegions].map((r) => {
1520
+ const rings = parsePathRings(r.d);
1521
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
1522
+ for (const ring of rings)
1523
+ for (const p of ring) {
1524
+ if (p[0] < minX) minX = p[0];
1525
+ if (p[0] > maxX) maxX = p[0];
1526
+ if (p[1] < minY) minY = p[1];
1527
+ if (p[1] > maxY) maxY = p[1];
1528
+ }
1529
+ r.bbox = [minX, minY, maxX, maxY];
1530
+ r.rings = rings;
1531
+ return { fill: r.fill, rings, minX, minY, maxX, maxY };
1532
+ });
1533
+ const fillAt = (x, y) => {
1534
+ let hit = water;
1535
+ for (const t of fillHitTargets) {
1536
+ if (x < t.minX || x > t.maxX || y < t.minY || y > t.maxY) continue;
1537
+ if (pointInRings(x, y, t.rings)) hit = t.fill;
1538
+ }
1539
+ return hit;
1540
+ };
1541
+ const labelOnFill = (fill) => {
1542
+ const color = contrastRatio(fill, palette.textOnFillDark) >= contrastRatio(fill, palette.textOnFillLight) ? palette.textOnFillDark : palette.textOnFillLight;
1543
+ const haloColor = color === palette.textOnFillLight ? palette.textOnFillDark : palette.textOnFillLight;
1544
+ return {
1545
+ color,
1546
+ halo: contrastRatio(fill, color) < REGION_LABEL_HALO_RATIO,
1547
+ haloColor
1548
+ };
1549
+ };
1550
+ const reliefAllowed = resolved.directives.noRelief !== true;
1551
+ const relief = [];
1552
+ let reliefHatch = null;
1553
+ if (reliefAllowed && data.mountainRanges) {
1554
+ for (const [, f] of decodeLayer(data.mountainRanges)) {
1555
+ const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
1556
+ if (!viewF) continue;
1557
+ const area = path.area(viewF);
1558
+ if (!Number.isFinite(area) || area < RELIEF_MIN_AREA) continue;
1559
+ const box = path.bounds(viewF);
1560
+ if (box[1][0] - box[0][0] < RELIEF_MIN_DIM || box[1][1] - box[0][1] < RELIEF_MIN_DIM)
1561
+ continue;
1562
+ const d = path(viewF) ?? "";
1563
+ if (!d) continue;
1564
+ relief.push({ d });
1565
+ }
1566
+ if (relief.length) {
1567
+ const darkTone = isDark ? palette.bg : palette.text;
1568
+ const lightTone = isDark ? palette.text : palette.bg;
1569
+ const reliefLandRef = colorizeActive ? isDark ? palette.surface : palette.bg : neutralFill;
1570
+ const landLum = relativeLuminance(reliefLandRef);
1571
+ const tone = Math.abs(landLum - relativeLuminance(darkTone)) > 0.04 ? darkTone : lightTone;
1572
+ reliefHatch = {
1573
+ color: mix(tone, reliefLandRef, RELIEF_HATCH_STRENGTH),
1574
+ spacing: RELIEF_HATCH_SPACING,
1575
+ width: RELIEF_HATCH_WIDTH
1576
+ };
1577
+ }
1578
+ }
1579
+ let coastlineStyle = null;
1580
+ if (resolved.directives.noCoastline !== true) {
1581
+ const minDim = Math.min(width, height);
1582
+ coastlineStyle = {
1583
+ color: mix(regionStroke, water, COASTLINE_STROKE_MIX),
1584
+ // N equal-width rings: distance steps outward by COASTLINE_STEP; opacity
1585
+ // fades linearly from NEAR (innermost) to FAR (outermost).
1586
+ lines: Array.from({ length: COASTLINE_RING_COUNT }, (_, k) => ({
1587
+ d: (COASTLINE_D0 + k * COASTLINE_STEP) * minDim,
1588
+ thickness: COASTLINE_THICKNESS * minDim,
1589
+ opacity: COASTLINE_OPACITY_NEAR + (COASTLINE_OPACITY_FAR - COASTLINE_OPACITY_NEAR) * k / (COASTLINE_RING_COUNT - 1)
1590
+ })),
1591
+ minExtent: (isGlobalView ? COASTLINE_MIN_EXTENT_GLOBAL : COASTLINE_MIN_EXTENT) * minDim
1592
+ };
1593
+ }
1594
+ const riverColor = mix(palette.colors.blue, water, 32);
1595
+ const rivers = [];
1596
+ if (data.rivers) {
1597
+ for (const [, f] of decodeLayer(data.rivers)) {
1598
+ const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
1599
+ if (!viewF) continue;
1600
+ const d = path(viewF) ?? "";
1601
+ if (!d) continue;
1602
+ rivers.push({ d, color: riverColor, width: RIVER_WIDTH });
1603
+ }
1604
+ }
1605
+ const sizeVals = resolved.pois.map((p) => Number(p.meta["size"])).filter((n) => Number.isFinite(n) && n > 0);
1606
+ const sizeMin = sizeVals.length ? Math.min(...sizeVals) : 0;
1607
+ const sizeMax = sizeVals.length ? Math.max(...sizeVals) : 0;
1608
+ const radiusFor = (p) => {
1609
+ const v = Number(p.meta["size"]);
1610
+ if (!Number.isFinite(v) || v <= 0 || sizeMax <= 0) return R_DEFAULT;
1611
+ const t = sizeMax > sizeMin ? (Math.sqrt(v) - Math.sqrt(sizeMin)) / (Math.sqrt(sizeMax) - Math.sqrt(sizeMin)) : 1;
1612
+ return R_MIN + Math.max(0, Math.min(1, t)) * (R_MAX - R_MIN);
1613
+ };
1614
+ const fillOpacityFor = (r) => {
1615
+ const t = Math.max(0, Math.min(1, (r - R_MIN) / (R_MAX - R_MIN)));
1616
+ return POI_FILL_OPACITY_MAX - t * (POI_FILL_OPACITY_MAX - POI_FILL_OPACITY_MIN);
1617
+ };
1618
+ const poiFill = (p) => {
1619
+ const directHex = p.color ? resolveColor(p.color, palette) : null;
1620
+ if (directHex)
1621
+ return { fill: directHex, stroke: mix(directHex, palette.text, 18) };
1622
+ for (const group of resolved.tagGroups) {
1623
+ const val = p.tags[tagAttrKey(group.name)];
1624
+ if (!val) continue;
1625
+ const entry = group.entries.find(
1626
+ (e) => e.value.toLowerCase() === val.toLowerCase()
1627
+ );
1628
+ const hex = entry?.color;
1629
+ if (hex) return { fill: hex, stroke: mix(hex, palette.text, 18) };
1630
+ }
1631
+ return {
1632
+ fill: palette.colors.orange,
1633
+ stroke: mix(palette.colors.orange, palette.text, 18)
1634
+ };
1635
+ };
1636
+ const lineColor = (tags) => {
1637
+ for (const group of resolved.tagGroups) {
1638
+ const val = tags[tagAttrKey(group.name)];
1639
+ if (!val) continue;
1640
+ const entry = group.entries.find(
1641
+ (e) => e.value.toLowerCase() === val.toLowerCase()
1642
+ );
1643
+ if (entry?.color) return entry.color;
1644
+ }
1645
+ return null;
1646
+ };
1647
+ const routeNumberById = /* @__PURE__ */ new Map();
1648
+ const originIds = /* @__PURE__ */ new Set();
1649
+ for (const rt of resolved.routes) {
1650
+ rt.stopIds.forEach((id, i) => {
1651
+ if (i === 0) originIds.add(id);
1652
+ if (!routeNumberById.has(id)) routeNumberById.set(id, i + 1);
1653
+ });
1654
+ }
1655
+ const poiScreen = /* @__PURE__ */ new Map();
1656
+ const pois = [];
1657
+ const orderedPois = [...resolved.pois].sort(
1658
+ (a, b) => a.lineNumber - b.lineNumber || (a.id < b.id ? -1 : 1)
1659
+ );
1660
+ const projected = [];
1661
+ for (const p of orderedPois) {
1662
+ const xy = project(p.lon, p.lat);
1663
+ if (xy) projected.push({ p, xy });
1664
+ }
1665
+ const placePoi = (e, cx, cy, clusterId) => {
1666
+ const { fill, stroke } = poiFill(e.p);
1667
+ const r = radiusFor(e.p);
1668
+ poiScreen.set(e.p.id, { cx, cy, r });
1669
+ const num = routeNumberById.get(e.p.id);
1670
+ pois.push({
1671
+ id: e.p.id,
1672
+ cx,
1673
+ cy,
1674
+ r,
1675
+ fill,
1676
+ fillOpacity: fillOpacityFor(r),
1677
+ stroke,
1678
+ lineNumber: e.p.lineNumber,
1679
+ implicit: !!e.p.implicit,
1680
+ isOrigin: originIds.has(e.p.id),
1681
+ ...num !== void 0 && { routeNumber: num },
1682
+ ...Object.keys(e.p.tags).length > 0 && { tags: e.p.tags },
1683
+ ...clusterId !== void 0 && { clusterId }
1684
+ });
1685
+ };
1686
+ const clusters = [];
1687
+ const connected = /* @__PURE__ */ new Set();
1688
+ for (const e of resolved.edges) {
1689
+ connected.add(e.fromId);
1690
+ connected.add(e.toId);
1691
+ }
1692
+ for (const rt of resolved.routes) {
1693
+ rt.stopIds.forEach((id) => connected.add(id));
1694
+ }
1695
+ const radiusOf = (e) => radiusFor(e.p);
1696
+ for (const e of projected) {
1697
+ if (connected.has(e.p.id)) placePoi(e, e.xy[0], e.xy[1]);
1698
+ }
1699
+ const groups = [];
1700
+ for (const e of projected) {
1701
+ if (connected.has(e.p.id)) continue;
1702
+ const r = radiusOf(e);
1703
+ const near = groups.find(
1704
+ (g) => g.some(
1705
+ (q) => Math.hypot(q.xy[0] - e.xy[0], q.xy[1] - e.xy[1]) < (r + radiusOf(q)) * STACK_OVERLAP
1706
+ )
1707
+ );
1708
+ if (near) near.push(e);
1709
+ else groups.push([e]);
1710
+ }
1711
+ for (const g of groups) {
1712
+ if (g.length === 1) {
1713
+ placePoi(g[0], g[0].xy[0], g[0].xy[1]);
1714
+ continue;
1715
+ }
1716
+ const clusterId = g[0].p.id;
1717
+ const cx0 = g.reduce((s, e) => s + e.xy[0], 0) / g.length;
1718
+ const cy0 = g.reduce((s, e) => s + e.xy[1], 0) / g.length;
1719
+ const maxR = Math.max(...g.map(radiusOf));
1720
+ const sep = 2 * maxR + STACK_RING_GAP;
1721
+ const ringR = Math.max(
1722
+ COLO_R,
1723
+ sep / (2 * Math.sin(Math.PI / Math.max(g.length, 2)))
1724
+ );
1725
+ const positions = g.map((e, i) => {
1726
+ if (g.length <= STACK_RING_MAX) {
1727
+ const ang2 = -Math.PI / 2 + i * 2 * Math.PI / g.length;
1728
+ return {
1729
+ e,
1730
+ mx: cx0 + Math.cos(ang2) * ringR,
1731
+ my: cy0 + Math.sin(ang2) * ringR
1732
+ };
1733
+ }
1734
+ const ang = i * GOLDEN_ANGLE;
1735
+ const rr = ringR * Math.sqrt((i + 1) / g.length);
1736
+ return { e, mx: cx0 + Math.cos(ang) * rr, my: cy0 + Math.sin(ang) * rr };
1737
+ });
1738
+ let minX = cx0 - maxR;
1739
+ let maxX = cx0 + maxR;
1740
+ let minY = cy0 - maxR;
1741
+ let maxY = cy0 + maxR;
1742
+ for (const { mx, my, e } of positions) {
1743
+ const r = radiusOf(e);
1744
+ minX = Math.min(minX, mx - r);
1745
+ maxX = Math.max(maxX, mx + r);
1746
+ minY = Math.min(minY, my - r);
1747
+ maxY = Math.max(maxY, my + r);
1748
+ }
1749
+ let dx = 0;
1750
+ let dy = 0;
1751
+ if (minX + dx < 2) dx = 2 - minX;
1752
+ if (maxX + dx > width - 2) dx = width - 2 - maxX;
1753
+ if (minY + dy < 2) dy = 2 - minY;
1754
+ if (maxY + dy > height - 2) dy = height - 2 - maxY;
1755
+ const legsOut = [];
1756
+ for (const { e, mx, my } of positions) {
1757
+ const fx = mx + dx;
1758
+ const fy = my + dy;
1759
+ placePoi(e, fx, fy, clusterId);
1760
+ legsOut.push({ x2: fx, y2: fy, color: poiFill(e.p).fill });
1761
+ }
1762
+ clusters.push({
1763
+ id: clusterId,
1764
+ cx: cx0 + dx,
1765
+ cy: cy0 + dy,
1766
+ count: g.length,
1767
+ hitR: ringR + maxR + 6,
1768
+ legs: legsOut
1769
+ });
1770
+ }
1771
+ const legs = [];
1772
+ const RIM_GAP = 1.5;
1773
+ const bowMagnitude = (mx, my, nx, ny, offset, len, away) => {
1774
+ if (offset !== 0) return offset;
1775
+ const base = len * ARC_CURVE_FRAC;
1776
+ if (!away) return base;
1777
+ const dot = nx * (mx - away.x) + ny * (my - away.y);
1778
+ return dot < 0 ? -base : base;
1779
+ };
1780
+ const legPath = (a, b, curved, offset, away) => {
1781
+ const mx = (a.cx + b.cx) / 2;
1782
+ const my = (a.cy + b.cy) / 2;
1783
+ const dx = b.cx - a.cx;
1784
+ const dy = b.cy - a.cy;
1785
+ const len = Math.hypot(dx, dy) || 1;
1786
+ const trimA = Math.min(a.r + RIM_GAP, len * 0.45);
1787
+ const trimB = Math.min(b.r + RIM_GAP, len * 0.45);
1788
+ if (!curved && offset === 0) {
1789
+ const ux = dx / len;
1790
+ const uy = dy / len;
1791
+ const ax2 = a.cx + ux * trimA;
1792
+ const ay2 = a.cy + uy * trimA;
1793
+ const bx2 = b.cx - ux * trimB;
1794
+ const by2 = b.cy - uy * trimB;
1795
+ return `M${ax2},${ay2}L${bx2},${by2}`;
1796
+ }
1797
+ const nx = -dy / len;
1798
+ const ny = dx / len;
1799
+ const bow = bowMagnitude(mx, my, nx, ny, offset, len, away);
1800
+ const px = mx + nx * bow;
1801
+ const py = my + ny * bow;
1802
+ const ta = Math.hypot(px - a.cx, py - a.cy) || 1;
1803
+ const tb = Math.hypot(b.cx - px, b.cy - py) || 1;
1804
+ const ax = a.cx + (px - a.cx) / ta * trimA;
1805
+ const ay = a.cy + (py - a.cy) / ta * trimA;
1806
+ const bx = b.cx - (b.cx - px) / tb * trimB;
1807
+ const by = b.cy - (b.cy - py) / tb * trimB;
1808
+ return `M${ax},${ay}Q${px},${py} ${bx},${by}`;
1809
+ };
1810
+ const legLabelPoint = (a, b, curved, offset, away) => {
1811
+ const mx = (a.cx + b.cx) / 2;
1812
+ const my = (a.cy + b.cy) / 2;
1813
+ if (!curved && offset === 0) return { x: mx, y: my - 4 };
1814
+ const dx = b.cx - a.cx;
1815
+ const dy = b.cy - a.cy;
1816
+ const len = Math.hypot(dx, dy) || 1;
1817
+ const nx = -dy / len;
1818
+ const ny = dx / len;
1819
+ const bow = bowMagnitude(mx, my, nx, ny, offset, len, away);
1820
+ const off = bow * 0.5 + Math.sign(bow || 1) * 8;
1821
+ return { x: mx + nx * off, y: my + ny * off };
1822
+ };
1823
+ const routeLegVals = resolved.routes.flatMap((rt) => rt.legs).map((l) => Number(l.value)).filter((n) => Number.isFinite(n) && n > 0);
1824
+ const rlMin = routeLegVals.length ? Math.min(...routeLegVals) : 0;
1825
+ const rlMax = routeLegVals.length ? Math.max(...routeLegVals) : 0;
1826
+ const routeWidthFor = (v) => {
1827
+ if (!Number.isFinite(v) || v <= 0 || rlMax <= 0) return W_MIN;
1828
+ const t = rlMax > rlMin ? (v - rlMin) / (rlMax - rlMin) : 1;
1829
+ return W_MIN + t * (W_MAX - W_MIN);
1830
+ };
1831
+ for (const rt of resolved.routes) {
1832
+ const stopPts = rt.stopIds.map((id) => poiScreen.get(id)).filter((p) => !!p);
1833
+ const center = stopPts.length >= 3 ? {
1834
+ x: stopPts.reduce((s, p) => s + p.cx, 0) / stopPts.length,
1835
+ y: stopPts.reduce((s, p) => s + p.cy, 0) / stopPts.length
1836
+ } : void 0;
1837
+ for (const leg of rt.legs) {
1838
+ const a = poiScreen.get(leg.fromId);
1839
+ const b = poiScreen.get(leg.toId);
1840
+ if (!a || !b) continue;
1841
+ const lp = legLabelPoint(a, b, leg.style === "arc", 0, center);
1842
+ const bow = {
1843
+ curved: leg.style === "arc",
1844
+ offset: 0,
1845
+ center,
1846
+ labelX: lp.x,
1847
+ labelY: lp.y
1848
+ };
1849
+ const routeLabelStyle = leg.label !== void 0 ? labelOnFill(fillAt(bow.labelX, bow.labelY)) : void 0;
1850
+ const routeVal = Number(leg.value);
1851
+ legs.push({
1852
+ d: legPath(a, b, bow.curved, bow.offset, bow.center),
1853
+ width: routeWidthFor(routeVal),
1854
+ color: lineColor(leg.tags) ?? mix(palette.text, palette.bg, 72),
1855
+ arrow: true,
1856
+ fromId: leg.fromId,
1857
+ toId: leg.toId,
1858
+ ...Number.isFinite(routeVal) && routeVal > 0 && { value: routeVal },
1859
+ ...Object.keys(leg.tags).length > 0 && { tags: leg.tags },
1860
+ lineNumber: leg.lineNumber,
1861
+ ...leg.label !== void 0 && {
1862
+ label: leg.label,
1863
+ labelX: bow.labelX,
1864
+ labelY: bow.labelY,
1865
+ labelColor: routeLabelStyle.color,
1866
+ labelHalo: routeLabelStyle.halo,
1867
+ labelHaloColor: routeLabelStyle.haloColor
1868
+ }
1869
+ });
1870
+ }
1871
+ }
1872
+ const weightVals = resolved.edges.map((e) => Number(e.meta["width"])).filter((n) => Number.isFinite(n) && n > 0);
1873
+ const wMin = weightVals.length ? Math.min(...weightVals) : 0;
1874
+ const wMax = weightVals.length ? Math.max(...weightVals) : 0;
1875
+ const widthFor = (e) => {
1876
+ const v = Number(e.meta["width"]);
1877
+ if (!Number.isFinite(v) || v <= 0 || wMax <= 0) return W_MIN;
1878
+ const t = wMax > wMin ? (v - wMin) / (wMax - wMin) : 1;
1879
+ return W_MIN + t * (W_MAX - W_MIN);
1880
+ };
1881
+ const pairGroups = /* @__PURE__ */ new Map();
1882
+ for (const e of resolved.edges) {
1883
+ const key = [e.fromId, e.toId].sort().join(" ");
1884
+ const arr = pairGroups.get(key);
1885
+ if (arr) arr.push(e);
1886
+ else pairGroups.set(key, [e]);
1887
+ }
1888
+ for (const groupEdges of pairGroups.values()) {
1889
+ const ordered = [...groupEdges].sort((a, b) => a.lineNumber - b.lineNumber);
1890
+ const n = ordered.length;
1891
+ ordered.forEach((e, i) => {
1892
+ const a = poiScreen.get(e.fromId);
1893
+ const b = poiScreen.get(e.toId);
1894
+ if (!a || !b) return;
1895
+ const fanOffset = n > 1 ? (i - (n - 1) / 2) * FAN_STEP : 0;
1896
+ const curved = e.style === "arc" || n > 1;
1897
+ const lp = legLabelPoint(a, b, curved, fanOffset);
1898
+ const bow = {
1899
+ curved,
1900
+ offset: fanOffset,
1901
+ labelX: lp.x,
1902
+ labelY: lp.y
1903
+ };
1904
+ const edgeLabelStyle = e.label !== void 0 ? labelOnFill(fillAt(bow.labelX, bow.labelY)) : void 0;
1905
+ const edgeVal = Number(e.meta["width"]);
1906
+ legs.push({
1907
+ d: legPath(a, b, bow.curved, bow.offset),
1908
+ width: widthFor(e),
1909
+ color: lineColor(e.tags) ?? mix(palette.text, palette.bg, 66),
1910
+ arrow: e.directed,
1911
+ fromId: e.fromId,
1912
+ toId: e.toId,
1913
+ ...Number.isFinite(edgeVal) && edgeVal > 0 && { value: edgeVal },
1914
+ ...Object.keys(e.tags).length > 0 && { tags: e.tags },
1915
+ lineNumber: e.lineNumber,
1916
+ ...e.label !== void 0 && {
1917
+ label: e.label,
1918
+ labelX: bow.labelX,
1919
+ labelY: bow.labelY,
1920
+ labelColor: edgeLabelStyle.color,
1921
+ labelHalo: edgeLabelStyle.halo,
1922
+ labelHaloColor: edgeLabelStyle.haloColor
1923
+ }
1924
+ });
1925
+ });
1926
+ }
1927
+ const labels = [];
1928
+ const obstacles = [...topReserved];
1929
+ const regionLabelGuards = [];
1930
+ const markers = pois.map((p) => ({
1931
+ cx: p.cx,
1932
+ cy: p.cy,
1933
+ r: p.r
1934
+ }));
1935
+ const legSegments = [];
1936
+ for (const leg of legs) {
1937
+ const m = /^M(-?[\d.]+),(-?[\d.]+)(?:L(-?[\d.]+),(-?[\d.]+)|Q(-?[\d.]+),(-?[\d.]+) (-?[\d.]+),(-?[\d.]+))$/.exec(
1938
+ leg.d
1939
+ );
1940
+ if (!m) continue;
1941
+ const x0 = +m[1];
1942
+ const y0 = +m[2];
1943
+ if (m[3] !== void 0) {
1944
+ legSegments.push([x0, y0, +m[3], +m[4]]);
1945
+ } else {
1946
+ const cx = +m[5];
1947
+ const cy = +m[6];
1948
+ const ex = +m[7];
1949
+ const ey = +m[8];
1950
+ const N = 8;
1951
+ let px = x0;
1952
+ let py = y0;
1953
+ for (let i = 1; i <= N; i++) {
1954
+ const t = i / N;
1955
+ const u = 1 - t;
1956
+ const qx = u * u * x0 + 2 * u * t * cx + t * t * ex;
1957
+ const qy = u * u * y0 + 2 * u * t * cy + t * t * ey;
1958
+ legSegments.push([px, py, qx, qy]);
1959
+ px = qx;
1960
+ py = qy;
1961
+ }
1962
+ }
1963
+ }
1964
+ const collides = (rect) => markers.some((m) => rectCircleOverlap(rect, m)) || obstacles.some((o) => rectsOverlap(rect, o)) || legSegments.some((s) => segmentRectOverlap(s[0], s[1], s[2], s[3], rect));
1965
+ const showRegionLabels = resolved.directives.noRegionLabels !== true;
1966
+ const labeledRegionIds = /* @__PURE__ */ new Set();
1967
+ const showRegionValues = resolved.directives.noRegionValue !== true && activeIsScore;
1968
+ const regionValueStr = (value) => showRegionValues && value !== void 0 ? compactNumber(value) : void 0;
1969
+ const isCompact = width < COMPACT_WIDTH_PX;
1970
+ const usChoroplethZoom = resolved.projection === "mercator" && resolved.basemaps.subdivisions.includes("us-states") && activeIsScore;
1971
+ const LABEL_PADX = 6;
1972
+ const LABEL_PADY = 3;
1973
+ const VALUE_GAP = 1;
1974
+ const labelW = (text, font = FONT2) => measureLegendText(text, font) + 2 * LABEL_PADX;
1975
+ const labelH = FONT2 + 2 * LABEL_PADY;
1976
+ const stackW = (text, valueText, font = FONT2) => Math.max(
1977
+ labelW(text, font),
1978
+ valueText ? measureLegendText(valueText, Math.round(font * 0.82)) + 2 * LABEL_PADX : 0
1979
+ );
1980
+ const stackH = (hasValue, font = FONT2) => {
1981
+ const lh = font + 2 * LABEL_PADY;
1982
+ return hasValue ? lh + VALUE_GAP + Math.round(font * 0.82) : lh;
1983
+ };
1984
+ const REGION_FONT_MAX_ORIENT = 22;
1985
+ const REGION_SIZE_FRAC_MIN = 0.06;
1986
+ const REGION_SIZE_FRAC_MAX = 0.32;
1987
+ const SUBJECT_FONT_MAX = 18;
1988
+ const SUBJECT_MIN_PROMINENCE = 13;
1989
+ const SUBJECT_LEADER_FONT = 15;
1990
+ const canvasLinear = Math.sqrt(Math.max(1, width * height));
1991
+ const sizeT = (boxW, boxH) => {
1992
+ const frac = Math.sqrt(Math.max(0, boxW * boxH)) / canvasLinear;
1993
+ return Math.min(
1994
+ 1,
1995
+ Math.max(
1996
+ 0,
1997
+ (frac - REGION_SIZE_FRAC_MIN) / (REGION_SIZE_FRAC_MAX - REGION_SIZE_FRAC_MIN)
1998
+ )
1999
+ );
2000
+ };
2001
+ const pushRegionLabel = (x, y, text, fill, lineNumber, valueLine, fontSize = FONT2, colorOverride) => {
2002
+ const { color: baseColor, haloColor } = labelOnFill(fill);
2003
+ const color = colorOverride ?? baseColor;
2004
+ const vf = Math.round(fontSize * 0.82);
2005
+ const halfW = Math.max(
2006
+ measureLegendText(text, fontSize),
2007
+ valueLine ? measureLegendText(valueLine, vf) : 0
2008
+ ) / 2;
2009
+ const overflows = [y - fontSize * 0.55, y - fontSize * 0.1].some(
2010
+ (sy) => fillAt(x - halfW, sy) !== fill || fillAt(x + halfW, sy) !== fill
2011
+ );
2012
+ labels.push({
2013
+ x,
2014
+ y,
2015
+ text,
2016
+ anchor: "middle",
2017
+ color,
2018
+ // A backdrop label (colorOverride) NEVER haloes: the placement gate above
2019
+ // guarantees it sits on a floored-readable substrate (own fill, neutral
2020
+ // land, foreign land, or water), so a halo is pure noise. Data labels keep
2021
+ // the containment-gated halo until the role-tier pass (Phase 3).
2022
+ halo: colorOverride ? false : overflows,
2023
+ haloColor,
2024
+ ...fontSize !== FONT2 && { fontSize },
2025
+ ...valueLine !== void 0 && { valueLine },
2026
+ lineNumber
2027
+ });
2028
+ };
2029
+ const REGION_LABEL_GAP = 2;
2030
+ const regionLabelRect = (cx, cy, text, valueText, font = FONT2) => {
2031
+ const vf = Math.round(font * 0.82);
2032
+ const w = Math.max(
2033
+ measureLegendText(text, font),
2034
+ valueText ? measureLegendText(valueText, vf) : 0
2035
+ ) + 2 * REGION_LABEL_GAP;
2036
+ const h = valueText ? font + VALUE_GAP + vf : font;
2037
+ return { x: cx - w / 2, y: cy - h / 2, w, h };
2038
+ };
2039
+ if (showRegionLabels) {
2040
+ const frameContainers = new Set(resolved.poiFrameContainers);
2041
+ const entries = regions.map((r) => {
2042
+ const isContainer = frameContainers.has(r.id);
2043
+ if (r.layer === "base" && !isContainer || r.label === void 0)
2044
+ return null;
2045
+ const isUsState = r.layer === "us-state" || r.id.startsWith("US-");
2046
+ const f = isUsState ? usLayer?.get(r.id) : worldLayer.get(r.id);
2047
+ if (!f) return null;
2048
+ const [[x0, y0], [x1, y1]] = path.bounds(f);
2049
+ const boxW = x1 - x0;
2050
+ const boxH = y1 - y0;
2051
+ const abbrev = isUsState && !usChoroplethZoom ? r.id.replace(/^US-/, "") : void 0;
2052
+ const candidates = abbrev !== void 0 ? isCompact ? [abbrev, r.label] : isContainer ? [r.label] : [r.label, abbrev] : [r.label];
2053
+ const anchor = !isUsState ? WORLD_LABEL_ANCHORS[r.id] : void 0;
2054
+ const c = anchor ? project(anchor[0], anchor[1]) : path.centroid(f);
2055
+ if (!c || !Number.isFinite(c[0])) return null;
2056
+ return { r, c, boxW, boxH, area: boxW * boxH, candidates };
2057
+ }).filter((e) => e !== null).sort((a, b) => b.area - a.area || a.r.lineNumber - b.r.lineNumber);
2058
+ const placedRegionRects = [...topReserved];
2059
+ const placedLeaders = [];
2060
+ const isEmptyFill = (f) => f === water || f === neutralFill || f === foreignFill;
2061
+ const POI_LABEL_PAD = 14;
2062
+ const poiObstacles = pois.map((p) => ({
2063
+ x: p.cx - p.r - POI_LABEL_PAD,
2064
+ y: p.cy - p.r - POI_LABEL_PAD,
2065
+ w: 2 * (p.r + POI_LABEL_PAD),
2066
+ h: 2 * (p.r + POI_LABEL_PAD)
2067
+ }));
2068
+ const SHORT_HOP_MAX = Math.max(46, Math.min(width, height) * 0.11);
2069
+ const STEP = 4;
2070
+ const HOP_GAP = 9;
2071
+ const CLEAR_STEP = 6;
2072
+ const CLEAR_MAX = SHORT_HOP_MAX * 3;
2073
+ const CLEAR_TOL = CLEAR_STEP;
2074
+ const clearanceAt = (px, py, ox, oy) => {
2075
+ const baseAng = Math.atan2(oy, ox);
2076
+ let minClear = CLEAR_MAX;
2077
+ for (let k = -2; k <= 2; k++) {
2078
+ const a = baseAng + k * Math.PI / 4;
2079
+ const rx = Math.cos(a);
2080
+ const ry = Math.sin(a);
2081
+ let d = CLEAR_STEP;
2082
+ for (; d <= CLEAR_MAX; d += CLEAR_STEP) {
2083
+ const qx = px + rx * d;
2084
+ const qy = py + ry * d;
2085
+ if (qx < FIT_PAD || qy < topPad || qx > width - FIT_PAD || qy > height - FIT_PAD)
2086
+ break;
2087
+ if (fillAt(qx, qy) !== water) break;
2088
+ }
2089
+ if (d < minClear) minClear = d;
2090
+ }
2091
+ return minClear;
2092
+ };
2093
+ const tryShortHopCallout = (cx, cy, fill, name, value, font = FONT2, gap = HOP_GAP, maxLen = SHORT_HOP_MAX) => {
2094
+ const chipW = stackW(name, value, font);
2095
+ const chipH = stackH(value !== void 0, font);
2096
+ const D = Math.SQRT1_2;
2097
+ const dirs = [
2098
+ [0, -1],
2099
+ // north
2100
+ [0, 1],
2101
+ // south
2102
+ [1, 0],
2103
+ // east
2104
+ [-1, 0],
2105
+ // west
2106
+ [D, -D],
2107
+ // NE
2108
+ [D, D],
2109
+ // SE
2110
+ [-D, -D],
2111
+ // NW
2112
+ [-D, D]
2113
+ // SW
2114
+ ];
2115
+ let best = null;
2116
+ for (const [dx, dy] of dirs) {
2117
+ let ex = cx;
2118
+ let ey = cy;
2119
+ let steps = 0;
2120
+ const maxSteps = Math.ceil(maxLen / STEP) + 1;
2121
+ while (steps < maxSteps && fillAt(ex, ey) === fill) {
2122
+ ex += dx * STEP;
2123
+ ey += dy * STEP;
2124
+ steps++;
2125
+ }
2126
+ if (fillAt(ex, ey) === fill) continue;
2127
+ const halfAlong = (Math.abs(dx) * chipW + Math.abs(dy) * chipH) / 2;
2128
+ const ccx = ex + dx * (gap + halfAlong);
2129
+ const ccy = ey + dy * (gap + halfAlong);
2130
+ const rect = {
2131
+ x: ccx - chipW / 2,
2132
+ y: ccy - chipH / 2,
2133
+ w: chipW,
2134
+ h: chipH
2135
+ };
2136
+ if (rect.x < FIT_PAD || rect.y < topPad || rect.x + rect.w > width - FIT_PAD || rect.y + rect.h > height - FIT_PAD)
2137
+ continue;
2138
+ const sx = [rect.x + 2, ccx, rect.x + rect.w - 2];
2139
+ const sy = [rect.y + 2, ccy, rect.y + rect.h - 2];
2140
+ let onEmpty = true;
2141
+ for (const px of sx)
2142
+ for (const py of sy)
2143
+ if (!isEmptyFill(fillAt(px, py))) {
2144
+ onEmpty = false;
2145
+ break;
2146
+ }
2147
+ if (!onEmpty) continue;
2148
+ if (placedRegionRects.some((p) => rectsOverlap(rect, p))) continue;
2149
+ if (poiObstacles.some((o) => rectsOverlap(rect, o))) continue;
2150
+ if (markers.some((m) => rectCircleOverlap(rect, m))) continue;
2151
+ const innerX = ccx - dx * (chipW / 2);
2152
+ const innerY = ccy - dy * (chipH / 2);
2153
+ const len = Math.hypot(innerX - cx, innerY - cy);
2154
+ if (len > maxLen) continue;
2155
+ if (placedLeaders.some(
2156
+ (l) => segmentsCross(cx, cy, innerX, innerY, l[0], l[1], l[2], l[3])
2157
+ ))
2158
+ continue;
2159
+ if (placedRegionRects.some(
2160
+ (p) => segmentRectOverlap(cx, cy, innerX, innerY, p)
2161
+ ))
2162
+ continue;
2163
+ const clear = clearanceAt(ccx, ccy, dx, dy);
2164
+ const better = best === null || clear > best.clear + CLEAR_TOL || clear >= best.clear - CLEAR_TOL && len < best.len;
2165
+ if (better)
2166
+ best = {
2167
+ x: ccx,
2168
+ y: ccy,
2169
+ rect,
2170
+ leader: [cx, cy, innerX, innerY],
2171
+ len,
2172
+ clear
2173
+ };
2174
+ }
2175
+ return best ? { x: best.x, y: best.y, rect: best.rect, leader: best.leader } : null;
2176
+ };
2177
+ for (const { r, c, boxW, boxH, candidates } of entries) {
2178
+ const valStr = regionValueStr(r.value);
2179
+ const fitsRegions = (rect) => !placedRegionRects.some((p) => rectsOverlap(rect, p));
2180
+ const fitsPois = (rect) => !poiObstacles.some((o) => rectsOverlap(rect, o));
2181
+ const isSubject = regionById.has(r.id);
2182
+ const extremesOnFill = (ax, ay, text) => {
2183
+ const halfW = measureLegendText(text, FONT2) / 2;
2184
+ return fillAt(ax - halfW, ay) === r.fill && fillAt(ax + halfW, ay) === r.fill;
2185
+ };
2186
+ if (isSubject && valStr === void 0 && r.label !== void 0) {
2187
+ const name = candidates[0];
2188
+ let best = -1;
2189
+ for (let f = SUBJECT_FONT_MAX; f >= FONT2; f--) {
2190
+ if (measureLegendText(name, f) > boxW || f + 2 * LABEL_PADY > boxH)
2191
+ continue;
2192
+ const rect = regionLabelRect(c[0], c[1], name, void 0, f);
2193
+ if (!fitsRegions(rect) || !fitsPois(rect) || collides(rect)) continue;
2194
+ const halfW = measureLegendText(name, f) / 2;
2195
+ if (fillAt(c[0] - halfW, c[1]) !== r.fill || fillAt(c[0] + halfW, c[1]) !== r.fill)
2196
+ continue;
2197
+ best = f;
2198
+ break;
2199
+ }
2200
+ if (best >= SUBJECT_MIN_PROMINENCE) {
2201
+ const rect = regionLabelRect(c[0], c[1], name, void 0, best);
2202
+ placedRegionRects.push(rect);
2203
+ pushRegionLabel(
2204
+ c[0],
2205
+ c[1],
2206
+ name,
2207
+ r.fill,
2208
+ r.lineNumber,
2209
+ void 0,
2210
+ best
2211
+ );
2212
+ labeledRegionIds.add(r.id);
2213
+ regionLabelGuards.push({ label: labels[labels.length - 1], rect });
2214
+ } else {
2215
+ const hop = tryShortHopCallout(
2216
+ c[0],
2217
+ c[1],
2218
+ r.fill,
2219
+ r.label,
2220
+ void 0,
2221
+ SUBJECT_LEADER_FONT,
2222
+ HOP_GAP + SUBJECT_LEADER_FONT,
2223
+ SHORT_HOP_MAX * 2.2
2224
+ );
2225
+ if (hop) {
2226
+ placedRegionRects.push(hop.rect);
2227
+ placedLeaders.push(hop.leader);
2228
+ const dark = mix(r.fill, palette.text, 60);
2229
+ labels.push({
2230
+ x: hop.x,
2231
+ y: hop.y,
2232
+ text: r.label,
2233
+ anchor: "middle",
2234
+ color: palette.text,
2235
+ halo: false,
2236
+ haloColor: palette.bg,
2237
+ fontSize: SUBJECT_LEADER_FONT,
2238
+ leader: {
2239
+ x1: hop.leader[0],
2240
+ y1: hop.leader[1],
2241
+ x2: hop.leader[2],
2242
+ y2: hop.leader[3]
2243
+ },
2244
+ leaderColor: dark,
2245
+ calloutDot: { x: c[0], y: c[1], color: dark },
2246
+ lineNumber: r.lineNumber
2247
+ });
2248
+ labeledRegionIds.add(r.id);
2249
+ }
2250
+ }
2251
+ continue;
2252
+ }
2253
+ const seekAnchors = [
2254
+ { x: c[0], y: c[1], guard: false }
2255
+ ];
2256
+ if (valStr) {
2257
+ seekAnchors.push(
2258
+ { x: c[0], y: c[1] + boxH * 0.26, guard: true },
2259
+ { x: c[0], y: c[1] - boxH * 0.26, guard: true },
2260
+ { x: c[0] + boxW * 0.26, y: c[1], guard: true },
2261
+ { x: c[0] - boxW * 0.26, y: c[1], guard: true },
2262
+ { x: c[0] + boxW * 0.22, y: c[1] + boxH * 0.22, guard: true },
2263
+ { x: c[0] - boxW * 0.22, y: c[1] + boxH * 0.22, guard: true },
2264
+ { x: c[0] + boxW * 0.22, y: c[1] - boxH * 0.22, guard: true },
2265
+ { x: c[0] - boxW * 0.22, y: c[1] - boxH * 0.22, guard: true }
2266
+ );
2267
+ }
2268
+ let chosen;
2269
+ for (const a of seekAnchors) {
2270
+ if (a.guard && fillAt(a.x, a.y) !== r.fill) continue;
2271
+ for (const t of candidates) {
2272
+ const nameRect = regionLabelRect(a.x, a.y, t);
2273
+ if (valStr && stackW(t, valStr) <= boxW && stackH(true) <= boxH) {
2274
+ const stackRect = regionLabelRect(a.x, a.y, t, valStr);
2275
+ if (fitsRegions(stackRect) && fitsPois(nameRect) && extremesOnFill(a.x, a.y, t)) {
2276
+ chosen = { text: t, valueLine: valStr, ax: a.x, ay: a.y };
2277
+ break;
2278
+ }
2279
+ }
2280
+ if (labelW(t) <= boxW && labelH <= boxH) {
2281
+ if (fitsRegions(nameRect) && fitsPois(nameRect) && (valStr === void 0 || extremesOnFill(a.x, a.y, t))) {
2282
+ chosen = { text: t, ax: a.x, ay: a.y };
2283
+ break;
2284
+ }
2285
+ }
2286
+ }
2287
+ if (chosen) break;
2288
+ }
2289
+ if (chosen === void 0 && r.label !== void 0 && isSubject) {
2290
+ const hop = tryShortHopCallout(c[0], c[1], r.fill, r.label, valStr);
2291
+ if (hop) {
2292
+ placedRegionRects.push(hop.rect);
2293
+ placedLeaders.push(hop.leader);
2294
+ const dark = mix(r.fill, palette.text, 60);
2295
+ labels.push({
2296
+ x: hop.x,
2297
+ y: hop.y,
2298
+ text: r.label,
2299
+ anchor: "middle",
2300
+ color: palette.text,
2301
+ halo: false,
2302
+ haloColor: palette.bg,
2303
+ ...valStr !== void 0 && { valueLine: valStr },
2304
+ leader: {
2305
+ x1: hop.leader[0],
2306
+ y1: hop.leader[1],
2307
+ x2: hop.leader[2],
2308
+ y2: hop.leader[3]
2309
+ },
2310
+ leaderColor: dark,
2311
+ calloutDot: { x: c[0], y: c[1], color: dark },
2312
+ lineNumber: r.lineNumber
2313
+ });
2314
+ labeledRegionIds.add(r.id);
2315
+ }
2316
+ continue;
2317
+ }
2318
+ if (chosen === void 0) continue;
2319
+ const isOrient = r.value === void 0 && r.layer === "base";
2320
+ let font = FONT2;
2321
+ if (isOrient) {
2322
+ const growT = sizeT(boxW, boxH);
2323
+ const desiredFont = Math.round(
2324
+ FONT2 + growT * (REGION_FONT_MAX_ORIENT - FONT2)
2325
+ );
2326
+ const hasVal = chosen.valueLine !== void 0;
2327
+ const extremesClean = (f) => {
2328
+ const halfW = measureLegendText(chosen.text, f) / 2;
2329
+ return [chosen.ax - halfW, chosen.ax + halfW].every((sx) => {
2330
+ const fAt = fillAt(sx, chosen.ay);
2331
+ return fAt === r.fill || isEmptyFill(fAt);
2332
+ });
2333
+ };
2334
+ let fit = -1;
2335
+ for (let f = desiredFont; f >= FONT2; f--) {
2336
+ if (stackW(chosen.text, chosen.valueLine, f) > boxW || stackH(hasVal, f) > boxH)
2337
+ continue;
2338
+ const gRect = regionLabelRect(
2339
+ chosen.ax,
2340
+ chosen.ay,
2341
+ chosen.text,
2342
+ chosen.valueLine,
2343
+ f
2344
+ );
2345
+ const gName = regionLabelRect(
2346
+ chosen.ax,
2347
+ chosen.ay,
2348
+ chosen.text,
2349
+ void 0,
2350
+ f
2351
+ );
2352
+ if (!fitsRegions(gRect) || !fitsPois(gName) || collides(gName))
2353
+ continue;
2354
+ if (!extremesClean(f)) continue;
2355
+ fit = f;
2356
+ break;
2357
+ }
2358
+ if (fit < 0) {
2359
+ if (!frameContainers.has(r.id)) continue;
2360
+ font = FONT2;
2361
+ } else {
2362
+ font = fit;
2363
+ }
2364
+ }
2365
+ const rRect = regionLabelRect(
2366
+ chosen.ax,
2367
+ chosen.ay,
2368
+ chosen.text,
2369
+ chosen.valueLine,
2370
+ font
2371
+ );
2372
+ placedRegionRects.push(rRect);
2373
+ pushRegionLabel(
2374
+ chosen.ax,
2375
+ chosen.ay,
2376
+ chosen.text,
2377
+ r.fill,
2378
+ r.lineNumber,
2379
+ chosen.valueLine,
2380
+ font,
2381
+ isOrient ? backdropLandTone : void 0
2382
+ );
2383
+ labeledRegionIds.add(r.id);
2384
+ regionLabelGuards.push({
2385
+ label: labels[labels.length - 1],
2386
+ rect: rRect
2387
+ });
2388
+ }
2389
+ for (const seed of insetLabelSeeds) {
2390
+ const text = isCompact ? seed.iso.replace(/^US-/, "") : seed.name;
2391
+ const src = regionById.get(seed.iso);
2392
+ const valStr = regionValueStr(src?.value);
2393
+ pushRegionLabel(
2394
+ seed.x,
2395
+ seed.y,
2396
+ text,
2397
+ src ? regionFill(src) : neutralFill,
2398
+ seed.lineNumber,
2399
+ valStr
2400
+ );
2401
+ labeledRegionIds.add(seed.iso);
2402
+ regionLabelGuards.push({
2403
+ label: labels[labels.length - 1],
2404
+ rect: regionLabelRect(seed.x, seed.y, text, valStr)
2405
+ });
2406
+ }
2407
+ }
2408
+ if (resolved.directives.noPoiLabels !== true) {
2409
+ const ordered = [...pois].filter((p) => p.clusterId === void 0).sort((a, b) => a.lineNumber - b.lineNumber || (a.id < b.id ? -1 : 1));
2410
+ const poiById = new Map(resolved.pois.map((q) => [q.id, q]));
2411
+ const labelText = (p) => {
2412
+ const src = poiById.get(p.id);
2413
+ return src?.label ?? src?.name ?? p.id;
2414
+ };
2415
+ const poiLabH = FONT2 * 1.25;
2416
+ const labelInfo = (p) => {
2417
+ const text = labelText(p);
2418
+ return { text, w: measureLegendText(text, FONT2) };
2419
+ };
2420
+ const GAP = 3;
2421
+ const POI_EDGE_CLEAR = Math.max(
2422
+ 20,
2423
+ Math.round(Math.min(width, height) * 0.03)
2424
+ );
2425
+ const clusterMembersById = /* @__PURE__ */ new Map();
2426
+ for (const p of pois) {
2427
+ if (p.clusterId === void 0) continue;
2428
+ const arr = clusterMembersById.get(p.clusterId);
2429
+ if (arr) arr.push(p);
2430
+ else clusterMembersById.set(p.clusterId, [p]);
2431
+ }
2432
+ const inlineRect = (p, w, side) => {
2433
+ switch (side) {
2434
+ case "right":
2435
+ return { x: p.cx + p.r + GAP, y: p.cy - poiLabH / 2, w, h: poiLabH };
2436
+ case "left":
2437
+ return {
2438
+ x: p.cx - p.r - GAP - w,
2439
+ y: p.cy - poiLabH / 2,
2440
+ w,
2441
+ h: poiLabH
2442
+ };
2443
+ case "above":
2444
+ return {
2445
+ x: p.cx - w / 2,
2446
+ y: p.cy - p.r - GAP - poiLabH,
2447
+ w,
2448
+ h: poiLabH
2449
+ };
2450
+ case "below":
2451
+ return { x: p.cx - w / 2, y: p.cy + p.r + GAP, w, h: poiLabH };
2452
+ // Diagonal corners: offset both axes by (r+GAP)/√2 so the box's near
2453
+ // corner clears the dot rim by the same gap as a cardinal side.
2454
+ case "below-right": {
2455
+ const d = (p.r + GAP) * 0.7071;
2456
+ return { x: p.cx + d, y: p.cy + d, w, h: poiLabH };
2457
+ }
2458
+ case "below-left": {
2459
+ const d = (p.r + GAP) * 0.7071;
2460
+ return { x: p.cx - d - w, y: p.cy + d, w, h: poiLabH };
2461
+ }
2462
+ case "above-right": {
2463
+ const d = (p.r + GAP) * 0.7071;
2464
+ return { x: p.cx + d, y: p.cy - d - poiLabH, w, h: poiLabH };
2465
+ }
2466
+ case "above-left": {
2467
+ const d = (p.r + GAP) * 0.7071;
2468
+ return { x: p.cx - d - w, y: p.cy - d - poiLabH, w, h: poiLabH };
2469
+ }
2470
+ }
2471
+ };
2472
+ const pushInline = (p, text, w, side, clusterId) => {
2473
+ const rect = inlineRect(p, w, side);
2474
+ obstacles.push(rect);
2475
+ const startSide = side === "right" || side === "below-right" || side === "above-right";
2476
+ const endSide = side === "left" || side === "below-left" || side === "above-left";
2477
+ const anchor = startSide ? "start" : endSide ? "end" : "middle";
2478
+ const x = startSide ? rect.x : endSide ? rect.x + w : p.cx;
2479
+ labels.push({
2480
+ x,
2481
+ y: rect.y + poiLabH / 2 + FONT2 / 3,
2482
+ text,
2483
+ anchor,
2484
+ color: palette.text,
2485
+ halo: false,
2486
+ haloColor: palette.bg,
2487
+ poiId: p.id,
2488
+ lineNumber: p.lineNumber,
2489
+ ...clusterId !== void 0 && { clusterMember: clusterId }
2490
+ });
2491
+ };
2492
+ const inlineFits = (p, w, side) => {
2493
+ const rect = inlineRect(p, w, side);
2494
+ return rect.x >= 0 && rect.x + rect.w <= width && rect.y >= 0 && rect.y + rect.h <= height && !collides(rect);
2495
+ };
2496
+ const GROUP_R = 30;
2497
+ const groups2 = [];
2498
+ for (const p of ordered) {
2499
+ const near = groups2.find(
2500
+ (g) => g.some((q) => Math.hypot(q.cx - p.cx, q.cy - p.cy) < GROUP_R)
2501
+ );
2502
+ if (near) near.push(p);
2503
+ else groups2.push([p]);
2504
+ }
2505
+ const ROW_GAP = 3;
2506
+ const step = poiLabH + ROW_GAP;
2507
+ const COL_GAP = 16;
2508
+ const makeItems = (group) => group.map((p) => ({ p, ...labelInfo(p) })).sort((a, b) => a.p.cy - b.p.cy || (a.text < b.text ? -1 : 1));
2509
+ const columnRows = (items, side) => {
2510
+ const left = Math.min(...items.map((o) => o.p.cx - o.p.r));
2511
+ const right = Math.max(...items.map((o) => o.p.cx + o.p.r));
2512
+ const maxW = Math.max(...items.map((o) => o.w));
2513
+ const cyMid = (Math.min(...items.map((o) => o.p.cy)) + Math.max(...items.map((o) => o.p.cy))) / 2;
2514
+ const colX = side === "right" ? Math.min(right + COL_GAP, width - POI_EDGE_CLEAR - maxW) : Math.max(left - COL_GAP, POI_EDGE_CLEAR + maxW);
2515
+ const totalH = items.length * step;
2516
+ let startY = cyMid - totalH / 2;
2517
+ startY = Math.max(
2518
+ POI_EDGE_CLEAR,
2519
+ Math.min(startY, height - totalH - POI_EDGE_CLEAR)
2520
+ );
2521
+ return items.map((o, i) => {
2522
+ const rowCy = startY + i * step + step / 2;
2523
+ return {
2524
+ o,
2525
+ colX,
2526
+ rowCy,
2527
+ rect: {
2528
+ x: side === "right" ? colX : colX - o.w,
2529
+ y: rowCy - poiLabH / 2,
2530
+ w: o.w,
2531
+ h: poiLabH
2532
+ }
2533
+ };
2534
+ });
2535
+ };
2536
+ const wouldColumnBeClean = (items, side) => columnRows(items, side).every(
2537
+ ({ rect }) => rect.x >= 0 && rect.x + rect.w <= width && rect.y >= 0 && rect.y + rect.h <= height && !collides(rect)
2538
+ );
2539
+ const WATER_PREF = 1e3;
2540
+ const openness = (rect) => {
2541
+ const xs = [
2542
+ rect.x + rect.w * 0.15,
2543
+ rect.x + rect.w * 0.5,
2544
+ rect.x + rect.w * 0.85
2545
+ ];
2546
+ const ys = [rect.y + rect.h * 0.25, rect.y + rect.h * 0.75];
2547
+ let waterHits = 0;
2548
+ for (const x of xs)
2549
+ for (const y of ys) if (fillAt(x, y) === water) waterHits++;
2550
+ const waterFrac = waterHits / (xs.length * ys.length);
2551
+ const edgeClear = Math.max(
2552
+ 0,
2553
+ Math.min(
2554
+ rect.x,
2555
+ width - (rect.x + rect.w),
2556
+ rect.y,
2557
+ height - (rect.y + rect.h)
2558
+ )
2559
+ );
2560
+ return WATER_PREF * waterFrac + edgeClear * 0.1;
2561
+ };
2562
+ const columnSideScore = (items, side) => {
2563
+ const rows = columnRows(items, side);
2564
+ if (rows.length === 0) return -Infinity;
2565
+ return rows.reduce((s, { rect }) => s + openness(rect), 0) / rows.length;
2566
+ };
2567
+ const defaultColumnSide = (items) => columnSideScore(items, "right") >= columnSideScore(items, "left") ? "right" : "left";
2568
+ const commitColumn = (items, side, clusterId) => {
2569
+ for (const { o, colX, rowCy, rect } of columnRows(items, side)) {
2570
+ obstacles.push(rect);
2571
+ labels.push({
2572
+ x: colX,
2573
+ y: rowCy + FONT2 / 3,
2574
+ text: o.text,
2575
+ anchor: side === "right" ? "start" : "end",
2576
+ color: palette.text,
2577
+ halo: false,
2578
+ haloColor: palette.bg,
2579
+ leader: {
2580
+ x1: o.p.cx,
2581
+ y1: o.p.cy,
2582
+ x2: side === "right" ? colX - 2 : colX + 2,
2583
+ y2: rowCy
2584
+ },
2585
+ leaderColor: o.p.fill,
2586
+ poiId: o.p.id,
2587
+ lineNumber: o.p.lineNumber,
2588
+ ...clusterId !== void 0 && { clusterMember: clusterId }
2589
+ });
2590
+ }
2591
+ };
2592
+ const pushHidden = (p) => {
2593
+ const { text, w } = labelInfo(p);
2594
+ let x = p.cx + p.r + GAP;
2595
+ let anchor = "start";
2596
+ if (x + w > width) {
2597
+ x = p.cx - p.r - GAP - w;
2598
+ anchor = "end";
2599
+ }
2600
+ const y = Math.max(0, Math.min(p.cy - poiLabH / 2, height - poiLabH));
2601
+ labels.push({
2602
+ x: anchor === "start" ? x : x + w,
2603
+ y: y + poiLabH / 2 + FONT2 / 3,
2604
+ text,
2605
+ anchor,
2606
+ color: palette.text,
2607
+ halo: false,
2608
+ haloColor: palette.bg,
2609
+ poiId: p.id,
2610
+ hidden: true,
2611
+ lineNumber: p.lineNumber
2612
+ });
2613
+ };
2614
+ const STACK_RADIAL_MAX = 4;
2615
+ const radialSide = (p, cx, cy) => {
2616
+ const dx = p.cx - cx;
2617
+ const dy = p.cy - cy;
2618
+ return Math.abs(dy) >= Math.abs(dx) ? dy <= 0 ? "above" : "below" : dx < 0 ? "left" : "right";
2619
+ };
2620
+ const tryStackRadial = (items, clusterId) => {
2621
+ const cluster = clusters.find((c) => c.id === clusterId);
2622
+ if (!cluster || items.length > STACK_RADIAL_MAX) return false;
2623
+ const temp = [];
2624
+ const seated = [];
2625
+ for (const { p, text, w } of items) {
2626
+ const pref = radialSide(p, cluster.cx, cluster.cy);
2627
+ const order = [
2628
+ pref,
2629
+ ...["above", "below", "right", "left"].filter(
2630
+ (s) => s !== pref
2631
+ )
2632
+ ];
2633
+ const side = order.find((s) => {
2634
+ const rect = inlineRect(p, w, s);
2635
+ return rect.x >= 0 && rect.x + rect.w <= width && rect.y >= 0 && rect.y + rect.h <= height && !collides(rect) && !temp.some((t) => rectsOverlap(t, rect));
2636
+ });
2637
+ if (side === void 0) return false;
2638
+ temp.push(inlineRect(p, w, side));
2639
+ seated.push({ p, text, w, side });
2640
+ }
2641
+ for (const { p, text, w, side } of seated)
2642
+ pushInline(p, text, w, side, clusterId);
2643
+ return true;
2644
+ };
2645
+ for (const [clusterId, members] of clusterMembersById) {
2646
+ if (members.length === 0) continue;
2647
+ const items = makeItems(members);
2648
+ if (tryStackRadial(items, clusterId)) continue;
2649
+ const cleanR = wouldColumnBeClean(items, "right");
2650
+ const cleanL = wouldColumnBeClean(items, "left");
2651
+ const side = cleanR && cleanL ? defaultColumnSide(items) : cleanR ? "right" : cleanL ? "left" : defaultColumnSide(items);
2652
+ commitColumn(items, side, clusterId);
2653
+ }
2654
+ const CLEAR_CAP = 48;
2655
+ const CLEAR_W = 8;
2656
+ const WATER_W = 0.25;
2657
+ const ptSegDist = (px, py, ax, ay, bx, by) => {
2658
+ const dx = bx - ax;
2659
+ const dy = by - ay;
2660
+ const len2 = dx * dx + dy * dy;
2661
+ let t = len2 > 0 ? ((px - ax) * dx + (py - ay) * dy) / len2 : 0;
2662
+ t = Math.max(0, Math.min(1, t));
2663
+ return Math.hypot(px - (ax + t * dx), py - (ay + t * dy));
2664
+ };
2665
+ const clearanceOf = (rect, ownId) => {
2666
+ const cxL = rect.x + rect.w / 2;
2667
+ const cyL = rect.y + rect.h / 2;
2668
+ let d = CLEAR_CAP;
2669
+ for (const q of pois) {
2670
+ if (q.id === ownId) continue;
2671
+ d = Math.min(d, Math.hypot(q.cx - cxL, q.cy - cyL) - q.r);
2672
+ }
2673
+ for (const s of legSegments)
2674
+ d = Math.min(d, ptSegDist(cxL, cyL, s[0], s[1], s[2], s[3]));
2675
+ return Math.max(0, d);
2676
+ };
2677
+ const LEGIBILITY = {
2678
+ right: 160,
2679
+ left: 160,
2680
+ above: 100,
2681
+ below: 100,
2682
+ "below-right": 0,
2683
+ "below-left": 0,
2684
+ "above-right": 0,
2685
+ "above-left": 0
2686
+ };
2687
+ const ALL_SIDES = Object.keys(LEGIBILITY);
2688
+ const placementScore = (p, w, side) => {
2689
+ const rect = inlineRect(p, w, side);
2690
+ return CLEAR_W * clearanceOf(rect, p.id) + WATER_W * openness(rect) + LEGIBILITY[side];
2691
+ };
2692
+ const maxExtent = MAX_CLUSTER_EXTENT_FACTOR * Math.min(width, height);
2693
+ const clusterPending = [];
2694
+ for (const g of groups2) {
2695
+ const items = makeItems(g);
2696
+ if (g.length === 1) {
2697
+ const { p, text, w } = items[0];
2698
+ const scored = ALL_SIDES.filter((s) => inlineFits(p, w, s)).map(
2699
+ (s) => ({
2700
+ s,
2701
+ v: placementScore(p, w, s)
2702
+ })
2703
+ );
2704
+ if (scored.length === 0) {
2705
+ commitColumn(items, defaultColumnSide(items));
2706
+ continue;
2707
+ }
2708
+ const side = scored.reduce((b, c) => c.v > b.v ? c : b).s;
2709
+ pushInline(p, text, w, side);
2710
+ continue;
2711
+ }
2712
+ const left = Math.min(...items.map((o) => o.p.cx - o.p.r));
2713
+ const right = Math.max(...items.map((o) => o.p.cx + o.p.r));
2714
+ const minCy = Math.min(...items.map((o) => o.p.cy));
2715
+ const maxCy = Math.max(...items.map((o) => o.p.cy));
2716
+ const diag = Math.hypot(right - left, maxCy - minCy);
2717
+ if (diag > maxExtent || items.length > MAX_COLUMN_ROWS) {
2718
+ items.forEach((o) => pushHidden(o.p));
2719
+ } else {
2720
+ clusterPending.push(items);
2721
+ }
2722
+ }
2723
+ for (const items of clusterPending) {
2724
+ const cleanSides = ["right", "left"].filter(
2725
+ (s) => wouldColumnBeClean(items, s)
2726
+ );
2727
+ const side = cleanSides.length > 1 ? defaultColumnSide(items) : cleanSides[0];
2728
+ if (side) commitColumn(items, side);
2729
+ else items.forEach((o) => pushHidden(o.p));
2730
+ }
2731
+ const clearancePass = opts._poiClearancePass ?? 0;
2732
+ const MAX_CLEARANCE_PASSES = 4;
2733
+ if (clearancePass < MAX_CLEARANCE_PASSES && pois.length > 0) {
2734
+ const EDGE_CLEAR = POI_EDGE_CLEAR;
2735
+ const capH = Math.floor(width * 0.3);
2736
+ const capV = Math.floor(height * 0.3);
2737
+ const poiById2 = new Map(pois.map((p) => [p.id, p]));
2738
+ let needLeft = 0;
2739
+ let needRight = 0;
2740
+ let needTop = 0;
2741
+ let needBottom = 0;
2742
+ for (const p of pois) {
2743
+ needLeft = Math.max(needLeft, EDGE_CLEAR - (p.cx - p.r));
2744
+ needRight = Math.max(needRight, p.cx + p.r + EDGE_CLEAR - width);
2745
+ needTop = Math.max(needTop, EDGE_CLEAR - (p.cy - p.r));
2746
+ needBottom = Math.max(needBottom, p.cy + p.r + EDGE_CLEAR - height);
2747
+ }
2748
+ for (const l of labels) {
2749
+ if (l.poiId === void 0) continue;
2750
+ const p = poiById2.get(l.poiId);
2751
+ if (!p) continue;
2752
+ if (l.hidden || l.leader) {
2753
+ const lw = l.hidden ? labelInfo(p).w : measureLegendText(l.text, FONT2);
2754
+ const reach = p.r + COL_GAP + lw + EDGE_CLEAR;
2755
+ if (p.cx >= width / 2)
2756
+ needRight = Math.max(needRight, p.cx + reach - width);
2757
+ else needLeft = Math.max(needLeft, reach - p.cx);
2758
+ continue;
2759
+ }
2760
+ const w = measureLegendText(l.text, FONT2);
2761
+ const boxLeft = l.anchor === "start" ? l.x : l.anchor === "end" ? l.x - w : l.x - w / 2;
2762
+ const boxTop = l.y - FONT2 / 3 - poiLabH / 2;
2763
+ const boxRight = boxLeft + w;
2764
+ const boxBottom = boxTop + poiLabH;
2765
+ needLeft = Math.max(needLeft, EDGE_CLEAR - boxLeft);
2766
+ needRight = Math.max(needRight, boxRight + EDGE_CLEAR - width);
2767
+ needTop = Math.max(needTop, EDGE_CLEAR - boxTop);
2768
+ needBottom = Math.max(needBottom, boxBottom + EDGE_CLEAR - height);
2769
+ }
2770
+ needLeft = Math.min(Math.max(0, Math.ceil(needLeft)), capH);
2771
+ needRight = Math.min(Math.max(0, Math.ceil(needRight)), capH);
2772
+ needTop = Math.min(Math.max(0, Math.ceil(needTop)), capV);
2773
+ needBottom = Math.min(Math.max(0, Math.ceil(needBottom)), capV);
2774
+ if (needLeft >= 1 || needRight >= 1 || needTop >= 1 || needBottom >= 1) {
2775
+ const prev = opts._calloutReserve;
2776
+ const left = Math.min((prev?.left ?? 0) + needLeft, capH);
2777
+ const right = Math.min((prev?.right ?? 0) + needRight, capH);
2778
+ const top = Math.min((prev?.top ?? 0) + needTop, capV);
2779
+ const bottom = Math.min((prev?.bottom ?? 0) + needBottom, capV);
2780
+ return layoutMap(resolved, data, size, {
2781
+ ...opts,
2782
+ _poiClearancePass: clearancePass + 1,
2783
+ _calloutReserve: {
2784
+ ...left > 0 && { left },
2785
+ ...right > 0 && { right },
2786
+ ...top > 0 && { top },
2787
+ ...bottom > 0 && { bottom }
2788
+ }
2789
+ });
2790
+ }
2791
+ }
2792
+ }
2793
+ if (regionLabelGuards.length > 0) {
2794
+ const PAD = 2;
2795
+ const poiRects = labels.filter((l) => l.poiId !== void 0 && l.hidden !== true).map((l) => {
2796
+ const w = measureLegendText(l.text, FONT2);
2797
+ const x = l.anchor === "start" ? l.x : l.anchor === "end" ? l.x - w : l.x - w / 2;
2798
+ return {
2799
+ x: x - PAD,
2800
+ y: l.y - FONT2,
2801
+ w: w + 2 * PAD,
2802
+ h: FONT2 * 1.4 + 2 * PAD
2803
+ };
2804
+ });
2805
+ for (const g of regionLabelGuards) {
2806
+ if (poiRects.some((pr) => rectsOverlap(pr, g.rect))) {
2807
+ const i = labels.indexOf(g.label);
2808
+ if (i >= 0) labels.splice(i, 1);
2809
+ }
2810
+ }
2811
+ }
2812
+ if (legs.some((lg) => lg.label !== void 0)) {
2813
+ const committedBoxes = labels.filter((l) => !l.hidden).map((l) => {
2814
+ const w = labelW(l.text);
2815
+ const x = l.anchor === "start" ? l.x : l.anchor === "end" ? l.x - w : l.x - w / 2;
2816
+ return { x, y: l.y - labelH / 2, w, h: labelH };
2817
+ });
2818
+ const placedEdge = [];
2819
+ const T_LIST = [0.5, 0.42, 0.58, 0.34, 0.66, 0.28, 0.72];
2820
+ for (let i = 0; i < legs.length; i++) {
2821
+ const lg = legs[i];
2822
+ if (lg.label === void 0) continue;
2823
+ const a = poiScreen.get(lg.fromId);
2824
+ const b = poiScreen.get(lg.toId);
2825
+ if (!a || !b) continue;
2826
+ const w = labelW(lg.label);
2827
+ const dx = b.cx - a.cx;
2828
+ const dy = b.cy - a.cy;
2829
+ const len = Math.hypot(dx, dy) || 1;
2830
+ const nx = -dy / len;
2831
+ const ny = dx / len;
2832
+ const perp = labelH + 2;
2833
+ const stroke = lg.width ?? W_MIN;
2834
+ const clearW = stroke / 2 + labelH / 2 + 3;
2835
+ const offList = stroke >= 4 ? [clearW, -clearW, clearW + perp, -(clearW + perp), 0] : [0, perp, -perp, 2 * perp, -2 * perp];
2836
+ const pm = /^M(-?[\d.]+),(-?[\d.]+)(?:L(-?[\d.]+),(-?[\d.]+)|Q(-?[\d.]+),(-?[\d.]+) (-?[\d.]+),(-?[\d.]+))$/.exec(
2837
+ lg.d
2838
+ );
2839
+ const pointAt = (t) => {
2840
+ if (pm?.[5] !== void 0) {
2841
+ const u = 1 - t;
2842
+ return [
2843
+ u * u * +pm[1] + 2 * u * t * +pm[5] + t * t * +pm[7],
2844
+ u * u * +pm[2] + 2 * u * t * +pm[6] + t * t * +pm[8]
2845
+ ];
2846
+ }
2847
+ if (pm?.[3] !== void 0)
2848
+ return [
2849
+ +pm[1] + (+pm[3] - +pm[1]) * t,
2850
+ +pm[2] + (+pm[4] - +pm[2]) * t
2851
+ ];
2852
+ return [a.cx + dx * t, a.cy + dy * t];
2853
+ };
2854
+ const candidates = [];
2855
+ for (const off of offList)
2856
+ for (const t of T_LIST) {
2857
+ const [bx, by] = pointAt(t);
2858
+ candidates.push([bx + nx * off, by + ny * off - 4]);
2859
+ }
2860
+ const clean = ([cx2, cy2]) => {
2861
+ const rect2 = { x: cx2 - w / 2, y: cy2 - labelH / 2, w, h: labelH };
2862
+ if (rect2.x < 0 || rect2.x + w > width || rect2.y < 0 || rect2.y + rect2.h > height)
2863
+ return false;
2864
+ if (markers.some((m) => rectCircleOverlap(rect2, m))) return false;
2865
+ if (committedBoxes.some((o) => rectsOverlap(o, rect2))) return false;
2866
+ if (placedEdge.some((o) => rectsOverlap(o, rect2))) return false;
2867
+ return true;
2868
+ };
2869
+ const mid = pointAt(0.5);
2870
+ const [cx, cy] = candidates.find(clean) ?? [mid[0], mid[1] - 4];
2871
+ const style = labelOnFill(fillAt(cx, cy));
2872
+ legs[i] = {
2873
+ ...lg,
2874
+ labelX: cx,
2875
+ labelY: cy,
2876
+ labelColor: style.color,
2877
+ labelHalo: style.halo,
2878
+ labelHaloColor: style.haloColor
2879
+ };
2880
+ const rect = { x: cx - w / 2, y: cy - labelH / 2, w, h: labelH };
2881
+ placedEdge.push(rect);
2882
+ obstacles.push(rect);
2883
+ }
2884
+ }
2885
+ if (resolved.directives.noContextLabels !== true) {
2886
+ for (const l of labels) {
2887
+ if (l.hidden) continue;
2888
+ const w = labelW(l.text);
2889
+ const x = l.anchor === "start" ? l.x : l.anchor === "end" ? l.x - w : l.x - w / 2;
2890
+ obstacles.push({ x, y: l.y - labelH / 2, w, h: labelH });
2891
+ }
2892
+ for (const box of insets)
2893
+ obstacles.push({ x: box.x, y: box.y, w: box.w, h: box.h });
2894
+ const countryCandidates = [];
2895
+ const rawCountries = [];
2896
+ for (const f of worldLayer.values()) {
2897
+ const iso = typeof f.id === "string" ? f.id : String(f.id ?? "");
2898
+ if (!iso || regionById.has(iso) || labeledRegionIds.has(iso)) continue;
2899
+ if (usContext && iso === "US") continue;
2900
+ let hasReferencedSub = false;
2901
+ for (const k of regionById.keys())
2902
+ if (k.startsWith(iso + "-")) {
2903
+ hasReferencedSub = true;
2904
+ break;
2905
+ }
2906
+ if (hasReferencedSub) continue;
2907
+ const b = path.bounds(f);
2908
+ const [x0, y0] = b[0];
2909
+ const [x1, y1] = b[1];
2910
+ if (!Number.isFinite(x0) || !Number.isFinite(x1)) continue;
2911
+ const anchorLngLat = WORLD_LABEL_ANCHORS[iso];
2912
+ const a = anchorLngLat ? project(anchorLngLat[0], anchorLngLat[1]) : path.centroid(f);
2913
+ rawCountries.push({
2914
+ f,
2915
+ iso,
2916
+ name: f.properties?.name ?? iso,
2917
+ bbox: [x0, y0, x1, y1],
2918
+ anchor: a && Number.isFinite(a[0]) ? [a[0], a[1]] : null,
2919
+ curatedLngLat: anchorLngLat ?? null,
2920
+ area: (x1 - x0) * (y1 - y0)
2921
+ });
2922
+ }
2923
+ const cBand = tierBand(Math.max(dLonSpan, dLatSpan));
2924
+ const cBudget = labelBudget(width, height, cBand);
2925
+ const nationsUnconditional = cBand !== "world";
2926
+ const topN = cBudget + COUNTRY_POS_TOPN_MARGIN;
2927
+ const rankOrder = rawCountries.map((r, idx) => ({ idx, area: r.area })).filter((r) => Number.isFinite(r.area) && r.area > 0).sort((a, b) => b.area - a.area).slice(0, topN);
2928
+ const genIdx = new Set(rankOrder.map((r) => r.idx));
2929
+ for (let i = 0; i < rawCountries.length; i++) {
2930
+ const r = rawCountries[i];
2931
+ let anchor = r.anchor;
2932
+ let positions;
2933
+ if (genIdx.has(i) && anchor) {
2934
+ const gb = geoBounds(r.f);
2935
+ const gen = countryLabelPositions({
2936
+ geometry: r.f.geometry,
2937
+ bounds: gb,
2938
+ project,
2939
+ width,
2940
+ height,
2941
+ curated: r.curatedLngLat
2942
+ });
2943
+ if (gen.length) {
2944
+ positions = gen.map((p) => p.screen);
2945
+ anchor = positions[0];
2946
+ }
2947
+ }
2948
+ countryCandidates.push({
2949
+ name: r.name,
2950
+ bbox: r.bbox,
2951
+ anchor,
2952
+ curatedAnchor: !!r.curatedLngLat,
2953
+ bordering: nationsUnconditional,
2954
+ ...positions ? { positions } : {}
2955
+ });
2956
+ }
2957
+ const framedStateContainers = (resolved.poiFrameContainers ?? []).some(
2958
+ (id) => id.startsWith("US-")
2959
+ );
2960
+ if (usLayer && framedStateContainers) {
2961
+ for (const [iso, f] of usLayer) {
2962
+ if (regionById.has(iso) || labeledRegionIds.has(iso)) continue;
2963
+ const viewF = cullFeatureToView(f);
2964
+ if (!viewF) continue;
2965
+ const b = path.bounds(viewF);
2966
+ const [x0, y0] = b[0];
2967
+ const [x1, y1] = b[1];
2968
+ if (!Number.isFinite(x0) || !Number.isFinite(x1)) continue;
2969
+ const a = path.centroid(viewF);
2970
+ countryCandidates.push({
2971
+ name: f.properties?.name ?? iso,
2972
+ bbox: [x0, y0, x1, y1],
2973
+ anchor: a && Number.isFinite(a[0]) ? [a[0], a[1]] : null
2974
+ });
2975
+ }
2976
+ }
2977
+ const contextLabels = placeContextLabels({
2978
+ projection: resolved.projection,
2979
+ dLonSpan,
2980
+ dLatSpan,
2981
+ width,
2982
+ height,
2983
+ waterBodies: data.waterBodies,
2984
+ countries: countryCandidates,
2985
+ palette,
2986
+ // One muted, contrast-floored tone shared with the orientation backdrop
2987
+ // (region pass) so a context country (Canada) and an orientation region
2988
+ // (Minnesota) read identically — never below the WCAG-AA floor, no fade.
2989
+ countryTone: backdropLandTone,
2990
+ waterTone: backdropWaterTone,
2991
+ project,
2992
+ collides,
2993
+ // Water labels must stay over open water — `fillAt` returns the ocean
2994
+ // backdrop colour off-land and a region fill on-land (lakes/states count
2995
+ // as land here, which is the safe side for an ocean name).
2996
+ overLand: (x, y) => fillAt(x, y) !== water
2997
+ });
2998
+ labels.push(...contextLabels);
2999
+ }
3000
+ const cityDots = [];
3001
+ if (resolved.directives.noCities !== true) {
3002
+ const CITY_DOT_SPACING = 12;
3003
+ const CITY_DOT_CAP = 220;
3004
+ const SPACING_SQ = CITY_DOT_SPACING * CITY_DOT_SPACING;
3005
+ const CITY_DOT_R_MIN = 0.7;
3006
+ const CITY_DOT_R_MAX = 2.6;
3007
+ const CITY_POP_MIN = 5e4;
3008
+ const CITY_POP_MAX = 15e6;
3009
+ const LOG_MIN = Math.log10(CITY_POP_MIN);
3010
+ const LOG_SPAN = Math.log10(CITY_POP_MAX) - LOG_MIN;
3011
+ const cityDotRadius = (pop) => {
3012
+ if (!(pop > CITY_POP_MIN)) return CITY_DOT_R_MIN;
3013
+ const t = Math.min(1, (Math.log10(pop) - LOG_MIN) / LOG_SPAN);
3014
+ return CITY_DOT_R_MIN + t * (CITY_DOT_R_MAX - CITY_DOT_R_MIN);
3015
+ };
3016
+ const placed = pois.map((p) => ({
3017
+ x: p.cx,
3018
+ y: p.cy
3019
+ }));
3020
+ const sorted = [...data.gazetteer.cities].sort((a, b) => b[3] - a[3]);
3021
+ for (const c of sorted) {
3022
+ if (cityDots.length >= CITY_DOT_CAP) break;
3023
+ const lat = c[0];
3024
+ const lon = c[1];
3025
+ const p = project(lon, lat);
3026
+ if (!p) continue;
3027
+ const [px, py] = p;
3028
+ if (px < 0 || px > width || py < 0 || py > height) continue;
3029
+ let tooClose = false;
3030
+ for (const q of placed) {
3031
+ const dx = q.x - px;
3032
+ const dy = q.y - py;
3033
+ if (dx * dx + dy * dy < SPACING_SQ) {
3034
+ tooClose = true;
3035
+ break;
3036
+ }
3037
+ }
3038
+ if (tooClose) continue;
3039
+ placed.push({ x: px, y: py });
3040
+ cityDots.push({ cx: px, cy: py, r: cityDotRadius(c[3]) });
3041
+ }
3042
+ }
3043
+ return {
3044
+ width,
3045
+ height,
3046
+ background: water,
3047
+ title: shownTitle,
3048
+ ...resolved.subtitle !== void 0 && { subtitle: resolved.subtitle },
3049
+ ...resolved.caption !== void 0 && { caption: resolved.caption },
3050
+ regions,
3051
+ rivers,
3052
+ relief,
3053
+ reliefHatch,
3054
+ coastlineStyle,
3055
+ legs,
3056
+ pois,
3057
+ cityDots,
3058
+ clusters,
3059
+ labels,
3060
+ legend,
3061
+ insets,
3062
+ insetRegions,
3063
+ projection,
3064
+ stretch: stretchParams,
3065
+ diagnostics: []
3066
+ };
3067
+ }
3068
+
3069
+ export {
3070
+ mapLegendGroups,
3071
+ mapLegendConfig,
3072
+ mapBackgroundColor,
3073
+ mapNeutralLandColor,
3074
+ buildMapProjection,
3075
+ parsePathRings,
3076
+ albersSkewFallback,
3077
+ layoutMap
3078
+ };
3079
+ //# sourceMappingURL=chunk-IPWPWM6R.js.map