@diagrammo/dgmo 0.21.1 → 0.23.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 (87) hide show
  1. package/README.md +16 -6
  2. package/dist/advanced.cjs +2230 -503
  3. package/dist/advanced.d.cts +5731 -0
  4. package/dist/advanced.d.ts +5731 -0
  5. package/dist/advanced.js +2226 -503
  6. package/dist/auto.cjs +2272 -479
  7. package/dist/auto.d.cts +39 -0
  8. package/dist/auto.d.ts +39 -0
  9. package/dist/auto.js +124 -124
  10. package/dist/auto.mjs +2274 -480
  11. package/dist/cli.cjs +170 -170
  12. package/dist/editor.cjs +16 -16
  13. package/dist/editor.js +16 -16
  14. package/dist/highlight.cjs +18 -13
  15. package/dist/highlight.js +18 -13
  16. package/dist/index.cjs +2253 -465
  17. package/dist/index.d.cts +339 -0
  18. package/dist/index.d.ts +339 -0
  19. package/dist/index.js +2255 -466
  20. package/dist/internal.cjs +2230 -503
  21. package/dist/internal.d.cts +5731 -0
  22. package/dist/internal.d.ts +5731 -0
  23. package/dist/internal.js +2226 -503
  24. package/dist/map-data/PROVENANCE.json +1 -1
  25. package/dist/map-data/gazetteer.json +1 -1
  26. package/dist/map-data/mountain-ranges.json +1 -1
  27. package/dist/map-data/water-bodies.json +1 -0
  28. package/dist/map-data/world-coarse.json +1 -1
  29. package/dist/map-data/world-detail.json +1 -1
  30. package/docs/language-reference.md +55 -9
  31. package/gallery/fixtures/boxes-and-lines.dgmo +6 -4
  32. package/gallery/fixtures/map-categorical-world.dgmo +16 -0
  33. package/gallery/fixtures/map-categorical.dgmo +0 -1
  34. package/gallery/fixtures/map-choropleth.dgmo +0 -1
  35. package/gallery/fixtures/map-coastline.dgmo +7 -0
  36. package/gallery/fixtures/map-colorize.dgmo +11 -0
  37. package/gallery/fixtures/map-direct-color.dgmo +0 -1
  38. package/gallery/fixtures/map-reference-world.dgmo +11 -0
  39. package/gallery/fixtures/map-region-scope.dgmo +0 -3
  40. package/gallery/fixtures/map-route.dgmo +0 -1
  41. package/package.json +1 -1
  42. package/src/advanced.ts +12 -1
  43. package/src/boxes-and-lines/parser.ts +39 -0
  44. package/src/boxes-and-lines/renderer.ts +205 -20
  45. package/src/boxes-and-lines/types.ts +9 -0
  46. package/src/cli.ts +1 -1
  47. package/src/completion.ts +36 -30
  48. package/src/cycle/renderer.ts +14 -1
  49. package/src/d3.ts +20 -6
  50. package/src/editor/highlight-api.ts +4 -0
  51. package/src/editor/keywords.ts +16 -16
  52. package/src/infra/renderer.ts +35 -7
  53. package/src/map/colorize.ts +54 -0
  54. package/src/map/context-labels.ts +429 -0
  55. package/src/map/data/PROVENANCE.json +1 -1
  56. package/src/map/data/README.md +6 -0
  57. package/src/map/data/gazetteer.json +1 -1
  58. package/src/map/data/mountain-ranges.json +1 -1
  59. package/src/map/data/types.ts +34 -0
  60. package/src/map/data/water-bodies.json +1 -0
  61. package/src/map/data/world-coarse.json +1 -1
  62. package/src/map/data/world-detail.json +1 -1
  63. package/src/map/dimensions.ts +117 -0
  64. package/src/map/geo-query.ts +21 -3
  65. package/src/map/geo.ts +47 -1
  66. package/src/map/layout.ts +1408 -266
  67. package/src/map/load-data.ts +10 -2
  68. package/src/map/parser.ts +42 -116
  69. package/src/map/renderer.ts +604 -14
  70. package/src/map/resolved-types.ts +16 -2
  71. package/src/map/resolver.ts +208 -59
  72. package/src/map/types.ts +30 -32
  73. package/src/mindmap/renderer.ts +10 -1
  74. package/src/palettes/atlas.ts +77 -0
  75. package/src/palettes/blueprint.ts +73 -0
  76. package/src/palettes/color-utils.ts +58 -1
  77. package/src/palettes/index.ts +12 -3
  78. package/src/palettes/slate.ts +73 -0
  79. package/src/palettes/tidewater.ts +73 -0
  80. package/src/render.ts +8 -1
  81. package/src/tech-radar/renderer.ts +3 -0
  82. package/src/tech-radar/types.ts +3 -0
  83. package/src/utils/d3-types.ts +5 -0
  84. package/src/utils/legend-layout.ts +21 -4
  85. package/src/utils/legend-types.ts +7 -0
  86. package/src/utils/reserved-key-registry.ts +8 -3
  87. package/src/palettes/bold.ts +0 -67
package/dist/internal.js CHANGED
@@ -371,18 +371,18 @@ function computeQuadrantPointLabels(points, chartBounds, obstacles, pointRadius,
371
371
  const results = [];
372
372
  for (let i = 0; i < points.length; i++) {
373
373
  const pt = points[i];
374
- const labelWidth = pt.label.length * fontSize * CHAR_WIDTH_RATIO + 8;
374
+ const labelWidth2 = pt.label.length * fontSize * CHAR_WIDTH_RATIO + 8;
375
375
  let best = null;
376
376
  const directions = [
377
377
  {
378
378
  // Above
379
379
  gen: (offset) => {
380
- const lx = pt.cx - labelWidth / 2;
380
+ const lx = pt.cx - labelWidth2 / 2;
381
381
  const ly = pt.cy - offset - labelHeight;
382
- if (ly < chartBounds.top || lx < chartBounds.left || lx + labelWidth > chartBounds.right)
382
+ if (ly < chartBounds.top || lx < chartBounds.left || lx + labelWidth2 > chartBounds.right)
383
383
  return null;
384
384
  return {
385
- rect: { x: lx, y: ly, w: labelWidth, h: labelHeight },
385
+ rect: { x: lx, y: ly, w: labelWidth2, h: labelHeight },
386
386
  textX: pt.cx,
387
387
  textY: ly + labelHeight / 2,
388
388
  anchor: "middle"
@@ -392,12 +392,12 @@ function computeQuadrantPointLabels(points, chartBounds, obstacles, pointRadius,
392
392
  {
393
393
  // Below
394
394
  gen: (offset) => {
395
- const lx = pt.cx - labelWidth / 2;
395
+ const lx = pt.cx - labelWidth2 / 2;
396
396
  const ly = pt.cy + offset;
397
- if (ly + labelHeight > chartBounds.bottom || lx < chartBounds.left || lx + labelWidth > chartBounds.right)
397
+ if (ly + labelHeight > chartBounds.bottom || lx < chartBounds.left || lx + labelWidth2 > chartBounds.right)
398
398
  return null;
399
399
  return {
400
- rect: { x: lx, y: ly, w: labelWidth, h: labelHeight },
400
+ rect: { x: lx, y: ly, w: labelWidth2, h: labelHeight },
401
401
  textX: pt.cx,
402
402
  textY: ly + labelHeight / 2,
403
403
  anchor: "middle"
@@ -409,10 +409,10 @@ function computeQuadrantPointLabels(points, chartBounds, obstacles, pointRadius,
409
409
  gen: (offset) => {
410
410
  const lx = pt.cx + offset;
411
411
  const ly = pt.cy - labelHeight / 2;
412
- if (lx + labelWidth > chartBounds.right || ly < chartBounds.top || ly + labelHeight > chartBounds.bottom)
412
+ if (lx + labelWidth2 > chartBounds.right || ly < chartBounds.top || ly + labelHeight > chartBounds.bottom)
413
413
  return null;
414
414
  return {
415
- rect: { x: lx, y: ly, w: labelWidth, h: labelHeight },
415
+ rect: { x: lx, y: ly, w: labelWidth2, h: labelHeight },
416
416
  textX: lx,
417
417
  textY: pt.cy,
418
418
  anchor: "start"
@@ -422,13 +422,13 @@ function computeQuadrantPointLabels(points, chartBounds, obstacles, pointRadius,
422
422
  {
423
423
  // Left
424
424
  gen: (offset) => {
425
- const lx = pt.cx - offset - labelWidth;
425
+ const lx = pt.cx - offset - labelWidth2;
426
426
  const ly = pt.cy - labelHeight / 2;
427
427
  if (lx < chartBounds.left || ly < chartBounds.top || ly + labelHeight > chartBounds.bottom)
428
428
  return null;
429
429
  return {
430
- rect: { x: lx, y: ly, w: labelWidth, h: labelHeight },
431
- textX: lx + labelWidth,
430
+ rect: { x: lx, y: ly, w: labelWidth2, h: labelHeight },
431
+ textX: lx + labelWidth2,
432
432
  textY: pt.cy,
433
433
  anchor: "end"
434
434
  };
@@ -478,10 +478,10 @@ function computeQuadrantPointLabels(points, chartBounds, obstacles, pointRadius,
478
478
  }
479
479
  }
480
480
  if (!best) {
481
- const lx = pt.cx - labelWidth / 2;
481
+ const lx = pt.cx - labelWidth2 / 2;
482
482
  const ly = pt.cy - minGap - labelHeight;
483
483
  best = {
484
- rect: { x: lx, y: ly, w: labelWidth, h: labelHeight },
484
+ rect: { x: lx, y: ly, w: labelWidth2, h: labelHeight },
485
485
  textX: pt.cx,
486
486
  textY: ly + labelHeight / 2,
487
487
  anchor: "middle",
@@ -858,6 +858,9 @@ var init_reserved_key_registry = __esm({
858
858
  "value",
859
859
  "label",
860
860
  "style"
861
+ // `surface:` was removed in the 2026-06-02 defaults-on review — it is no longer
862
+ // a recognized metadata key (the route/edge surface feature was cut; §24B.7).
863
+ // A stray `surface: water` is no longer captured as a reserved key.
861
864
  ]);
862
865
  ORG_REGISTRY = staticRegistry([
863
866
  "color",
@@ -912,9 +915,7 @@ var init_reserved_key_registry = __esm({
912
915
  BOXES_AND_LINES_REGISTRY = staticRegistry([
913
916
  "color",
914
917
  "description",
915
- "width",
916
- "split",
917
- "fanout"
918
+ "value"
918
919
  ]);
919
920
  TIMELINE_REGISTRY = staticRegistry([
920
921
  "color",
@@ -1920,77 +1921,266 @@ function getSegmentColors(palette, count) {
1920
1921
  (_, i) => hslToHex(Math.round((startHue + i * step) % 360), avgS, avgL)
1921
1922
  );
1922
1923
  }
1924
+ function politicalTints(palette, count, isDark) {
1925
+ if (count <= 0) return [];
1926
+ const base = isDark ? palette.surface : palette.bg;
1927
+ const c = palette.colors;
1928
+ const swatches = [
1929
+ .../* @__PURE__ */ new Set([
1930
+ c.green,
1931
+ c.yellow,
1932
+ c.orange,
1933
+ c.purple,
1934
+ c.red,
1935
+ c.teal,
1936
+ c.cyan,
1937
+ c.blue
1938
+ ])
1939
+ ];
1940
+ const bands = isDark ? POLITICAL_TINT_BANDS.dark : POLITICAL_TINT_BANDS.light;
1941
+ const out = [];
1942
+ for (const pct of bands) {
1943
+ if (out.length >= count) break;
1944
+ for (const s of swatches) out.push(mix(s, base, pct));
1945
+ }
1946
+ return out.slice(0, count);
1947
+ }
1948
+ var POLITICAL_TINT_BANDS;
1923
1949
  var init_color_utils = __esm({
1924
1950
  "src/palettes/color-utils.ts"() {
1925
1951
  "use strict";
1952
+ POLITICAL_TINT_BANDS = {
1953
+ light: [32, 48, 64, 80],
1954
+ dark: [44, 58, 72, 86]
1955
+ };
1926
1956
  }
1927
1957
  });
1928
1958
 
1929
- // src/palettes/bold.ts
1930
- var boldPalette;
1931
- var init_bold = __esm({
1932
- "src/palettes/bold.ts"() {
1959
+ // src/palettes/atlas.ts
1960
+ var atlasPalette;
1961
+ var init_atlas = __esm({
1962
+ "src/palettes/atlas.ts"() {
1933
1963
  "use strict";
1934
1964
  init_registry();
1935
- boldPalette = {
1936
- id: "bold",
1937
- name: "Bold",
1965
+ atlasPalette = {
1966
+ id: "atlas",
1967
+ name: "Atlas",
1938
1968
  light: {
1939
- bg: "#ffffff",
1940
- surface: "#f0f0f0",
1941
- overlay: "#f0f0f0",
1942
- border: "#cccccc",
1943
- text: "#000000",
1944
- textMuted: "#666666",
1945
- textOnFillLight: "#ffffff",
1946
- textOnFillDark: "#000000",
1947
- primary: "#0000ff",
1948
- secondary: "#ff00ff",
1949
- accent: "#00cccc",
1950
- destructive: "#ff0000",
1969
+ bg: "#f3ead3",
1970
+ // warm manila / parchment
1971
+ surface: "#ece0c0",
1972
+ // deeper paper (cards, panels)
1973
+ overlay: "#e8dab8",
1974
+ // popovers, dropdowns
1975
+ border: "#bcaa86",
1976
+ // muted sepia rule line
1977
+ text: "#463a26",
1978
+ // aged sepia-brown ink
1979
+ textMuted: "#7a6a4f",
1980
+ // faded annotation ink
1981
+ textOnFillLight: "#f7f1de",
1982
+ // parchment (light text on dark fills)
1983
+ textOnFillDark: "#3a2e1c",
1984
+ // deep ink (dark text on light fills)
1985
+ primary: "#5b7a99",
1986
+ // pull-down map ocean (steel-blue)
1987
+ secondary: "#7e9a6f",
1988
+ // lowland sage / celadon
1989
+ accent: "#b07f7c",
1990
+ // dusty rose
1991
+ destructive: "#b25a45",
1992
+ // brick / terracotta
1951
1993
  colors: {
1952
- red: "#ff0000",
1953
- orange: "#ff8000",
1954
- yellow: "#ffcc00",
1955
- green: "#00cc00",
1956
- blue: "#0000ff",
1957
- purple: "#cc00cc",
1958
- teal: "#008080",
1959
- cyan: "#00cccc",
1960
- gray: "#808080",
1961
- black: "#000000",
1962
- white: "#f0f0f0"
1994
+ red: "#bf6a52",
1995
+ // terracotta brick
1996
+ orange: "#cf9a5c",
1997
+ // map tan / ochre
1998
+ yellow: "#cdb35e",
1999
+ // straw / muted lemon
2000
+ green: "#7e9a6f",
2001
+ // sage / celadon lowland
2002
+ blue: "#5b7a99",
2003
+ // steel-blue ocean
2004
+ purple: "#9a7fa6",
2005
+ // dusty lilac / mauve
2006
+ teal: "#6fa094",
2007
+ // muted seafoam
2008
+ cyan: "#79a7b5",
2009
+ // shallow-water blue
2010
+ gray: "#8a7d68",
2011
+ // warm taupe
2012
+ black: "#463a26",
2013
+ // ink
2014
+ white: "#ece0c0"
2015
+ // paper
1963
2016
  }
1964
2017
  },
1965
2018
  dark: {
1966
- bg: "#000000",
1967
- surface: "#111111",
1968
- overlay: "#1a1a1a",
1969
- border: "#333333",
1970
- text: "#ffffff",
1971
- textMuted: "#aaaaaa",
1972
- textOnFillLight: "#ffffff",
1973
- textOnFillDark: "#000000",
1974
- primary: "#00ccff",
1975
- secondary: "#ff00ff",
1976
- accent: "#ffff00",
1977
- destructive: "#ff0000",
2019
+ bg: "#1e2a33",
2020
+ // deep map ocean (night globe)
2021
+ surface: "#27353f",
2022
+ // raised ocean
2023
+ overlay: "#2e3d48",
2024
+ // popovers, dropdowns
2025
+ border: "#3d4f5c",
2026
+ // depth-contour line
2027
+ text: "#e8dcc0",
2028
+ // parchment ink, inverted
2029
+ textMuted: "#a89a7d",
2030
+ // faded label
2031
+ textOnFillLight: "#f7f1de",
2032
+ // parchment
2033
+ textOnFillDark: "#1a242c",
2034
+ // deep ocean ink
2035
+ primary: "#7ba0bf",
2036
+ // brighter ocean
2037
+ secondary: "#9bb588",
2038
+ // sage, lifted
2039
+ accent: "#cf9a96",
2040
+ // dusty rose, lifted
2041
+ destructive: "#c9745c",
2042
+ // brick, lifted
2043
+ colors: {
2044
+ red: "#cf7a60",
2045
+ // terracotta
2046
+ orange: "#d9a96a",
2047
+ // tan / ochre
2048
+ yellow: "#d8c074",
2049
+ // straw
2050
+ green: "#9bb588",
2051
+ // sage lowland
2052
+ blue: "#7ba0bf",
2053
+ // ocean
2054
+ purple: "#b59ac0",
2055
+ // lilac / mauve
2056
+ teal: "#85b3a6",
2057
+ // seafoam
2058
+ cyan: "#92bccb",
2059
+ // shallow-water blue
2060
+ gray: "#9a8d76",
2061
+ // warm taupe
2062
+ black: "#27353f",
2063
+ // raised ocean
2064
+ white: "#e8dcc0"
2065
+ // parchment
2066
+ }
2067
+ }
2068
+ };
2069
+ registerPalette(atlasPalette);
2070
+ }
2071
+ });
2072
+
2073
+ // src/palettes/blueprint.ts
2074
+ var blueprintPalette;
2075
+ var init_blueprint = __esm({
2076
+ "src/palettes/blueprint.ts"() {
2077
+ "use strict";
2078
+ init_registry();
2079
+ blueprintPalette = {
2080
+ id: "blueprint",
2081
+ name: "Blueprint",
2082
+ light: {
2083
+ bg: "#f4f8fb",
2084
+ // pale drafting white (faint cyan)
2085
+ surface: "#e6eef4",
2086
+ // drafting panel
2087
+ overlay: "#dde9f1",
2088
+ // popovers, dropdowns
2089
+ border: "#aac3d6",
2090
+ // pale blue grid line
2091
+ text: "#123a5e",
2092
+ // blueprint navy ink
2093
+ textMuted: "#4f7390",
2094
+ // faint draft note
2095
+ textOnFillLight: "#f4f8fb",
2096
+ // drafting white
2097
+ textOnFillDark: "#0c2f4d",
2098
+ // deep blueprint navy
2099
+ primary: "#1f5e8c",
2100
+ // blueprint blue
2101
+ secondary: "#5b7d96",
2102
+ // steel
2103
+ accent: "#b08a3e",
2104
+ // draftsman's ochre highlight
2105
+ destructive: "#c0504d",
2106
+ // correction red
2107
+ colors: {
2108
+ red: "#c25a4e",
2109
+ // correction red
2110
+ orange: "#c2823e",
2111
+ // ochre
2112
+ yellow: "#c2a843",
2113
+ // pencil gold
2114
+ green: "#4f8a6b",
2115
+ // drafting green
2116
+ blue: "#1f5e8c",
2117
+ // blueprint blue
2118
+ purple: "#6f5e96",
2119
+ // indigo pencil
2120
+ teal: "#3a8a8a",
2121
+ // teal
2122
+ cyan: "#3f8fb5",
2123
+ // cyan
2124
+ gray: "#7e8e98",
2125
+ // graphite
2126
+ black: "#123a5e",
2127
+ // navy ink
2128
+ white: "#e6eef4"
2129
+ // panel
2130
+ }
2131
+ },
2132
+ dark: {
2133
+ bg: "#103a5e",
2134
+ // deep blueprint blue (cyanotype ground)
2135
+ surface: "#16466e",
2136
+ // raised sheet
2137
+ overlay: "#1c5180",
2138
+ // popovers, dropdowns
2139
+ border: "#3a6f96",
2140
+ // grid line
2141
+ text: "#eaf2f8",
2142
+ // chalk white
2143
+ textMuted: "#9fc0d6",
2144
+ // faint chalk note
2145
+ textOnFillLight: "#eaf2f8",
2146
+ // chalk white
2147
+ textOnFillDark: "#0c2f4d",
2148
+ // deep blueprint navy
2149
+ primary: "#7fb8d8",
2150
+ // chalk cyan
2151
+ secondary: "#9fb8c8",
2152
+ // pale steel
2153
+ accent: "#d8c27a",
2154
+ // chalk amber
2155
+ destructive: "#e08a7a",
2156
+ // chalk correction red
1978
2157
  colors: {
1979
- red: "#ff0000",
1980
- orange: "#ff8000",
1981
- yellow: "#ffff00",
1982
- green: "#00ff00",
1983
- blue: "#0066ff",
1984
- purple: "#ff00ff",
1985
- teal: "#00cccc",
1986
- cyan: "#00ffff",
1987
- gray: "#808080",
1988
- black: "#111111",
1989
- white: "#ffffff"
2158
+ red: "#e0907e",
2159
+ // chalk red
2160
+ orange: "#e0ab78",
2161
+ // chalk amber
2162
+ yellow: "#e3d089",
2163
+ // chalk gold
2164
+ green: "#93c79e",
2165
+ // chalk green
2166
+ blue: "#8ec3e0",
2167
+ // chalk cyan-blue
2168
+ purple: "#b6a6d8",
2169
+ // chalk indigo
2170
+ teal: "#84c7c2",
2171
+ // chalk teal
2172
+ cyan: "#9fd6e0",
2173
+ // chalk cyan
2174
+ gray: "#aebecb",
2175
+ // chalk graphite
2176
+ black: "#16466e",
2177
+ // raised sheet
2178
+ white: "#eaf2f8"
2179
+ // chalk white
1990
2180
  }
1991
2181
  }
1992
2182
  };
1993
- registerPalette(boldPalette);
2183
+ registerPalette(blueprintPalette);
1994
2184
  }
1995
2185
  });
1996
2186
 
@@ -2487,6 +2677,120 @@ var init_rose_pine = __esm({
2487
2677
  }
2488
2678
  });
2489
2679
 
2680
+ // src/palettes/slate.ts
2681
+ var slatePalette;
2682
+ var init_slate = __esm({
2683
+ "src/palettes/slate.ts"() {
2684
+ "use strict";
2685
+ init_registry();
2686
+ slatePalette = {
2687
+ id: "slate",
2688
+ name: "Slate",
2689
+ light: {
2690
+ bg: "#ffffff",
2691
+ // clean slide white
2692
+ surface: "#f3f5f8",
2693
+ // light cool-gray panel
2694
+ overlay: "#eaeef3",
2695
+ // popovers, dropdowns
2696
+ border: "#d4dae1",
2697
+ // hairline rule
2698
+ text: "#1f2933",
2699
+ // near-black slate (softer than pure black)
2700
+ textMuted: "#5b6672",
2701
+ // secondary label
2702
+ textOnFillLight: "#ffffff",
2703
+ // light text on dark fills
2704
+ textOnFillDark: "#1f2933",
2705
+ // dark text on light fills
2706
+ primary: "#3b6ea5",
2707
+ // confident corporate blue
2708
+ secondary: "#5b6672",
2709
+ // slate gray
2710
+ accent: "#3a9188",
2711
+ // muted teal accent
2712
+ destructive: "#c0504d",
2713
+ // brick red
2714
+ colors: {
2715
+ red: "#c0504d",
2716
+ // brick
2717
+ orange: "#cc7a33",
2718
+ // muted amber
2719
+ yellow: "#c9a227",
2720
+ // gold (not neon)
2721
+ green: "#5b9357",
2722
+ // forest / sage
2723
+ blue: "#3b6ea5",
2724
+ // corporate blue
2725
+ purple: "#7d5ba6",
2726
+ // muted violet
2727
+ teal: "#3a9188",
2728
+ // teal
2729
+ cyan: "#4f96c4",
2730
+ // steel cyan
2731
+ gray: "#7e8a97",
2732
+ // cool gray
2733
+ black: "#1f2933",
2734
+ // slate ink
2735
+ white: "#f3f5f8"
2736
+ // panel
2737
+ }
2738
+ },
2739
+ dark: {
2740
+ bg: "#161b22",
2741
+ // deep slate (keynote dark)
2742
+ surface: "#202833",
2743
+ // raised panel
2744
+ overlay: "#29323e",
2745
+ // popovers, dropdowns
2746
+ border: "#38424f",
2747
+ // divider
2748
+ text: "#e6eaef",
2749
+ // off-white
2750
+ textMuted: "#9aa5b1",
2751
+ // secondary label
2752
+ textOnFillLight: "#ffffff",
2753
+ // light text on dark fills
2754
+ textOnFillDark: "#161b22",
2755
+ // dark text on light fills
2756
+ primary: "#5b9bd5",
2757
+ // lifted corporate blue
2758
+ secondary: "#8593a3",
2759
+ // slate gray, lifted
2760
+ accent: "#45b3a3",
2761
+ // teal, lifted
2762
+ destructive: "#e07b6e",
2763
+ // brick, lifted
2764
+ colors: {
2765
+ red: "#e07b6e",
2766
+ // brick
2767
+ orange: "#e0975a",
2768
+ // amber
2769
+ yellow: "#d9bd5a",
2770
+ // gold
2771
+ green: "#74b56e",
2772
+ // forest / sage
2773
+ blue: "#5b9bd5",
2774
+ // corporate blue
2775
+ purple: "#a585c9",
2776
+ // violet
2777
+ teal: "#45b3a3",
2778
+ // teal
2779
+ cyan: "#62b0d9",
2780
+ // steel cyan
2781
+ gray: "#95a1ae",
2782
+ // cool gray
2783
+ black: "#202833",
2784
+ // raised panel
2785
+ white: "#e6eaef"
2786
+ // off-white
2787
+ }
2788
+ }
2789
+ };
2790
+ registerPalette(slatePalette);
2791
+ }
2792
+ });
2793
+
2490
2794
  // src/palettes/solarized.ts
2491
2795
  var solarizedPalette;
2492
2796
  var init_solarized = __esm({
@@ -2582,6 +2886,120 @@ var init_solarized = __esm({
2582
2886
  }
2583
2887
  });
2584
2888
 
2889
+ // src/palettes/tidewater.ts
2890
+ var tidewaterPalette;
2891
+ var init_tidewater = __esm({
2892
+ "src/palettes/tidewater.ts"() {
2893
+ "use strict";
2894
+ init_registry();
2895
+ tidewaterPalette = {
2896
+ id: "tidewater",
2897
+ name: "Tidewater",
2898
+ light: {
2899
+ bg: "#eceff0",
2900
+ // weathered sea-mist paper
2901
+ surface: "#e0e4e3",
2902
+ // worn deck panel
2903
+ overlay: "#dadfdf",
2904
+ // popovers, dropdowns
2905
+ border: "#a9b2b3",
2906
+ // muted slate rule
2907
+ text: "#18313f",
2908
+ // ship's-log navy ink
2909
+ textMuted: "#51636b",
2910
+ // faded log entry
2911
+ textOnFillLight: "#f3f5f3",
2912
+ // weathered white
2913
+ textOnFillDark: "#162c38",
2914
+ // deep navy
2915
+ primary: "#1f4e6b",
2916
+ // deep-sea navy
2917
+ secondary: "#b08a4f",
2918
+ // rope / manila tan
2919
+ accent: "#c69a3e",
2920
+ // brass
2921
+ destructive: "#c1433a",
2922
+ // signal-flag red
2923
+ colors: {
2924
+ red: "#c1433a",
2925
+ // signal-flag red
2926
+ orange: "#cc7a38",
2927
+ // weathered amber
2928
+ yellow: "#d6bf5a",
2929
+ // brass gold
2930
+ green: "#4f8a6b",
2931
+ // sea-glass green
2932
+ blue: "#1f4e6b",
2933
+ // deep-sea navy
2934
+ purple: "#6a5a8c",
2935
+ // twilight harbor
2936
+ teal: "#3d8c8c",
2937
+ // sea-glass teal
2938
+ cyan: "#4f9bb5",
2939
+ // shallow water
2940
+ gray: "#8a8d86",
2941
+ // driftwood gray
2942
+ black: "#18313f",
2943
+ // navy ink
2944
+ white: "#e0e4e3"
2945
+ // deck panel
2946
+ }
2947
+ },
2948
+ dark: {
2949
+ bg: "#0f2230",
2950
+ // night-harbor deep sea
2951
+ surface: "#16303f",
2952
+ // raised hull
2953
+ overlay: "#1d3a4a",
2954
+ // popovers, dropdowns
2955
+ border: "#2c4856",
2956
+ // rigging line
2957
+ text: "#e6ebe8",
2958
+ // weathered white
2959
+ textMuted: "#9aaab0",
2960
+ // faded label
2961
+ textOnFillLight: "#f3f5f3",
2962
+ // weathered white
2963
+ textOnFillDark: "#0f2230",
2964
+ // deep sea
2965
+ primary: "#4f9bc4",
2966
+ // lifted sea blue
2967
+ secondary: "#c9a46a",
2968
+ // rope tan, lifted
2969
+ accent: "#d9b25a",
2970
+ // brass, lifted
2971
+ destructive: "#e06a5e",
2972
+ // signal red, lifted
2973
+ colors: {
2974
+ red: "#e06a5e",
2975
+ // signal-flag red
2976
+ orange: "#df9a52",
2977
+ // amber
2978
+ yellow: "#e0c662",
2979
+ // brass gold
2980
+ green: "#6fb58c",
2981
+ // sea-glass green
2982
+ blue: "#4f9bc4",
2983
+ // sea blue
2984
+ purple: "#9486bf",
2985
+ // twilight harbor
2986
+ teal: "#5cb0ac",
2987
+ // sea-glass teal
2988
+ cyan: "#62b4cf",
2989
+ // shallow water
2990
+ gray: "#9aa39c",
2991
+ // driftwood gray
2992
+ black: "#16303f",
2993
+ // raised hull
2994
+ white: "#e6ebe8"
2995
+ // weathered white
2996
+ }
2997
+ }
2998
+ };
2999
+ registerPalette(tidewaterPalette);
3000
+ }
3001
+ });
3002
+
2585
3003
  // src/palettes/tokyo-night.ts
2586
3004
  var tokyoNightPalette;
2587
3005
  var init_tokyo_night = __esm({
@@ -2857,7 +3275,8 @@ var init_monokai = __esm({
2857
3275
  // src/palettes/index.ts
2858
3276
  var palettes_exports = {};
2859
3277
  __export(palettes_exports, {
2860
- boldPalette: () => boldPalette,
3278
+ atlasPalette: () => atlasPalette,
3279
+ blueprintPalette: () => blueprintPalette,
2861
3280
  catppuccinPalette: () => catppuccinPalette,
2862
3281
  contrastText: () => contrastText,
2863
3282
  draculaPalette: () => draculaPalette,
@@ -2878,7 +3297,9 @@ __export(palettes_exports, {
2878
3297
  rosePinePalette: () => rosePinePalette,
2879
3298
  shade: () => shade,
2880
3299
  shapeFill: () => shapeFill,
3300
+ slatePalette: () => slatePalette,
2881
3301
  solarizedPalette: () => solarizedPalette,
3302
+ tidewaterPalette: () => tidewaterPalette,
2882
3303
  tint: () => tint,
2883
3304
  tokyoNightPalette: () => tokyoNightPalette
2884
3305
  });
@@ -2888,17 +3309,21 @@ var init_palettes = __esm({
2888
3309
  "use strict";
2889
3310
  init_registry();
2890
3311
  init_color_utils();
2891
- init_bold();
3312
+ init_atlas();
3313
+ init_blueprint();
2892
3314
  init_catppuccin();
2893
3315
  init_gruvbox();
2894
3316
  init_nord();
2895
3317
  init_one_dark();
2896
3318
  init_rose_pine();
3319
+ init_slate();
2897
3320
  init_solarized();
3321
+ init_tidewater();
2898
3322
  init_tokyo_night();
2899
3323
  init_dracula();
2900
3324
  init_monokai();
2901
- init_bold();
3325
+ init_atlas();
3326
+ init_blueprint();
2902
3327
  init_catppuccin();
2903
3328
  init_dracula();
2904
3329
  init_gruvbox();
@@ -2906,9 +3331,15 @@ var init_palettes = __esm({
2906
3331
  init_nord();
2907
3332
  init_one_dark();
2908
3333
  init_rose_pine();
3334
+ init_slate();
2909
3335
  init_solarized();
3336
+ init_tidewater();
2910
3337
  init_tokyo_night();
2911
3338
  palettes = {
3339
+ atlas: atlasPalette,
3340
+ blueprint: blueprintPalette,
3341
+ slate: slatePalette,
3342
+ tidewater: tidewaterPalette,
2912
3343
  nord: nordPalette,
2913
3344
  catppuccin: catppuccinPalette,
2914
3345
  solarized: solarizedPalette,
@@ -2917,8 +3348,7 @@ var init_palettes = __esm({
2917
3348
  oneDark: oneDarkPalette,
2918
3349
  rosePine: rosePinePalette,
2919
3350
  dracula: draculaPalette,
2920
- monokai: monokaiPalette,
2921
- bold: boldPalette
3351
+ monokai: monokaiPalette
2922
3352
  };
2923
3353
  }
2924
3354
  });
@@ -3428,6 +3858,9 @@ function controlsGroupCapsuleWidth(toggles) {
3428
3858
  }
3429
3859
  return w;
3430
3860
  }
3861
+ function isAppHostedControls(config, isExport) {
3862
+ return !isExport && config.controlsHost === "app" && !!config.controlsGroup && config.controlsGroup.toggles.length > 0;
3863
+ }
3431
3864
  function buildControlsGroupLayout(config, state) {
3432
3865
  const cg = config.controlsGroup;
3433
3866
  if (!cg || cg.toggles.length === 0) return void 0;
@@ -3481,6 +3914,7 @@ function buildControlsGroupLayout(config, state) {
3481
3914
  function computeLegendLayout(config, state, containerWidth) {
3482
3915
  const { groups, controls: configControls, mode } = config;
3483
3916
  const isExport = mode === "export";
3917
+ const gated = isAppHostedControls(config, isExport);
3484
3918
  const activeGroupName = state.activeGroup?.toLowerCase() ?? null;
3485
3919
  if (isExport && !activeGroupName) {
3486
3920
  return {
@@ -3491,7 +3925,7 @@ function computeLegendLayout(config, state, containerWidth) {
3491
3925
  pills: []
3492
3926
  };
3493
3927
  }
3494
- const controlsGroupLayout = isExport ? void 0 : buildControlsGroupLayout(config, state);
3928
+ const controlsGroupLayout = isExport || gated ? void 0 : buildControlsGroupLayout(config, state);
3495
3929
  const visibleGroups = config.showEmptyGroups ? groups : groups.filter((g) => g.entries.length > 0 || !!g.gradient);
3496
3930
  if (visibleGroups.length === 0 && (!configControls || configControls.length === 0) && !controlsGroupLayout) {
3497
3931
  return {
@@ -8345,8 +8779,8 @@ function computeScatterLabelGraphics(points, chartBounds, fontSize, symbolSize,
8345
8779
  const pt = points[i];
8346
8780
  const ptSize = pt.size ?? symbolSize;
8347
8781
  const minGap = ptSize / 2 + 4;
8348
- const labelWidth = pt.name.length * fontSize * 0.6 + 8;
8349
- const labelX = pt.px - labelWidth / 2;
8782
+ const labelWidth2 = pt.name.length * fontSize * 0.6 + 8;
8783
+ const labelX = pt.px - labelWidth2 / 2;
8350
8784
  let bestLabelY = 0;
8351
8785
  let bestOffset = Infinity;
8352
8786
  let placed = false;
@@ -8358,7 +8792,7 @@ function computeScatterLabelGraphics(points, chartBounds, fontSize, symbolSize,
8358
8792
  const candidate = {
8359
8793
  x: labelX,
8360
8794
  y: labelY,
8361
- w: labelWidth,
8795
+ w: labelWidth2,
8362
8796
  h: labelHeight
8363
8797
  };
8364
8798
  let collision = false;
@@ -8400,7 +8834,7 @@ function computeScatterLabelGraphics(points, chartBounds, fontSize, symbolSize,
8400
8834
  const labelRect = {
8401
8835
  x: labelX,
8402
8836
  y: bestLabelY,
8403
- w: labelWidth,
8837
+ w: labelWidth2,
8404
8838
  h: labelHeight
8405
8839
  };
8406
8840
  placedLabels.push(labelRect);
@@ -8436,7 +8870,7 @@ function computeScatterLabelGraphics(points, chartBounds, fontSize, symbolSize,
8436
8870
  shape: {
8437
8871
  x: labelX - bgPad,
8438
8872
  y: bestLabelY - bgPad,
8439
- width: labelWidth + bgPad * 2,
8873
+ width: labelWidth2 + bgPad * 2,
8440
8874
  height: labelHeight + bgPad * 2
8441
8875
  },
8442
8876
  style: { fill: bg },
@@ -15872,10 +16306,6 @@ function parseMap(content) {
15872
16306
  handleTag(trimmed, lineNumber);
15873
16307
  continue;
15874
16308
  }
15875
- if ((firstWord === "muted" || firstWord === "natural") && trimmed === firstWord) {
15876
- handleDirective(firstWord, "", lineNumber);
15877
- continue;
15878
- }
15879
16309
  if (DIRECTIVE_SET.has(firstWord) && !trimmed.slice(firstWord.length).trimStart().startsWith(":")) {
15880
16310
  handleDirective(
15881
16311
  firstWord,
@@ -15922,24 +16352,6 @@ function parseMap(content) {
15922
16352
  pushWarning(line12, `Duplicate directive "${key}" \u2014 last value wins.`);
15923
16353
  };
15924
16354
  switch (key) {
15925
- case "region":
15926
- dup(d.region);
15927
- d.region = value;
15928
- break;
15929
- case "projection":
15930
- dup(d.projection);
15931
- if (value && ![
15932
- "equirectangular",
15933
- "natural-earth",
15934
- "albers-usa",
15935
- "mercator"
15936
- ].includes(value))
15937
- pushWarning(
15938
- line12,
15939
- `Unknown projection "${value}" (expected equirectangular | natural-earth | albers-usa | mercator).`
15940
- );
15941
- d.projection = value;
15942
- break;
15943
16355
  case "region-metric": {
15944
16356
  dup(d.regionMetric);
15945
16357
  const { label: rmLabel, colorName: rmColor } = peelTrailingColorName(value);
@@ -15955,91 +16367,43 @@ function parseMap(content) {
15955
16367
  dup(d.flowMetric);
15956
16368
  d.flowMetric = value;
15957
16369
  break;
15958
- case "scale":
15959
- dup(d.scale);
15960
- {
15961
- const s = parseScale(value, line12);
15962
- if (s) d.scale = s;
15963
- }
15964
- break;
15965
- case "region-labels":
15966
- dup(d.regionLabels);
15967
- if (value && !["full", "abbrev", "off"].includes(value))
15968
- pushWarning(
15969
- line12,
15970
- `Unknown region-labels "${value}" (expected full | abbrev | off).`
15971
- );
15972
- d.regionLabels = value;
15973
- break;
15974
- case "poi-labels":
15975
- dup(d.poiLabels);
15976
- if (value && !["off", "auto", "all"].includes(value))
15977
- pushWarning(
15978
- line12,
15979
- `Unknown poi-labels "${value}" (expected off | auto | all).`
15980
- );
15981
- d.poiLabels = value;
15982
- break;
15983
- case "default-country":
15984
- dup(d.defaultCountry);
15985
- d.defaultCountry = value;
15986
- break;
15987
- case "default-state":
15988
- dup(d.defaultState);
15989
- d.defaultState = value;
16370
+ case "locale":
16371
+ dup(d.locale);
16372
+ d.locale = value;
15990
16373
  break;
15991
16374
  case "active-tag":
15992
16375
  dup(d.activeTag);
15993
16376
  d.activeTag = value;
15994
16377
  break;
16378
+ case "caption":
16379
+ dup(d.caption);
16380
+ d.caption = value;
16381
+ break;
16382
+ // ── Cosmetic `no-*` opt-outs: bare flags, idempotent (mirror `no-legend`,
16383
+ // no dup warning); each defaults the feature ON when absent. ──
15995
16384
  case "no-legend":
15996
16385
  d.noLegend = true;
15997
16386
  break;
15998
- case "no-insets":
15999
- d.noInsets = true;
16387
+ case "no-coastline":
16388
+ d.noCoastline = true;
16000
16389
  break;
16001
- case "relief":
16002
- d.relief = true;
16390
+ case "no-relief":
16391
+ d.noRelief = true;
16003
16392
  break;
16004
- case "muted":
16005
- case "natural":
16006
- if (d.basemapStyle !== void 0 && d.basemapStyle !== key)
16007
- pushWarning(
16008
- line12,
16009
- `Conflicting basemap dress \u2014 "${d.basemapStyle}" then "${key}"; last wins.`
16010
- );
16011
- d.basemapStyle = key;
16393
+ case "no-context-labels":
16394
+ d.noContextLabels = true;
16012
16395
  break;
16013
- case "subtitle":
16014
- dup(d.subtitle);
16015
- d.subtitle = value;
16396
+ case "no-region-labels":
16397
+ d.noRegionLabels = true;
16016
16398
  break;
16017
- case "caption":
16018
- dup(d.caption);
16019
- d.caption = value;
16399
+ case "no-poi-labels":
16400
+ d.noPoiLabels = true;
16401
+ break;
16402
+ case "no-colorize":
16403
+ d.noColorize = true;
16020
16404
  break;
16021
16405
  }
16022
16406
  }
16023
- function parseScale(value, line12) {
16024
- const toks = value.split(/\s+/).filter(Boolean);
16025
- const min = Number(toks[0]);
16026
- const max = Number(toks[1]);
16027
- if (!Number.isFinite(min) || !Number.isFinite(max)) {
16028
- pushError(line12, `scale requires numeric <min> <max> (got "${value}").`);
16029
- return null;
16030
- }
16031
- const scale = { min, max };
16032
- if (toks[2] === "center") {
16033
- const c = Number(toks[3]);
16034
- if (Number.isFinite(c)) scale.center = c;
16035
- else
16036
- pushError(
16037
- line12,
16038
- `scale center requires a number (got "${toks[3] ?? ""}").`
16039
- );
16040
- }
16041
- return scale;
16042
- }
16043
16407
  function handleTag(trimmed, line12) {
16044
16408
  const m = matchTagBlockHeading(trimmed);
16045
16409
  if (!m) {
@@ -16239,13 +16603,15 @@ function parseMap(content) {
16239
16603
  pushError(line12, `Edge has an empty endpoint: "${trimmed}".`);
16240
16604
  continue;
16241
16605
  }
16242
- const meta = k === links.length - 1 ? lastSplit.meta : {};
16606
+ const isLast = k === links.length - 1;
16607
+ const meta = isLast ? lastSplit.meta : {};
16608
+ const style = links[k].style === "arc" ? "arc" : "straight";
16243
16609
  edges.push({
16244
16610
  from,
16245
16611
  to,
16246
16612
  ...links[k].label !== void 0 && { label: links[k].label },
16247
16613
  directed: links[k].directed,
16248
- style: links[k].style,
16614
+ style,
16249
16615
  meta,
16250
16616
  lineNumber: line12
16251
16617
  });
@@ -16331,22 +16697,19 @@ var init_parser12 = __esm({
16331
16697
  LEG_ARROW_RE = /^(-[^>]*?->|->|~[^>]*?~>|~>|--)\s+(.+)$/;
16332
16698
  AT_RE = /(^|[\s,])at\s*:/i;
16333
16699
  DIRECTIVE_SET = /* @__PURE__ */ new Set([
16334
- "region",
16335
- "projection",
16336
16700
  "region-metric",
16337
16701
  "poi-metric",
16338
16702
  "flow-metric",
16339
- "scale",
16340
- "region-labels",
16341
- "poi-labels",
16342
- "default-country",
16343
- "default-state",
16703
+ "locale",
16344
16704
  "active-tag",
16705
+ "caption",
16345
16706
  "no-legend",
16346
- "no-insets",
16347
- "relief",
16348
- "subtitle",
16349
- "caption"
16707
+ "no-coastline",
16708
+ "no-relief",
16709
+ "no-context-labels",
16710
+ "no-region-labels",
16711
+ "no-poi-labels",
16712
+ "no-colorize"
16350
16713
  ]);
16351
16714
  }
16352
16715
  });
@@ -16524,6 +16887,21 @@ function parseBoxesAndLines(content) {
16524
16887
  }
16525
16888
  continue;
16526
16889
  }
16890
+ if (!contentStarted) {
16891
+ const metricMatch = trimmed.match(/^box-metric\s+(.+)$/i);
16892
+ if (metricMatch) {
16893
+ const { label, colorName } = peelTrailingColorName(
16894
+ metricMatch[1].trim()
16895
+ );
16896
+ result.boxMetric = label;
16897
+ if (colorName !== void 0) result.boxMetricColor = colorName;
16898
+ continue;
16899
+ }
16900
+ if (/^show-values$/i.test(trimmed)) {
16901
+ result.showValues = true;
16902
+ continue;
16903
+ }
16904
+ }
16527
16905
  if (!contentStarted) {
16528
16906
  const optMatch = trimmed.match(OPTION_NOCOLON_RE);
16529
16907
  if (optMatch) {
@@ -16902,6 +17280,19 @@ function parseNodeLine(trimmed, lineNum, metaAliasMap, diagnostics, nameAliasMap
16902
17280
  description = [metadata["description"]];
16903
17281
  delete metadata["description"];
16904
17282
  }
17283
+ let value;
17284
+ if (metadata["value"] !== void 0) {
17285
+ const raw = metadata["value"];
17286
+ const num = Number(raw);
17287
+ if (Number.isFinite(num)) {
17288
+ value = num;
17289
+ } else {
17290
+ diagnostics.push(
17291
+ makeDgmoError(lineNum, `value must be a number (got "${raw}")`, "error")
17292
+ );
17293
+ }
17294
+ delete metadata["value"];
17295
+ }
16905
17296
  if (split.alias) {
16906
17297
  nameAliasMap?.set(normalizeName(split.alias), label);
16907
17298
  }
@@ -16910,7 +17301,8 @@ function parseNodeLine(trimmed, lineNum, metaAliasMap, diagnostics, nameAliasMap
16910
17301
  label,
16911
17302
  lineNumber: lineNum,
16912
17303
  metadata,
16913
- ...description !== void 0 && { description }
17304
+ ...description !== void 0 && { description },
17305
+ ...value !== void 0 && { value }
16914
17306
  };
16915
17307
  }
16916
17308
  function splitTargetAndMeta(rest, metaAliasMap) {
@@ -24358,8 +24750,8 @@ function renderKanban(container, parsed, palette, isDark, options) {
24358
24750
  let metaY = separatorY + sCardSeparatorGap + sCardMetaFontSize;
24359
24751
  for (const meta of tagMeta) {
24360
24752
  cg.append("text").attr("x", cx + sCardPaddingX).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", onCardText).text(`${meta.label}: `);
24361
- const labelWidth = (meta.label.length + 2) * sCardMetaFontSize * 0.6;
24362
- cg.append("text").attr("x", cx + sCardPaddingX + labelWidth).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", onCardText).text(meta.value);
24753
+ const labelWidth2 = (meta.label.length + 2) * sCardMetaFontSize * 0.6;
24754
+ cg.append("text").attr("x", cx + sCardPaddingX + labelWidth2).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", onCardText).text(meta.value);
24363
24755
  metaY += sCardMetaLineHeight;
24364
24756
  }
24365
24757
  for (const detail of card.details) {
@@ -24703,8 +25095,8 @@ function renderSwimlaneCard(parent, cardLayout, tagGroups, activeTagGroup, palet
24703
25095
  let metaY = separatorY + sCardSeparatorGap + sCardMetaFontSize;
24704
25096
  for (const meta of tagMeta) {
24705
25097
  cg.append("text").attr("x", cx + sCardPaddingX).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", palette.textMuted).text(`${meta.label}: `);
24706
- const labelWidth = (meta.label.length + 2) * sCardMetaFontSize * 0.6;
24707
- cg.append("text").attr("x", cx + sCardPaddingX + labelWidth).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", onCardText).text(meta.value);
25098
+ const labelWidth2 = (meta.label.length + 2) * sCardMetaFontSize * 0.6;
25099
+ cg.append("text").attr("x", cx + sCardPaddingX + labelWidth2).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", onCardText).text(meta.value);
24708
25100
  metaY += sCardMetaLineHeight;
24709
25101
  }
24710
25102
  for (const detail of card.details) {
@@ -25538,8 +25930,8 @@ function classifyEREntities(tables, relationships) {
25538
25930
  }
25539
25931
  }
25540
25932
  const mmParticipants = /* @__PURE__ */ new Set();
25541
- for (const [id, neighbors] of tableStarNeighbors) {
25542
- if (neighbors.size >= 2) mmParticipants.add(id);
25933
+ for (const [id, neighbors2] of tableStarNeighbors) {
25934
+ if (neighbors2.size >= 2) mmParticipants.add(id);
25543
25935
  }
25544
25936
  const indegreeValues = Object.values(indegreeMap);
25545
25937
  const mean = indegreeValues.reduce((a, b) => a + b, 0) / indegreeValues.length;
@@ -26122,7 +26514,18 @@ function fitLabelToHeader(label, nodeWidth, maxLines) {
26122
26514
  const truncated = label.length > maxChars ? label.slice(0, maxChars - 1) + "\u2026" : label;
26123
26515
  return { lines: [truncated], fontSize: MIN_NODE_FONT_SIZE };
26124
26516
  }
26125
- function nodeColors(node, tagGroups, activeGroupName, palette, isDark, solid) {
26517
+ function nodeColors(node, tagGroups, activeGroupName, palette, isDark, value, solid) {
26518
+ const neutralFill = mix(palette.bg, palette.text, isDark ? 90 : 95);
26519
+ if (value.active) {
26520
+ const fill3 = node.value !== void 0 ? value.fillForValue(node.value) : neutralFill;
26521
+ const stroke3 = value.hue;
26522
+ const text2 = contrastText(
26523
+ fill3,
26524
+ palette.textOnFillLight,
26525
+ palette.textOnFillDark
26526
+ );
26527
+ return { fill: fill3, stroke: stroke3, text: text2 };
26528
+ }
26126
26529
  const tagColor = resolveTagColor(
26127
26530
  node.metadata,
26128
26531
  [...tagGroups],
@@ -26212,7 +26615,8 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26212
26615
  controlsExpanded,
26213
26616
  onToggleDescriptions,
26214
26617
  onToggleControlsExpand,
26215
- exportMode = false
26618
+ exportMode = false,
26619
+ controlsHost
26216
26620
  } = options ?? {};
26217
26621
  d3Selection6.select(container).selectAll(":not([data-d3-tooltip])").remove();
26218
26622
  const width = exportDims?.width ?? container.clientWidth;
@@ -26230,21 +26634,65 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26230
26634
  const sGroupLabelZone = sctx.structural(GROUP_LABEL_ZONE);
26231
26635
  const sTitleFontSize = sctx.text(TITLE_FONT_SIZE);
26232
26636
  const sTitleY = sctx.structural(TITLE_Y);
26233
- const sLegendHeight = sctx.structural(
26637
+ const nodeValues = parsed.nodes.filter((n) => n.value !== void 0).map((n) => n.value);
26638
+ const hasRamp = nodeValues.length > 0;
26639
+ const allNonNegative = hasRamp && nodeValues.every((v) => v >= 0);
26640
+ const rampMin = allNonNegative ? 0 : Math.min(...nodeValues);
26641
+ const rampMax = Math.max(...nodeValues);
26642
+ const rampHue = resolveColor(parsed.boxMetricColor ?? "", palette) ?? palette.primary;
26643
+ const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
26644
+ const fillForValue = (v) => {
26645
+ const t = rampMax > rampMin ? (v - rampMin) / (rampMax - rampMin) : 1;
26646
+ const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
26647
+ return mix(rampHue, rampBase, pct);
26648
+ };
26649
+ const VALUE_NAME = hasRamp ? parsed.boxMetric?.trim() || "Value" : null;
26650
+ const matchColorGroup = (v) => {
26651
+ const lv = v.trim().toLowerCase();
26652
+ if (lv === "none") return null;
26653
+ const tg = parsed.tagGroups.find((g) => g.name.toLowerCase() === lv);
26654
+ if (tg) return tg.name;
26655
+ if (lv === VALUE_NAME?.toLowerCase()) return VALUE_NAME;
26656
+ return v;
26657
+ };
26658
+ const override = activeTagGroup;
26659
+ let activeGroup;
26660
+ if (override !== void 0) {
26661
+ activeGroup = override === null ? null : matchColorGroup(override);
26662
+ } else if (parsed.options["active-tag"] !== void 0) {
26663
+ activeGroup = matchColorGroup(parsed.options["active-tag"]);
26664
+ } else {
26665
+ activeGroup = VALUE_NAME ?? (parsed.tagGroups.length > 0 ? parsed.tagGroups[0].name : null);
26666
+ }
26667
+ const activeIsValue = VALUE_NAME !== null && activeGroup === VALUE_NAME;
26668
+ const valueGroup = VALUE_NAME !== null ? {
26669
+ name: VALUE_NAME,
26670
+ entries: [],
26671
+ gradient: {
26672
+ min: rampMin,
26673
+ max: rampMax,
26674
+ hue: rampHue,
26675
+ base: rampBase
26676
+ }
26677
+ } : null;
26678
+ const legendGroups = [
26679
+ ...valueGroup ? [valueGroup] : [],
26680
+ ...parsed.tagGroups
26681
+ ];
26682
+ const reserveHasDescriptions = parsed.nodes.some(
26683
+ (n) => n.description && n.description.length > 0
26684
+ );
26685
+ const willRenderLegend = legendGroups.length > 0 || reserveHasDescriptions && controlsHost !== "app";
26686
+ const sLegendHeight = willRenderLegend ? sctx.structural(
26234
26687
  getMaxLegendReservedHeight(
26235
26688
  {
26236
- groups: parsed.tagGroups,
26689
+ groups: legendGroups,
26237
26690
  position: { placement: "top-center", titleRelation: "below-title" },
26238
26691
  mode: exportMode ? "export" : "preview"
26239
26692
  },
26240
26693
  width
26241
26694
  )
26242
- );
26243
- const activeGroup = resolveActiveTagGroup(
26244
- parsed.tagGroups,
26245
- parsed.options["active-tag"],
26246
- activeTagGroup
26247
- );
26695
+ ) : 0;
26248
26696
  const hidden = hiddenTagValues ?? parsed.initialHiddenTagValues;
26249
26697
  const nodeMap = /* @__PURE__ */ new Map();
26250
26698
  for (const node of parsed.nodes) nodeMap.set(node.label, node);
@@ -26255,7 +26703,7 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26255
26703
  const hasAnyDescriptions = parsed.nodes.some(
26256
26704
  (n) => n.description && n.description.length > 0
26257
26705
  );
26258
- const needsLegend = parsed.tagGroups.length > 0 || hasAnyDescriptions && onToggleDescriptions;
26706
+ const needsLegend = legendGroups.length > 0 || hasAnyDescriptions && onToggleDescriptions;
26259
26707
  const legendH = needsLegend ? sLegendHeight + 8 : 0;
26260
26708
  const groupLabelsSet = new Set(layout.groups.map((g) => g.label));
26261
26709
  let labelZoneExtension = 0;
@@ -26461,12 +26909,16 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26461
26909
  activeGroup,
26462
26910
  palette,
26463
26911
  isDark,
26912
+ { active: activeIsValue, hue: rampHue, fillForValue },
26464
26913
  parsed.options["solid-fill"] === "on"
26465
26914
  );
26466
26915
  const nodeG = diagramG.append("g").attr("class", "bl-node").attr("transform", `translate(${ln.x},${ln.y})`).attr("data-line-number", node.lineNumber).attr("data-node-id", node.label).style("cursor", onClickItem ? "pointer" : "default").style("--bl-node-stroke", colors.stroke);
26467
26916
  for (const [key, val] of Object.entries(node.metadata)) {
26468
26917
  nodeG.attr(`data-tag-${key.toLowerCase()}`, val.toLowerCase());
26469
26918
  }
26919
+ if (node.value !== void 0) {
26920
+ nodeG.attr("data-value", node.value);
26921
+ }
26470
26922
  if (onClickItem) {
26471
26923
  nodeG.on("click", (event) => {
26472
26924
  const target = event.target;
@@ -26550,14 +27002,30 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26550
27002
  nodeG.append("text").attr("x", 0).attr("y", -totalH / 2 + lineH / 2 + li * lineH).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", fitted.fontSize).attr("font-weight", "600").attr("fill", colors.text).text(fitted.lines[li]);
26551
27003
  }
26552
27004
  }
27005
+ if (parsed.showValues && node.value !== void 0) {
27006
+ const valueText = String(node.value);
27007
+ const descShown = !!(desc && desc.length > 0 && !hideDescriptions);
27008
+ if (descShown) {
27009
+ const padX = 6;
27010
+ const padY = 5;
27011
+ const bw = valueText.length * VALUE_FONT_SIZE * CHAR_WIDTH_RATIO2 + 8;
27012
+ const bh = VALUE_FONT_SIZE + 4;
27013
+ const bx = ln.width / 2 - bw - 4;
27014
+ const by = -ln.height / 2 + 4;
27015
+ nodeG.append("rect").attr("x", bx).attr("y", by).attr("width", bw).attr("height", bh).attr("rx", 3).attr("fill", palette.bg).attr("opacity", 0.85);
27016
+ nodeG.append("text").attr("class", "bl-node-value").attr("x", bx + bw - padX).attr("y", by + padY).attr("text-anchor", "end").attr("dominant-baseline", "central").attr("font-size", VALUE_FONT_SIZE).attr("font-weight", "600").attr("fill", palette.textMuted).text(valueText);
27017
+ } else {
27018
+ nodeG.append("text").attr("class", "bl-node-value").attr("x", 0).attr("y", ln.height / 2 - VALUE_FONT_SIZE).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", VALUE_FONT_SIZE).attr("font-weight", "600").attr("fill", colors.text).attr("opacity", 0.8).text(valueText);
27019
+ }
27020
+ }
26553
27021
  }
26554
27022
  const hasDescriptions = parsed.nodes.some(
26555
27023
  (n) => n.description && n.description.length > 0
26556
27024
  );
26557
- const hasLegend = parsed.tagGroups.length > 0 || hasDescriptions;
27025
+ const hasLegend = legendGroups.length > 0 || hasDescriptions && controlsHost !== "app";
26558
27026
  if (hasLegend) {
26559
27027
  let controlsGroup;
26560
- if (hasDescriptions && onToggleDescriptions) {
27028
+ if (hasDescriptions && (onToggleDescriptions || controlsHost === "app")) {
26561
27029
  controlsGroup = {
26562
27030
  toggles: [
26563
27031
  {
@@ -26572,10 +27040,17 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26572
27040
  };
26573
27041
  }
26574
27042
  const legendConfig = {
26575
- groups: parsed.tagGroups,
27043
+ groups: legendGroups,
26576
27044
  position: { placement: "top-center", titleRelation: "below-title" },
26577
27045
  mode: exportMode ? "export" : "preview",
26578
- ...controlsGroup !== void 0 && { controlsGroup }
27046
+ // Keep inactive sibling tag groups visible as collapsed pills so the user
27047
+ // can click one to flip the active colouring dimension (preview only —
27048
+ // export shows just the active group). Without this, declaring a second
27049
+ // tag group (e.g. Team) leaves it invisible whenever another group is
27050
+ // active. The app's BoxesAndLinesPreview already wires pill clicks.
27051
+ showInactivePills: true,
27052
+ ...controlsGroup !== void 0 && { controlsGroup },
27053
+ ...controlsHost !== void 0 && { controlsHost }
26579
27054
  };
26580
27055
  const legendState = {
26581
27056
  activeGroup,
@@ -26620,7 +27095,7 @@ function renderBoxesAndLinesForExport(container, parsed, layout, palette, isDark
26620
27095
  }
26621
27096
  });
26622
27097
  }
26623
- var DIAGRAM_PADDING6, NODE_FONT_SIZE, MIN_NODE_FONT_SIZE, EDGE_LABEL_FONT_SIZE4, EDGE_STROKE_WIDTH5, NODE_STROKE_WIDTH5, NODE_RX, COLLAPSE_BAR_HEIGHT3, ARROWHEAD_W2, ARROWHEAD_H2, DESC_FONT_SIZE, DESC_LINE_HEIGHT, MAX_DESC_LINES, CHAR_WIDTH_RATIO2, NODE_TEXT_PADDING, GROUP_RX, GROUP_LABEL_FONT_SIZE, GROUP_LABEL_ZONE, lineGeneratorLR, lineGeneratorTB;
27098
+ var DIAGRAM_PADDING6, NODE_FONT_SIZE, MIN_NODE_FONT_SIZE, EDGE_LABEL_FONT_SIZE4, EDGE_STROKE_WIDTH5, NODE_STROKE_WIDTH5, NODE_RX, COLLAPSE_BAR_HEIGHT3, ARROWHEAD_W2, ARROWHEAD_H2, DESC_FONT_SIZE, DESC_LINE_HEIGHT, MAX_DESC_LINES, CHAR_WIDTH_RATIO2, NODE_TEXT_PADDING, GROUP_RX, GROUP_LABEL_FONT_SIZE, GROUP_LABEL_ZONE, RAMP_FLOOR, VALUE_FONT_SIZE, lineGeneratorLR, lineGeneratorTB;
26624
27099
  var init_renderer6 = __esm({
26625
27100
  "src/boxes-and-lines/renderer.ts"() {
26626
27101
  "use strict";
@@ -26629,6 +27104,7 @@ var init_renderer6 = __esm({
26629
27104
  init_legend_layout();
26630
27105
  init_title_constants();
26631
27106
  init_color_utils();
27107
+ init_colors();
26632
27108
  init_tag_groups();
26633
27109
  init_inline_markdown();
26634
27110
  init_wrapped_desc();
@@ -26651,6 +27127,8 @@ var init_renderer6 = __esm({
26651
27127
  GROUP_RX = 8;
26652
27128
  GROUP_LABEL_FONT_SIZE = 14;
26653
27129
  GROUP_LABEL_ZONE = 32;
27130
+ RAMP_FLOOR = 15;
27131
+ VALUE_FONT_SIZE = 11;
26654
27132
  lineGeneratorLR = d3Shape4.line().x((d) => d.x).y((d) => d.y).curve(d3Shape4.curveBasis);
26655
27133
  lineGeneratorTB = d3Shape4.line().x((d) => d.x).y((d) => d.y).curve(d3Shape4.curveBasis);
26656
27134
  }
@@ -27822,8 +28300,9 @@ function renderMindmap(container, parsed, layout, palette, isDark, onClickItem,
27822
28300
  const containerHeight = exportDims?.height ?? (container.getBoundingClientRect().height || 600);
27823
28301
  d3Selection7.select(container).selectAll("*").remove();
27824
28302
  const svg = d3Selection7.select(container).append("svg").attr("width", containerWidth).attr("height", containerHeight).attr("viewBox", `0 0 ${containerWidth} ${containerHeight}`).attr("preserveAspectRatio", "xMidYMin meet").style("font-family", FONT_FAMILY);
28303
+ const appHosted = options?.controlsHost === "app";
27825
28304
  const hasControls = !!options?.onToggleColorByDepth || !!options?.onToggleDescriptions;
27826
- const hasLegend = parsed.tagGroups.length > 0 || hasControls;
28305
+ const hasLegend = parsed.tagGroups.length > 0 || hasControls && !appHosted;
27827
28306
  const fixedLegend = !isExport && hasLegend;
27828
28307
  const legendReserve = fixedLegend ? getMaxLegendReservedHeight(
27829
28308
  {
@@ -27917,7 +28396,10 @@ function renderMindmap(container, parsed, layout, palette, isDark, onClickItem,
27917
28396
  }),
27918
28397
  position: { placement: "top-center", titleRelation: "below-title" },
27919
28398
  mode: options?.exportMode ? "export" : "preview",
27920
- ...controlsToggles !== void 0 && { controlsGroup: controlsToggles }
28399
+ ...controlsToggles !== void 0 && { controlsGroup: controlsToggles },
28400
+ ...options?.controlsHost !== void 0 && {
28401
+ controlsHost: options.controlsHost
28402
+ }
27921
28403
  };
27922
28404
  const legendState = {
27923
28405
  activeGroup: options?.colorByDepth ? null : activeTagGroup !== void 0 ? activeTagGroup : parsed.options["active-tag"] ?? null,
@@ -28360,8 +28842,8 @@ function computeFieldAlignX(children) {
28360
28842
  for (const child of children) {
28361
28843
  if (child.metadata["_labelField"] === "true" && child.children.length >= 2) {
28362
28844
  const labelEl = child.children[0];
28363
- const labelWidth = labelEl.label.length * CHAR_WIDTH5;
28364
- maxLabelWidth = Math.max(maxLabelWidth, labelWidth);
28845
+ const labelWidth2 = labelEl.label.length * CHAR_WIDTH5;
28846
+ maxLabelWidth = Math.max(maxLabelWidth, labelWidth2);
28365
28847
  labelFieldCount++;
28366
28848
  }
28367
28849
  }
@@ -33326,7 +33808,7 @@ function hasRoles(node) {
33326
33808
  function computeNodeWidth2(node, expanded, options) {
33327
33809
  const badgeVal = node.computedConcurrentInvocations === 0 && node.computedInstances > 1 ? node.computedInstances : 0;
33328
33810
  const badgeLen = badgeVal > 0 ? `${badgeVal}x`.length + 2 : 0;
33329
- const labelWidth = (node.label.length + badgeLen) * CHAR_WIDTH7 + PADDING_X3;
33811
+ const labelWidth2 = (node.label.length + badgeLen) * CHAR_WIDTH7 + PADDING_X3;
33330
33812
  const allKeys = [];
33331
33813
  if (node.computedRps > 0) allKeys.push("RPS");
33332
33814
  if (expanded) {
@@ -33370,7 +33852,7 @@ function computeNodeWidth2(node, expanded, options) {
33370
33852
  allKeys.push("overflow");
33371
33853
  }
33372
33854
  }
33373
- if (allKeys.length === 0) return Math.max(MIN_NODE_WIDTH2, labelWidth);
33855
+ if (allKeys.length === 0) return Math.max(MIN_NODE_WIDTH2, labelWidth2);
33374
33856
  const maxKeyLen = Math.max(...allKeys.map((k) => k.length));
33375
33857
  let maxRowWidth = 0;
33376
33858
  if (node.computedRps > 0) {
@@ -33458,7 +33940,7 @@ function computeNodeWidth2(node, expanded, options) {
33458
33940
  truncated.length * META_CHAR_WIDTH3 + PADDING_X3
33459
33941
  );
33460
33942
  }
33461
- return Math.max(MIN_NODE_WIDTH2, labelWidth, maxRowWidth + 20, descWidth);
33943
+ return Math.max(MIN_NODE_WIDTH2, labelWidth2, maxRowWidth + 20, descWidth);
33462
33944
  }
33463
33945
  function computeNodeHeight2(node, expanded, options) {
33464
33946
  const propCount = countDisplayProps(node, expanded, options);
@@ -35008,8 +35490,9 @@ function computeInfraLegendGroups(nodes, tagGroups, palette, edges) {
35008
35490
  }
35009
35491
  return groups;
35010
35492
  }
35011
- function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDark, activeGroup, playback, exportMode = false) {
35493
+ function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDark, activeGroup, playback, exportMode = false, controlsHost) {
35012
35494
  if (legendGroups.length === 0 && !playback) return;
35495
+ const appHostedPlayback = controlsHost === "app" && !!playback;
35013
35496
  const legendG = rootSvg.append("g").attr("transform", `translate(0, ${legendY})`);
35014
35497
  if (activeGroup) {
35015
35498
  legendG.attr("data-legend-active", activeGroup.toLowerCase());
@@ -35018,14 +35501,29 @@ function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDa
35018
35501
  name: g.name,
35019
35502
  entries: g.entries.map((e) => ({ value: e.value, color: e.color }))
35020
35503
  }));
35021
- if (playback) {
35504
+ if (playback && !appHostedPlayback) {
35022
35505
  allGroups.push({ name: "Playback", entries: [] });
35023
35506
  }
35024
35507
  const legendConfig = {
35025
35508
  groups: allGroups,
35026
35509
  position: { placement: "top-center", titleRelation: "below-title" },
35027
35510
  mode: exportMode ? "export" : "preview",
35028
- showEmptyGroups: true
35511
+ showEmptyGroups: true,
35512
+ ...appHostedPlayback && {
35513
+ controlsHost: "app",
35514
+ controlsGroup: {
35515
+ toggles: [
35516
+ {
35517
+ id: "playback",
35518
+ type: "toggle",
35519
+ label: "Playback",
35520
+ active: true,
35521
+ onToggle: () => {
35522
+ }
35523
+ }
35524
+ ]
35525
+ }
35526
+ }
35029
35527
  };
35030
35528
  const legendState = { activeGroup };
35031
35529
  renderLegendD3(
@@ -35076,8 +35574,9 @@ function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDa
35076
35574
  }
35077
35575
  }
35078
35576
  }
35079
- function renderInfra(container, layout, palette, isDark, title, titleLineNumber, tagGroups, activeGroup, animate, playback, expandedNodeIds, exportMode, collapsedNodes) {
35577
+ function renderInfra(container, layout, palette, isDark, title, titleLineNumber, tagGroups, activeGroup, animate, playback, expandedNodeIds, exportMode, collapsedNodes, controlsHost) {
35080
35578
  d3Selection11.select(container).selectAll(":not([data-d3-tooltip])").remove();
35579
+ const appHostedPlayback = controlsHost === "app" && !!playback;
35081
35580
  const ctx = ScaleContext.identity();
35082
35581
  const sc = buildScaledConstants(ctx);
35083
35582
  const legendGroups = computeInfraLegendGroups(
@@ -35086,7 +35585,7 @@ function renderInfra(container, layout, palette, isDark, title, titleLineNumber,
35086
35585
  palette,
35087
35586
  layout.edges
35088
35587
  );
35089
- const hasLegend = legendGroups.length > 0 || !!playback;
35588
+ const hasLegend = legendGroups.length > 0 || !!playback && !appHostedPlayback;
35090
35589
  const fixedLegend = !exportMode && hasLegend;
35091
35590
  const legendDynamicH = hasLegend ? getMaxLegendReservedHeight(
35092
35591
  {
@@ -35230,7 +35729,8 @@ function renderInfra(container, layout, palette, isDark, title, titleLineNumber,
35230
35729
  isDark,
35231
35730
  activeGroup ?? null,
35232
35731
  playback ?? void 0,
35233
- exportMode
35732
+ exportMode,
35733
+ controlsHost
35234
35734
  );
35235
35735
  legendSvg.selectAll(".infra-legend-group").style("pointer-events", "auto");
35236
35736
  } else {
@@ -35243,7 +35743,8 @@ function renderInfra(container, layout, palette, isDark, title, titleLineNumber,
35243
35743
  isDark,
35244
35744
  activeGroup ?? null,
35245
35745
  playback ?? void 0,
35246
- exportMode
35746
+ exportMode,
35747
+ controlsHost
35247
35748
  );
35248
35749
  }
35249
35750
  }
@@ -43100,6 +43601,9 @@ function renderTechRadar(container, parsed, palette, isDark, onClickItem, export
43100
43601
  onToggle: (active) => options.onToggleListing(active)
43101
43602
  }
43102
43603
  ]
43604
+ },
43605
+ ...options.controlsHost !== void 0 && {
43606
+ controlsHost: options.controlsHost
43103
43607
  }
43104
43608
  };
43105
43609
  const legendState = {
@@ -44922,7 +45426,7 @@ function computeCycleLayout(parsed, options) {
44922
45426
  const circleNodes = parsed.options["circle-nodes"] === "true";
44923
45427
  const nodeDims = parsed.nodes.map((node) => {
44924
45428
  const hasDesc = !hideDescriptions && node.description.length > 0;
44925
- const labelWidth = Math.max(
45429
+ const labelWidth2 = Math.max(
44926
45430
  MIN_NODE_WIDTH4,
44927
45431
  node.label.length * LABEL_CHAR_W + NODE_PAD_X * 2
44928
45432
  );
@@ -44931,12 +45435,12 @@ function computeCycleLayout(parsed, options) {
44931
45435
  }
44932
45436
  if (!hasDesc) {
44933
45437
  return {
44934
- width: Math.min(MAX_NODE_WIDTH3, labelWidth),
45438
+ width: Math.min(MAX_NODE_WIDTH3, labelWidth2),
44935
45439
  height: PLAIN_NODE_HEIGHT,
44936
45440
  wrappedDesc: []
44937
45441
  };
44938
45442
  }
44939
- return chooseDescribedRectDims(node.description, labelWidth);
45443
+ return chooseDescribedRectDims(node.description, labelWidth2);
44940
45444
  });
44941
45445
  if (circleNodes) {
44942
45446
  const maxDiam = Math.max(...nodeDims.map((d) => d.width));
@@ -45132,10 +45636,10 @@ function computeCycleLayout(parsed, options) {
45132
45636
  scale
45133
45637
  };
45134
45638
  }
45135
- function chooseDescribedRectDims(description, labelWidth) {
45639
+ function chooseDescribedRectDims(description, labelWidth2) {
45136
45640
  const minW = Math.min(
45137
45641
  MAX_NODE_WIDTH3,
45138
- Math.max(MIN_NODE_WIDTH4, labelWidth, DESC_MIN_WIDTH)
45642
+ Math.max(MIN_NODE_WIDTH4, labelWidth2, DESC_MIN_WIDTH)
45139
45643
  );
45140
45644
  let best = null;
45141
45645
  let bestScore = Infinity;
@@ -45564,7 +46068,8 @@ function renderCycle(container, parsed, palette, isDark, onClickItem, exportDims
45564
46068
  const hideDescriptions = (renderOptions?.hideDescriptions ?? false) || parsed.options["no-descriptions"] === "true" || viewState?.hd === true;
45565
46069
  const showDescriptions = !hideDescriptions;
45566
46070
  const hasDescriptions = parsed.nodes.some((n) => n.description.length > 0) || parsed.edges.some((e) => e.description.length > 0);
45567
- const hasLegend = hasDescriptions && !!renderOptions?.onToggleDescriptions;
46071
+ const appHostedControls = renderOptions?.controlsHost === "app";
46072
+ const hasLegend = !appHostedControls && hasDescriptions && !!renderOptions?.onToggleDescriptions;
45568
46073
  const showTitle = !!parsed.title && parsed.options["no-title"] !== "on";
45569
46074
  const legendOffset = hasLegend ? sLegendHeight : 0;
45570
46075
  const layoutHeight = height - (showTitle ? sTitleAreaHeight : 0) - legendOffset;
@@ -45601,7 +46106,10 @@ function renderCycle(container, parsed, palette, isDark, onClickItem, exportDims
45601
46106
  groups: [],
45602
46107
  position: { placement: "top-center", titleRelation: "below-title" },
45603
46108
  mode: renderOptions?.exportMode ? "export" : "preview",
45604
- controlsGroup
46109
+ controlsGroup,
46110
+ ...renderOptions?.controlsHost !== void 0 && {
46111
+ controlsHost: renderOptions.controlsHost
46112
+ }
45605
46113
  };
45606
46114
  const legendState = {
45607
46115
  activeGroup: null,
@@ -45855,7 +46363,7 @@ var init_renderer15 = __esm({
45855
46363
  });
45856
46364
 
45857
46365
  // src/map/geo.ts
45858
- import { feature } from "topojson-client";
46366
+ import { feature, neighbors } from "topojson-client";
45859
46367
  import { geoBounds, geoArea } from "d3-geo";
45860
46368
  function geomObject(topo) {
45861
46369
  const key = Object.keys(topo.objects)[0];
@@ -45873,6 +46381,29 @@ function featureIndex(topo) {
45873
46381
  }
45874
46382
  return idx;
45875
46383
  }
46384
+ function buildAdjacency(topo) {
46385
+ const cached = adjacencyCache.get(topo);
46386
+ if (cached) return cached;
46387
+ const geometries = geomObject(topo).geometries;
46388
+ const nb = neighbors(geometries);
46389
+ const sets = /* @__PURE__ */ new Map();
46390
+ geometries.forEach((g, i) => {
46391
+ if (!g.type || g.type === "null") return;
46392
+ let set = sets.get(g.id);
46393
+ if (!set) {
46394
+ set = /* @__PURE__ */ new Set();
46395
+ sets.set(g.id, set);
46396
+ }
46397
+ for (const j of nb[i] ?? []) {
46398
+ const nid = geometries[j]?.id;
46399
+ if (nid && nid !== g.id) set.add(nid);
46400
+ }
46401
+ });
46402
+ const out = /* @__PURE__ */ new Map();
46403
+ for (const [iso, set] of sets) out.set(iso, [...set].sort());
46404
+ adjacencyCache.set(topo, out);
46405
+ return out;
46406
+ }
45876
46407
  function decodeFeatures(topo) {
45877
46408
  return geomObject(topo).geometries.map((g) => {
45878
46409
  const f = feature(topo, g);
@@ -46068,11 +46599,12 @@ function unionLongitudes(lons) {
46068
46599
  }
46069
46600
  return { west: pts[gapIdx], east: pts[gapIdx - 1] + 360 };
46070
46601
  }
46071
- var fold, EDGE_EPS, DETACH_GAP_DEG, DETACH_AREA_FRAC;
46602
+ var fold, adjacencyCache, EDGE_EPS, DETACH_GAP_DEG, DETACH_AREA_FRAC;
46072
46603
  var init_geo = __esm({
46073
46604
  "src/map/geo.ts"() {
46074
46605
  "use strict";
46075
46606
  fold = (s) => s.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase().trim();
46607
+ adjacencyCache = /* @__PURE__ */ new WeakMap();
46076
46608
  EDGE_EPS = 1e-9;
46077
46609
  DETACH_GAP_DEG = 10;
46078
46610
  DETACH_AREA_FRAC = 0.25;
@@ -46093,6 +46625,12 @@ function looksUS(lat, lon) {
46093
46625
  if (lat < 15 || lat > 72) return false;
46094
46626
  return lon >= -180 && lon <= -64 || lon >= 172;
46095
46627
  }
46628
+ function looksNorthAmericaNeighbor(lat, lon) {
46629
+ return lat >= 14 && lat <= 72 && lon >= -141 && lon <= -52;
46630
+ }
46631
+ function isWholeSphere(bb) {
46632
+ return bb[0][0] <= -179 && bb[1][0] >= 179 && bb[0][1] <= -89 && bb[1][1] >= 89;
46633
+ }
46096
46634
  function resolveMap(parsed, data) {
46097
46635
  const diagnostics = [...parsed.diagnostics];
46098
46636
  const err = (line12, message, code) => {
@@ -46103,9 +46641,6 @@ function resolveMap(parsed, data) {
46103
46641
  };
46104
46642
  const result = {
46105
46643
  title: parsed.title,
46106
- ...parsed.directives.subtitle !== void 0 && {
46107
- subtitle: parsed.directives.subtitle
46108
- },
46109
46644
  ...parsed.directives.caption !== void 0 && {
46110
46645
  caption: parsed.directives.caption
46111
46646
  },
@@ -46115,7 +46650,7 @@ function resolveMap(parsed, data) {
46115
46650
  // renderer's job (step 4) — the resolver only carries `tags` + `tagGroups`
46116
46651
  // through; it never resolves a tag value to a palette color (#10).
46117
46652
  directives: { ...parsed.directives },
46118
- basemaps: { world: "coarse", subdivisions: [] },
46653
+ basemaps: { world: "detail", subdivisions: [] },
46119
46654
  regions: [],
46120
46655
  pois: [],
46121
46656
  edges: [],
@@ -46124,7 +46659,8 @@ function resolveMap(parsed, data) {
46124
46659
  [-180, -85],
46125
46660
  [180, 85]
46126
46661
  ],
46127
- projection: "natural-earth",
46662
+ projection: "equirectangular",
46663
+ poiFrameContainers: [],
46128
46664
  diagnostics,
46129
46665
  error: parsed.error
46130
46666
  };
@@ -46134,7 +46670,10 @@ function resolveMap(parsed, data) {
46134
46670
  ...[...countryIndex.values()].map((v) => v.name),
46135
46671
  ...[...usStateIndex.values()].map((v) => v.name)
46136
46672
  ];
46137
- const usScoped = parsed.directives.region === "us-states" || parsed.directives.defaultCountry?.toUpperCase() === "US" || parsed.regions.some((r) => {
46673
+ const localeRaw = parsed.directives.locale?.toUpperCase();
46674
+ const localeCountry = localeRaw ? localeRaw.split("-")[0] : void 0;
46675
+ const localeSubdivision = localeRaw && /^[A-Z]{2}-/.test(localeRaw) ? localeRaw : void 0;
46676
+ const usScoped = localeCountry === "US" || parsed.regions.some((r) => {
46138
46677
  const f = fold(r.name);
46139
46678
  return usStateIndex.has(f) && !countryIndex.has(f);
46140
46679
  }) || parsed.regions.some(
@@ -46285,7 +46824,7 @@ function resolveMap(parsed, data) {
46285
46824
  if (!scope)
46286
46825
  warn(
46287
46826
  line12,
46288
- `"${name}" is ambiguous \u2014 resolved to the most-populous match.`,
46827
+ `"${name}" is ambiguous \u2014 resolved to the most-populous match. Set a default with \`locale <ISO>\` (e.g. \`locale US\` / \`locale US-GA\`) to steer it.`,
46289
46828
  "W_MAP_AMBIGUOUS_NAME"
46290
46829
  );
46291
46830
  }
@@ -46298,17 +46837,21 @@ function resolveMap(parsed, data) {
46298
46837
  return fold(pos.name);
46299
46838
  };
46300
46839
  const poiCountries = [];
46301
- let anyNonUsPoi = false;
46840
+ let anyUsPoi = false;
46841
+ let anyNonNaPoi = false;
46302
46842
  const noteCountry = (iso) => {
46303
46843
  if (iso) {
46304
46844
  poiCountries.push(iso);
46305
- if (iso !== "US") anyNonUsPoi = true;
46845
+ if (iso === "US") anyUsPoi = true;
46846
+ if (iso !== "US" && iso !== "CA" && iso !== "MX") anyNonNaPoi = true;
46306
46847
  }
46307
46848
  };
46308
46849
  const deferred = [];
46309
46850
  for (const p of parsed.pois) {
46310
46851
  if (p.pos.kind === "coords") {
46311
- if (!looksUS(p.pos.lat, p.pos.lon)) anyNonUsPoi = true;
46852
+ if (looksUS(p.pos.lat, p.pos.lon)) anyUsPoi = true;
46853
+ else if (!looksNorthAmericaNeighbor(p.pos.lat, p.pos.lon))
46854
+ anyNonNaPoi = true;
46312
46855
  addResolvedPoi(p.pos.lat, p.pos.lon, p);
46313
46856
  continue;
46314
46857
  }
@@ -46326,14 +46869,15 @@ function resolveMap(parsed, data) {
46326
46869
  deferred.push(p);
46327
46870
  }
46328
46871
  }
46329
- const inferredCountry = parsed.directives.defaultCountry?.toUpperCase() ?? mostCommonCountry(regions, poiCountries) ?? void 0;
46872
+ const inferredCountry = localeCountry ?? mostCommonCountry(regions, poiCountries) ?? void 0;
46873
+ const inferredScope = localeSubdivision ?? inferredCountry;
46330
46874
  for (const p of deferred) {
46331
46875
  if (p.pos.kind !== "name") continue;
46332
46876
  const got = lookupName(
46333
46877
  p.pos.name,
46334
46878
  p.pos.scope,
46335
46879
  p.lineNumber,
46336
- inferredCountry,
46880
+ inferredScope,
46337
46881
  true
46338
46882
  );
46339
46883
  if (got.kind === "ok") {
@@ -46403,7 +46947,8 @@ function resolveMap(parsed, data) {
46403
46947
  const meta = sizeValue !== void 0 ? { value: sizeValue } : {};
46404
46948
  if (pos.kind === "coords") {
46405
46949
  const id = alias ? fold(alias) : `@${pos.lat},${pos.lon}`;
46406
- if (!looksUS(pos.lat, pos.lon)) anyNonUsPoi = true;
46950
+ if (looksUS(pos.lat, pos.lon)) anyUsPoi = true;
46951
+ else if (!looksNorthAmericaNeighbor(pos.lat, pos.lon)) anyNonNaPoi = true;
46407
46952
  if (!registry.has(id)) {
46408
46953
  registerPoi(
46409
46954
  id,
@@ -46426,7 +46971,7 @@ function resolveMap(parsed, data) {
46426
46971
  if (registry.has(f)) return f;
46427
46972
  const aliased = declaredByName.get(f);
46428
46973
  if (aliased) return aliased;
46429
- const got = lookupName(pos.name, pos.scope, line12, inferredCountry, true);
46974
+ const got = lookupName(pos.name, pos.scope, line12, inferredScope, true);
46430
46975
  if (got.kind !== "ok") return null;
46431
46976
  noteCountry(got.iso);
46432
46977
  registerPoi(
@@ -46483,9 +47028,12 @@ function resolveMap(parsed, data) {
46483
47028
  }
46484
47029
  routes.push({ stopIds, legs, lineNumber: rt.lineNumber });
46485
47030
  }
47031
+ const hasUsContent = usSubdivisionReferenced || anyUsPoi || localeCountry === "US";
47032
+ const usOriented = !anyNonNaPoi && !regions.some(
47033
+ (r) => r.layer === "country" && !["US", "CA", "MX"].includes(r.iso)
47034
+ ) && hasUsContent;
46486
47035
  const subdivisions = [];
46487
- if (usSubdivisionReferenced || parsed.directives.region === "us-states")
46488
- subdivisions.push("us-states");
47036
+ if (usSubdivisionReferenced || usOriented) subdivisions.push("us-states");
46489
47037
  const regionBoxes = [];
46490
47038
  for (const ref of referencedRegionIds) {
46491
47039
  const bb = featureBbox(data.usStates, ref.id);
@@ -46503,17 +47051,51 @@ function resolveMap(parsed, data) {
46503
47051
  [-180, -85],
46504
47052
  [180, 85]
46505
47053
  ];
46506
- let extent2 = unioned ? pad(unioned, PAD_FRACTION) : DEFAULT_EXTENT;
47054
+ const basePad = regions.length > 0 ? REGION_PAD_FRACTION : PAD_FRACTION;
47055
+ let extent2 = unioned ? pad(unioned, basePad) : DEFAULT_EXTENT;
47056
+ const isPoiOnly = pois.length > 0 && regions.length === 0;
47057
+ const containerRegionIds = [];
47058
+ if (isPoiOnly) {
47059
+ const countries = decodeFeatures(data.worldDetail);
47060
+ const states = decodeFeatures(data.usStates);
47061
+ const seen = /* @__PURE__ */ new Set();
47062
+ const containerBoxes = [];
47063
+ for (const p of pois) {
47064
+ const { country, state } = regionAt([p.lon, p.lat], countries, states);
47065
+ const id = state?.iso ?? country?.iso;
47066
+ if (!id || seen.has(id)) continue;
47067
+ seen.add(id);
47068
+ containerRegionIds.push(id);
47069
+ const bb = state ? featureBbox(data.usStates, id) : featureBboxPrimary(data.worldCoarse, id);
47070
+ if (bb && !isWholeSphere(bb)) containerBoxes.push(bb);
47071
+ }
47072
+ const containerUnion = unionExtent(containerBoxes, points);
47073
+ if (containerUnion) extent2 = pad(containerUnion, PAD_FRACTION);
47074
+ }
47075
+ if (isPoiOnly) {
47076
+ const cx = (extent2[0][0] + extent2[1][0]) / 2;
47077
+ const cy = (extent2[0][1] + extent2[1][1]) / 2;
47078
+ const lon = extent2[1][0] - extent2[0][0];
47079
+ const lat = extent2[1][1] - extent2[0][1];
47080
+ const longer = Math.max(lon, lat);
47081
+ if (longer > 0 && longer < POI_ZOOM_FLOOR_DEG) {
47082
+ const k = POI_ZOOM_FLOOR_DEG / longer;
47083
+ const halfLon = lon * k / 2;
47084
+ const halfLat = lat * k / 2;
47085
+ extent2 = [
47086
+ [cx - halfLon, cy - halfLat],
47087
+ [cx + halfLon, cy + halfLat]
47088
+ ];
47089
+ }
47090
+ }
46507
47091
  const lonSpan = extent2[1][0] - extent2[0][0];
46508
47092
  const latSpan = extent2[1][1] - extent2[0][1];
46509
47093
  const span = Math.max(lonSpan, latSpan);
46510
47094
  const maxAbsLat = Math.max(Math.abs(extent2[0][1]), Math.abs(extent2[1][1]));
46511
- const usDominant = (subdivisions.includes("us-states") || regions.some((r) => r.layer === "us-state")) && !regions.some((r) => r.layer === "country" && r.iso !== "US") && !anyNonUsPoi;
46512
47095
  let projection;
46513
- const override = parsed.directives.projection;
46514
- if (override === "equirectangular" || override === "natural-earth" || override === "albers-usa" || override === "mercator") {
46515
- projection = override;
46516
- } else if (usDominant) {
47096
+ if (isPoiOnly && usOriented && lonSpan < US_NATIONAL_LON_SPAN) {
47097
+ projection = "mercator";
47098
+ } else if (usOriented) {
46517
47099
  projection = "albers-usa";
46518
47100
  } else if (span > WORLD_SPAN || maxAbsLat > MERCATOR_MAX_LAT) {
46519
47101
  projection = "equirectangular";
@@ -46531,11 +47113,20 @@ function resolveMap(parsed, data) {
46531
47113
  result.edges = edges;
46532
47114
  result.routes = routes;
46533
47115
  result.basemaps = {
46534
- world: span > WORLD_SPAN ? "coarse" : "detail",
47116
+ // Tier is intentionally pinned to detail (50m) at ALL scales. Diagrammo maps
47117
+ // are presentational (palette tints, relief hachures, POI hubs), not
47118
+ // survey-grade — recognizability > generalization: 110m coarse drops the
47119
+ // Italian boot to a stump at world scale. `WORLD_SPAN` lives on only for the
47120
+ // projection decision (the `usOriented`/`span > WORLD_SPAN` chain above); it
47121
+ // no longer gates basemap resolution.
47122
+ // `worldCoarse` is still loaded — it's the authoritative name/bbox index
47123
+ // (featureIndex, featureBboxPrimary), not dead code.
47124
+ world: "detail",
46535
47125
  subdivisions
46536
47126
  };
46537
47127
  result.extent = extent2;
46538
47128
  result.projection = projection;
47129
+ result.poiFrameContainers = containerRegionIds;
46539
47130
  result.error = parsed.error ?? firstError(diagnostics);
46540
47131
  return result;
46541
47132
  }
@@ -46572,7 +47163,7 @@ function firstError(diags) {
46572
47163
  const e = diags.find((d) => d.severity === "error");
46573
47164
  return e ? formatDgmoError(e) : null;
46574
47165
  }
46575
- var WORLD_SPAN, MERCATOR_MAX_LAT, PAD_FRACTION, WORLD_LAT_SOUTH, WORLD_LAT_NORTH, REGION_ALIASES, US_STATE_POSTAL;
47166
+ var WORLD_SPAN, MERCATOR_MAX_LAT, PAD_FRACTION, REGION_PAD_FRACTION, WORLD_LAT_SOUTH, WORLD_LAT_NORTH, POI_ZOOM_FLOOR_DEG, US_NATIONAL_LON_SPAN, REGION_ALIASES, US_STATE_POSTAL;
46576
47167
  var init_resolver2 = __esm({
46577
47168
  "src/map/resolver.ts"() {
46578
47169
  "use strict";
@@ -46581,8 +47172,11 @@ var init_resolver2 = __esm({
46581
47172
  WORLD_SPAN = 90;
46582
47173
  MERCATOR_MAX_LAT = 80;
46583
47174
  PAD_FRACTION = 0.05;
47175
+ REGION_PAD_FRACTION = 0.12;
46584
47176
  WORLD_LAT_SOUTH = -58;
46585
47177
  WORLD_LAT_NORTH = 78;
47178
+ POI_ZOOM_FLOOR_DEG = 7;
47179
+ US_NATIONAL_LON_SPAN = 48;
46586
47180
  REGION_ALIASES = {
46587
47181
  // Common everyday names → the Natural-Earth display name actually shipped.
46588
47182
  "united states": "united states of america",
@@ -46660,10 +47254,277 @@ var init_resolver2 = __esm({
46660
47254
  }
46661
47255
  });
46662
47256
 
47257
+ // src/map/colorize.ts
47258
+ function assignColors(isos, adjacency) {
47259
+ const sorted = [...isos].sort();
47260
+ const byIso = /* @__PURE__ */ new Map();
47261
+ let maxIndex = -1;
47262
+ for (const iso of sorted) {
47263
+ const taken = /* @__PURE__ */ new Set();
47264
+ for (const n of adjacency.get(iso) ?? []) {
47265
+ const c = byIso.get(n);
47266
+ if (c !== void 0) taken.add(c);
47267
+ }
47268
+ let h = 0;
47269
+ while (taken.has(h)) h++;
47270
+ byIso.set(iso, h);
47271
+ if (h > maxIndex) maxIndex = h;
47272
+ }
47273
+ return { byIso, huesNeeded: maxIndex + 1 };
47274
+ }
47275
+ var init_colorize = __esm({
47276
+ "src/map/colorize.ts"() {
47277
+ "use strict";
47278
+ }
47279
+ });
47280
+
47281
+ // src/map/context-labels.ts
47282
+ function tierBand(maxSpanDeg) {
47283
+ if (maxSpanDeg >= 90) return "world";
47284
+ if (maxSpanDeg >= 20) return "continental";
47285
+ if (maxSpanDeg >= 5) return "regional";
47286
+ return "local";
47287
+ }
47288
+ function labelBudget(width, height, band) {
47289
+ const bandCap = {
47290
+ world: 6,
47291
+ continental: 5,
47292
+ regional: 4,
47293
+ local: 3
47294
+ };
47295
+ const area2 = Math.floor(Math.sqrt(Math.max(0, width * height)) / 150);
47296
+ return Math.max(0, Math.min(area2, bandCap[band]));
47297
+ }
47298
+ function waterEligible(tier, kind, band) {
47299
+ switch (band) {
47300
+ case "world":
47301
+ return tier <= 1 && (kind === "ocean" || kind === "sea");
47302
+ case "continental":
47303
+ return tier <= 2;
47304
+ case "regional":
47305
+ return tier <= 3;
47306
+ case "local":
47307
+ return tier <= 4;
47308
+ }
47309
+ }
47310
+ function insideViewport(p, width, height) {
47311
+ return !!p && Number.isFinite(p[0]) && Number.isFinite(p[1]) && p[0] >= 0 && p[0] <= width && p[1] >= 0 && p[1] <= height;
47312
+ }
47313
+ function labelWidth(text, letterSpacing) {
47314
+ const spacing = letterSpacing > 0 ? Math.max(0, text.length - 1) * letterSpacing : 0;
47315
+ return measureLegendText(text, FONT) + spacing + 2 * PADX;
47316
+ }
47317
+ function wrapLabel2(text, letterSpacing) {
47318
+ const words = text.split(/\s+/).filter(Boolean);
47319
+ if (words.length <= 1) return [text];
47320
+ const maxLines = words.length >= 4 ? 3 : 2;
47321
+ const n = words.length;
47322
+ let best = null;
47323
+ for (let mask = 0; mask < 1 << n - 1; mask++) {
47324
+ const lines = [];
47325
+ let cur = [words[0]];
47326
+ for (let i = 1; i < n; i++) {
47327
+ if (mask & 1 << i - 1) {
47328
+ lines.push(cur.join(" "));
47329
+ cur = [words[i]];
47330
+ } else cur.push(words[i]);
47331
+ }
47332
+ lines.push(cur.join(" "));
47333
+ if (lines.length > maxLines) continue;
47334
+ const cost = Math.round(
47335
+ Math.max(...lines.map((l) => labelWidth(l, letterSpacing)))
47336
+ );
47337
+ const head = labelWidth(lines[0], letterSpacing);
47338
+ if (!best || cost < best.cost || cost === best.cost && lines.length < best.lines.length || cost === best.cost && lines.length === best.lines.length && head > best.head)
47339
+ best = { lines, cost, head };
47340
+ }
47341
+ return best?.lines ?? [text];
47342
+ }
47343
+ function rectAround(cx, cy, lines, letterSpacing) {
47344
+ const w = Math.max(...lines.map((l) => labelWidth(l, letterSpacing)));
47345
+ const h = (lines.length - 1) * LINE_HEIGHT + FONT + 2 * PADY;
47346
+ return { x: cx - w / 2, y: cy - h / 2, w, h };
47347
+ }
47348
+ function rectFits(r, width, height) {
47349
+ return r.x >= 0 && r.y >= 0 && r.x + r.w <= width && r.y + r.h <= height;
47350
+ }
47351
+ function overlapsPadded(a, b, pad2) {
47352
+ return a.x - pad2 < b.x + b.w && a.x + a.w + pad2 > b.x && a.y - pad2 < b.y + b.h && a.y + a.h + pad2 > b.y;
47353
+ }
47354
+ function placeContextLabels(args) {
47355
+ const {
47356
+ projection,
47357
+ dLonSpan,
47358
+ dLatSpan,
47359
+ width,
47360
+ height,
47361
+ waterBodies,
47362
+ countries,
47363
+ palette,
47364
+ project,
47365
+ collides,
47366
+ overLand
47367
+ } = args;
47368
+ void projection;
47369
+ const band = tierBand(Math.max(dLonSpan, dLatSpan));
47370
+ const budget = labelBudget(width, height, band);
47371
+ if (budget <= 0) return [];
47372
+ const waterColor = mix(palette.colors.blue, palette.textMuted, 50);
47373
+ const countryColor = palette.textMuted;
47374
+ const haloColor = palette.bg;
47375
+ const candidates = [];
47376
+ const center = [width / 2, height / 2];
47377
+ for (const e of waterBodies?.entries ?? []) {
47378
+ const [lat, lon, name, tier, kind, alt] = e;
47379
+ if (!waterEligible(tier, kind, band)) continue;
47380
+ const wlines = wrapLabel2(name, WATER_LETTER_SPACING);
47381
+ const anchorsLngLat = [[lon, lat]];
47382
+ for (const a of alt ?? []) anchorsLngLat.push([a[1], a[0]]);
47383
+ let best = null;
47384
+ let bestD = Infinity;
47385
+ let nearestProj = null;
47386
+ let nearestProjD = Infinity;
47387
+ for (const [aLon, aLat] of anchorsLngLat) {
47388
+ const p = project(aLon, aLat);
47389
+ if (!p || !Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;
47390
+ const d = (p[0] - center[0]) ** 2 + (p[1] - center[1]) ** 2;
47391
+ if (d < nearestProjD) {
47392
+ nearestProjD = d;
47393
+ nearestProj = p;
47394
+ }
47395
+ if (!insideViewport(p, width, height)) continue;
47396
+ if (d < bestD) {
47397
+ bestD = d;
47398
+ best = p;
47399
+ }
47400
+ }
47401
+ if (!best && tier === 0 && nearestProj) {
47402
+ const overX = Math.max(0, -nearestProj[0], nearestProj[0] - width);
47403
+ const overY = Math.max(0, -nearestProj[1], nearestProj[1] - height);
47404
+ if (overX <= width * EDGE_CLAMP_OVERSHOOT && overY <= height * EDGE_CLAMP_OVERSHOOT) {
47405
+ const halfW = Math.max(...wlines.map((l) => labelWidth(l, WATER_LETTER_SPACING))) / 2;
47406
+ const halfH = ((wlines.length - 1) * LINE_HEIGHT + FONT + 2 * PADY) / 2;
47407
+ const m = EDGE_CLAMP_MARGIN;
47408
+ best = [
47409
+ Math.min(Math.max(nearestProj[0], halfW + m), width - halfW - m),
47410
+ Math.min(Math.max(nearestProj[1], halfH + m), height - halfH - m)
47411
+ ];
47412
+ }
47413
+ }
47414
+ if (!best) continue;
47415
+ candidates.push({
47416
+ text: name,
47417
+ lines: wlines,
47418
+ cx: best[0],
47419
+ cy: best[1],
47420
+ italic: true,
47421
+ letterSpacing: WATER_LETTER_SPACING,
47422
+ color: waterColor,
47423
+ // Water before any country (×1000), then by tier, then kind, then name.
47424
+ sort: tier * 10 + KIND_ORDER[kind]
47425
+ });
47426
+ }
47427
+ const ranked = countries.map((c) => {
47428
+ const [x0, y0, x1, y1] = c.bbox;
47429
+ const w = x1 - x0;
47430
+ const h = y1 - y0;
47431
+ return { c, w, h, area: w * h };
47432
+ }).filter((r) => Number.isFinite(r.area) && r.area > 0).sort((a, b) => b.area - a.area);
47433
+ let ci = 0;
47434
+ for (const r of ranked) {
47435
+ const { c, w, h } = r;
47436
+ if (w > width * 0.66 || h > height * 0.66) continue;
47437
+ if (!insideViewport(c.anchor, width, height)) continue;
47438
+ const text = c.name;
47439
+ const tw = labelWidth(text, 0);
47440
+ if (tw > w || FONT + 2 * PADY > h) continue;
47441
+ candidates.push({
47442
+ text,
47443
+ lines: [text],
47444
+ cx: c.anchor[0],
47445
+ cy: c.anchor[1],
47446
+ italic: false,
47447
+ letterSpacing: 0,
47448
+ color: countryColor,
47449
+ // Always after every water body (+1e6); larger area = earlier.
47450
+ sort: 1e6 + ci++
47451
+ });
47452
+ }
47453
+ candidates.sort((a, b) => a.sort - b.sort);
47454
+ const placed = [];
47455
+ const placedRects = [];
47456
+ for (const cand of candidates) {
47457
+ if (placed.length >= budget) break;
47458
+ const rect = rectAround(cand.cx, cand.cy, cand.lines, cand.letterSpacing);
47459
+ if (!rectFits(rect, width, height)) continue;
47460
+ if (cand.italic && overLand) {
47461
+ const inset = 2;
47462
+ const top = cand.cy - (cand.lines.length - 1) / 2 * LINE_HEIGHT;
47463
+ const touchesLand = cand.lines.some((line12, li) => {
47464
+ const lw = labelWidth(line12, cand.letterSpacing);
47465
+ const x0 = cand.cx - lw / 2 + inset;
47466
+ const x1 = cand.cx + lw / 2 - inset;
47467
+ const xs = [x0, (x0 + cand.cx) / 2, cand.cx, (cand.cx + x1) / 2, x1];
47468
+ const base = top + li * LINE_HEIGHT;
47469
+ return [base, base - FONT * 0.4, base - FONT * 0.8].some(
47470
+ (y) => xs.some((x) => overLand(x, y))
47471
+ );
47472
+ });
47473
+ if (touchesLand) continue;
47474
+ }
47475
+ if (collides(rect)) continue;
47476
+ if (placedRects.some((r) => overlapsPadded(rect, r, CONTEXT_PAD))) continue;
47477
+ placedRects.push(rect);
47478
+ placed.push({
47479
+ x: cand.cx,
47480
+ y: cand.cy,
47481
+ text: cand.text,
47482
+ anchor: "middle",
47483
+ color: cand.color,
47484
+ // No halo: the bg-coloured outline reads as a ghost box behind the text
47485
+ // over the tinted water/land. Context labels are muted enough to sit
47486
+ // cleanly on the basemap without one.
47487
+ halo: false,
47488
+ haloColor,
47489
+ italic: cand.italic,
47490
+ letterSpacing: cand.letterSpacing,
47491
+ ...cand.lines.length > 1 ? { lines: cand.lines } : {},
47492
+ lineNumber: 0
47493
+ });
47494
+ }
47495
+ return placed;
47496
+ }
47497
+ var FONT, LINE_HEIGHT, PADX, PADY, WATER_LETTER_SPACING, CONTEXT_PAD, EDGE_CLAMP_MARGIN, EDGE_CLAMP_OVERSHOOT, KIND_ORDER;
47498
+ var init_context_labels = __esm({
47499
+ "src/map/context-labels.ts"() {
47500
+ "use strict";
47501
+ init_color_utils();
47502
+ init_legend_constants();
47503
+ FONT = 11;
47504
+ LINE_HEIGHT = FONT + 2;
47505
+ PADX = 4;
47506
+ PADY = 3;
47507
+ WATER_LETTER_SPACING = 1.5;
47508
+ CONTEXT_PAD = 4;
47509
+ EDGE_CLAMP_MARGIN = 8;
47510
+ EDGE_CLAMP_OVERSHOOT = 0.35;
47511
+ KIND_ORDER = {
47512
+ ocean: 0,
47513
+ sea: 1,
47514
+ gulf: 2,
47515
+ bay: 3,
47516
+ strait: 4,
47517
+ channel: 5,
47518
+ sound: 6
47519
+ };
47520
+ }
47521
+ });
47522
+
46663
47523
  // src/map/layout.ts
46664
47524
  import {
46665
47525
  geoPath,
46666
47526
  geoNaturalEarth1,
47527
+ geoEqualEarth,
46667
47528
  geoEquirectangular,
46668
47529
  geoConicEqualArea,
46669
47530
  geoMercator,
@@ -46675,12 +47536,34 @@ function geomObject2(topo) {
46675
47536
  const key = Object.keys(topo.objects)[0];
46676
47537
  return topo.objects[key];
46677
47538
  }
47539
+ function mergeFeatures(a, b) {
47540
+ const polysOf = (f) => {
47541
+ const g = f.geometry;
47542
+ if (!g) return null;
47543
+ if (g.type === "Polygon") return [g.coordinates];
47544
+ if (g.type === "MultiPolygon") return g.coordinates;
47545
+ return null;
47546
+ };
47547
+ const pa = polysOf(a);
47548
+ const pb = polysOf(b);
47549
+ if (!pa || !pb) return a;
47550
+ return {
47551
+ ...a,
47552
+ geometry: { type: "MultiPolygon", coordinates: [...pa, ...pb] }
47553
+ };
47554
+ }
46678
47555
  function decodeLayer(topo) {
47556
+ const cached = decodeCache.get(topo);
47557
+ if (cached) return cached;
46679
47558
  const out = /* @__PURE__ */ new Map();
46680
47559
  for (const g of geomObject2(topo).geometries) {
46681
47560
  const f = feature2(topo, g);
46682
- out.set(g.id, { ...f, id: g.id });
47561
+ if (!f.geometry) continue;
47562
+ const tagged = { ...f, id: g.id };
47563
+ const existing = out.get(g.id);
47564
+ out.set(g.id, existing ? mergeFeatures(existing, tagged) : tagged);
46683
47565
  }
47566
+ decodeCache.set(topo, out);
46684
47567
  return out;
46685
47568
  }
46686
47569
  function projectionFor(family) {
@@ -46689,9 +47572,12 @@ function projectionFor(family) {
46689
47572
  return usConusProjection();
46690
47573
  case "mercator":
46691
47574
  return geoMercator();
47575
+ case "equal-earth":
47576
+ return geoEqualEarth();
47577
+ case "equirectangular":
47578
+ return geoEquirectangular();
46692
47579
  case "natural-earth":
46693
47580
  return geoNaturalEarth1();
46694
- case "equirectangular":
46695
47581
  default:
46696
47582
  return geoEquirectangular();
46697
47583
  }
@@ -46710,13 +47596,11 @@ function mapNeutralLandColor(palette, isDark, _dataActive = false) {
46710
47596
  isDark ? LAND_TINT_DARK : LAND_TINT_LIGHT
46711
47597
  );
46712
47598
  }
46713
- function layoutMap(resolved, data, size, opts) {
46714
- const { palette, isDark } = opts;
46715
- const { width, height } = size;
47599
+ function buildMapProjection(resolved, data) {
46716
47600
  const wantsUsStates = resolved.basemaps.subdivisions.includes("us-states");
46717
- const usCrisp = resolved.projection === "albers-usa" && wantsUsStates && !!data.naLand;
47601
+ const usCrisp = (resolved.projection === "albers-usa" || resolved.projection === "mercator") && wantsUsStates && !!data.naLand;
46718
47602
  const worldTopo = usCrisp ? data.worldDetail : resolved.basemaps.world === "detail" ? data.worldDetail : data.worldCoarse;
46719
- const worldLayer = decodeLayer(worldTopo);
47603
+ const worldLayer = new Map(decodeLayer(worldTopo));
46720
47604
  if (usCrisp && data.naLand) {
46721
47605
  const [nbW, nbS, nbE, nbN] = [-140, 10, -52, 66];
46722
47606
  const crisp = decodeLayer(data.naLand);
@@ -46725,16 +47609,141 @@ function layoutMap(resolved, data, size, opts) {
46725
47609
  if (!base) continue;
46726
47610
  const [[bw, bs], [be, bn]] = geoBounds2(base);
46727
47611
  if (bw >= nbW && be <= nbE && bs >= nbS && bn <= nbN)
46728
- worldLayer.set(iso, cf);
47612
+ worldLayer.set(iso, { ...cf, properties: base.properties });
46729
47613
  }
46730
47614
  }
46731
47615
  const usLayer = wantsUsStates ? decodeLayer(data.usStates) : null;
47616
+ const extentOutline = () => {
47617
+ const [[w, s], [e, n]] = resolved.extent;
47618
+ const N = 16;
47619
+ const coords = [];
47620
+ for (let i = 0; i <= N; i++) {
47621
+ const t = i / N;
47622
+ const lon = w + (e - w) * t;
47623
+ const lat = s + (n - s) * t;
47624
+ coords.push([lon, s], [lon, n], [w, lat], [e, lat]);
47625
+ }
47626
+ return {
47627
+ type: "Feature",
47628
+ properties: {},
47629
+ geometry: { type: "MultiPoint", coordinates: coords }
47630
+ };
47631
+ };
47632
+ let fitFeatures;
47633
+ if (resolved.projection === "albers-usa" && usLayer) {
47634
+ fitFeatures = [...usLayer.entries()].filter(([iso]) => !US_NON_CONUS.has(iso)).map(([, f]) => f);
47635
+ const neighborPoints = resolved.pois.filter((p) => !inAlaska(p.lon, p.lat) && !inHawaii(p.lon, p.lat)).map((p) => [p.lon, p.lat]);
47636
+ if (neighborPoints.length > 0) {
47637
+ fitFeatures.push({
47638
+ type: "Feature",
47639
+ properties: {},
47640
+ geometry: { type: "MultiPoint", coordinates: neighborPoints }
47641
+ });
47642
+ }
47643
+ for (const r of resolved.regions) {
47644
+ if (r.layer === "country" && (r.iso === "CA" || r.iso === "MX")) {
47645
+ const cf = worldLayer.get(r.iso);
47646
+ if (cf) fitFeatures.push(cf);
47647
+ }
47648
+ }
47649
+ } else {
47650
+ fitFeatures = [extentOutline()];
47651
+ }
47652
+ const fitTarget = { type: "FeatureCollection", features: fitFeatures };
47653
+ const projection = projectionFor(resolved.projection);
47654
+ if (resolved.projection !== "albers-usa") {
47655
+ let centerLon = (resolved.extent[0][0] + resolved.extent[1][0]) / 2;
47656
+ if (centerLon > 180) centerLon -= 360;
47657
+ projection.rotate([-centerLon, 0]);
47658
+ }
47659
+ const fitGB = geoBounds2(fitTarget);
47660
+ const fitIsGlobal = fitGB[1][0] - fitGB[0][0] >= 270 || fitGB[1][1] - fitGB[0][1] >= 130;
47661
+ return {
47662
+ projection,
47663
+ fitTarget,
47664
+ fitIsGlobal,
47665
+ worldLayer,
47666
+ usLayer,
47667
+ usCrisp,
47668
+ wantsUsStates,
47669
+ worldTopo
47670
+ };
47671
+ }
47672
+ function parsePathRings(d) {
47673
+ const rings = [];
47674
+ let cur = [];
47675
+ const re = /([MLZ])([^MLZ]*)/g;
47676
+ let m;
47677
+ while (m = re.exec(d)) {
47678
+ if (m[1] === "Z") {
47679
+ if (cur.length) rings.push(cur);
47680
+ cur = [];
47681
+ continue;
47682
+ }
47683
+ if (m[1] === "M" && cur.length) {
47684
+ rings.push(cur);
47685
+ cur = [];
47686
+ }
47687
+ const nums = m[2].split(/[ ,]+/).map(Number);
47688
+ for (let i = 0; i + 1 < nums.length; i += 2) {
47689
+ const x = nums[i];
47690
+ const y = nums[i + 1];
47691
+ if (Number.isFinite(x) && Number.isFinite(y)) cur.push([x, y]);
47692
+ }
47693
+ }
47694
+ if (cur.length) rings.push(cur);
47695
+ return rings;
47696
+ }
47697
+ function dropAntimeridianWrapSlivers(d, width, height) {
47698
+ const rings = parsePathRings(d);
47699
+ if (rings.length <= 1) return d;
47700
+ const eps = 0.75;
47701
+ const minArea = 3e-3 * width * height;
47702
+ const ringArea = (r) => {
47703
+ let s = 0;
47704
+ for (let i = 0; i < r.length; i++) {
47705
+ const a = r[i];
47706
+ const b = r[(i + 1) % r.length];
47707
+ s += a[0] * b[1] - b[0] * a[1];
47708
+ }
47709
+ return Math.abs(s) / 2;
47710
+ };
47711
+ const areas = rings.map(ringArea);
47712
+ const maxArea = Math.max(...areas);
47713
+ 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;
47714
+ let dropped = false;
47715
+ const kept = rings.filter((r, idx) => {
47716
+ if (areas[idx] >= maxArea || areas[idx] >= minArea) return true;
47717
+ const touches = r.some((p, i) => onVEdge(p, r[(i + 1) % r.length]));
47718
+ if (touches) {
47719
+ dropped = true;
47720
+ return false;
47721
+ }
47722
+ return true;
47723
+ });
47724
+ if (!dropped) return d;
47725
+ return kept.map(
47726
+ (r) => r.map((p, i) => (i ? "L" : "M") + p[0] + "," + p[1]).join("") + "Z"
47727
+ ).join("");
47728
+ }
47729
+ function layoutMap(resolved, data, size, opts) {
47730
+ const { palette, isDark } = opts;
47731
+ const { width, height } = size;
47732
+ const {
47733
+ projection,
47734
+ fitTarget,
47735
+ fitIsGlobal,
47736
+ worldLayer,
47737
+ usLayer,
47738
+ usCrisp,
47739
+ worldTopo
47740
+ } = buildMapProjection(resolved, data);
46732
47741
  const usContext = usLayer !== null;
46733
47742
  const regionStroke = isDark ? mix(palette.bg, palette.text, 78) : mix(palette.text, palette.bg, 78);
46734
47743
  const values = resolved.regions.filter((r) => r.value !== void 0).map((r) => r.value);
46735
- const scaleOverride = resolved.directives.scale;
46736
- const rampMin = scaleOverride ? scaleOverride.min : Math.min(...values);
46737
- const rampMax = scaleOverride ? scaleOverride.max : Math.max(...values);
47744
+ const allNonNegative = values.length > 0 && values.every((v) => v >= 0);
47745
+ const rampMin = allNonNegative ? 0 : Math.min(...values);
47746
+ const rampMax = Math.max(...values);
46738
47747
  const rampHue = resolveColor(resolved.directives.regionMetricColor ?? "", palette) ?? palette.colors.red;
46739
47748
  const hasRamp = values.length > 0;
46740
47749
  const VALUE_NAME = hasRamp ? resolved.directives.regionMetric?.trim() || "Value" : null;
@@ -46755,7 +47764,7 @@ function layoutMap(resolved, data, size, opts) {
46755
47764
  activeGroup = VALUE_NAME ?? (resolved.tagGroups.length > 0 ? resolved.tagGroups[0].name : null);
46756
47765
  }
46757
47766
  const activeIsScore = VALUE_NAME !== null && activeGroup === VALUE_NAME;
46758
- const mutedBasemap = resolved.directives.basemapStyle === "muted" ? true : resolved.directives.basemapStyle === "natural" ? false : activeGroup !== null;
47767
+ const mutedBasemap = activeGroup !== null;
46759
47768
  const neutralFill = mapNeutralLandColor(palette, isDark, mutedBasemap);
46760
47769
  const water = mapBackgroundColor(palette, isDark, mutedBasemap);
46761
47770
  const lakeStroke = mix(regionStroke, water, 45);
@@ -46764,10 +47773,43 @@ function layoutMap(resolved, data, size, opts) {
46764
47773
  palette.bg,
46765
47774
  mutedBasemap ? isDark ? MUTED_FOREIGN_DARK : MUTED_FOREIGN_LIGHT : isDark ? FOREIGN_TINT_DARK : FOREIGN_TINT_LIGHT
46766
47775
  );
47776
+ const colorizeActive = resolved.directives.noColorize !== true && !hasRamp && resolved.tagGroups.length === 0;
47777
+ const colorByIso = /* @__PURE__ */ new Map();
47778
+ if (colorizeActive) {
47779
+ const adjacency = /* @__PURE__ */ new Map();
47780
+ const addEdges = (src) => {
47781
+ for (const [iso, ns] of src) {
47782
+ const cur = adjacency.get(iso);
47783
+ if (cur) cur.push(...ns);
47784
+ else adjacency.set(iso, [...ns]);
47785
+ }
47786
+ };
47787
+ addEdges(buildAdjacency(worldTopo));
47788
+ if (usLayer) {
47789
+ addEdges(buildAdjacency(data.usStates));
47790
+ for (const [country, states] of Object.entries(FOREIGN_BORDER)) {
47791
+ const cn = adjacency.get(country);
47792
+ if (!cn) continue;
47793
+ for (const st of states) {
47794
+ const sn = adjacency.get(st);
47795
+ if (!sn) continue;
47796
+ cn.push(st);
47797
+ sn.push(country);
47798
+ }
47799
+ }
47800
+ }
47801
+ const { byIso, huesNeeded } = assignColors(
47802
+ [...adjacency.keys()],
47803
+ adjacency
47804
+ );
47805
+ const tints = politicalTints(palette, huesNeeded, isDark);
47806
+ for (const [iso, idx] of byIso) colorByIso.set(iso, tints[idx]);
47807
+ }
47808
+ const colorizeStroke = (fill2) => mix(fill2, palette.text, 35);
46767
47809
  const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
46768
47810
  const fillForValue = (s) => {
46769
47811
  const t = rampMax > rampMin ? (s - rampMin) / (rampMax - rampMin) : 1;
46770
- const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
47812
+ const pct = RAMP_FLOOR2 + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR2);
46771
47813
  return mix(rampHue, rampBase, pct);
46772
47814
  };
46773
47815
  const tagFill = (tags, groupName) => {
@@ -46799,43 +47841,15 @@ function layoutMap(resolved, data, size, opts) {
46799
47841
  if (activeIsScore) {
46800
47842
  return r.value !== void 0 ? fillForValue(r.value) : neutralFill;
46801
47843
  }
47844
+ if (colorizeActive) return (r.iso && colorByIso.get(r.iso)) ?? neutralFill;
46802
47845
  return tagFill(r.tags, activeGroup) ?? neutralFill;
46803
47846
  };
46804
47847
  const regionById = new Map(resolved.regions.map((r) => [r.iso, r]));
46805
- const extentOutline = () => {
46806
- const [[w, s], [e, n]] = resolved.extent;
46807
- const N = 16;
46808
- const coords = [];
46809
- for (let i = 0; i <= N; i++) {
46810
- const t = i / N;
46811
- const lon = w + (e - w) * t;
46812
- const lat = s + (n - s) * t;
46813
- coords.push([lon, s], [lon, n], [w, lat], [e, lat]);
46814
- }
46815
- return {
46816
- type: "Feature",
46817
- properties: {},
46818
- geometry: { type: "MultiPoint", coordinates: coords }
46819
- };
46820
- };
46821
- let fitFeatures;
46822
- if (resolved.projection === "albers-usa" && usLayer) {
46823
- fitFeatures = [...usLayer.entries()].filter(([iso]) => !US_NON_CONUS.has(iso)).map(([, f]) => f);
46824
- } else {
46825
- fitFeatures = [extentOutline()];
46826
- }
46827
- const fitTarget = { type: "FeatureCollection", features: fitFeatures };
46828
- const projection = projectionFor(resolved.projection);
46829
- if (resolved.projection !== "albers-usa") {
46830
- let centerLon = (resolved.extent[0][0] + resolved.extent[1][0]) / 2;
46831
- if (centerLon > 180) centerLon -= 360;
46832
- projection.rotate([-centerLon, 0]);
46833
- }
46834
- const TITLE_GAP = 16;
47848
+ const TITLE_GAP2 = 16;
46835
47849
  let topPad = FIT_PAD;
46836
47850
  if (resolved.title && resolved.pois.length > 0) {
46837
47851
  const bannerBottom = (resolved.subtitle ? TITLE_Y + TITLE_FONT_SIZE : TITLE_Y) + TITLE_FONT_SIZE / 2;
46838
- topPad = Math.max(FIT_PAD, bannerBottom + TITLE_GAP);
47852
+ topPad = Math.max(FIT_PAD, bannerBottom + TITLE_GAP2);
46839
47853
  }
46840
47854
  const fitBox = [
46841
47855
  [FIT_PAD, topPad],
@@ -46845,21 +47859,20 @@ function layoutMap(resolved, data, size, opts) {
46845
47859
  ]
46846
47860
  ];
46847
47861
  projection.fitExtent(fitBox, fitTarget);
46848
- const fitGB = geoBounds2(fitTarget);
46849
- const fitIsGlobal = fitGB[1][0] - fitGB[0][0] >= 270 || fitGB[1][1] - fitGB[0][1] >= 130;
46850
47862
  let path;
46851
47863
  let project;
46852
47864
  let stretchParams = null;
46853
- if (fitIsGlobal) {
47865
+ if (fitIsGlobal && !opts.preferContain) {
46854
47866
  const cb = geoPath(projection).bounds(fitTarget);
46855
47867
  const bx0 = cb[0][0];
46856
47868
  const by0 = cb[0][1];
46857
47869
  const cw = cb[1][0] - bx0;
46858
47870
  const ch = cb[1][1] - by0;
46859
- const ox = fitBox[0][0];
46860
- const oy = fitBox[0][1];
46861
- const sx = cw > 0 ? (fitBox[1][0] - ox) / cw : 1;
46862
- const sy = ch > 0 ? (fitBox[1][1] - oy) / ch : 1;
47871
+ const topReserve = resolved.title && resolved.pois.length > 0 ? topPad : 0;
47872
+ const ox = 0;
47873
+ const oy = topReserve;
47874
+ const sx = cw > 0 ? width / cw : 1;
47875
+ const sy = ch > 0 ? (height - topReserve) / ch : 1;
46863
47876
  stretchParams = { sx, sy, ox, oy, bx0, by0 };
46864
47877
  const stretch = (x, y) => [
46865
47878
  ox + (x - bx0) * sx,
@@ -46892,7 +47905,9 @@ function layoutMap(resolved, data, size, opts) {
46892
47905
  const insets = [];
46893
47906
  const insetRegions = [];
46894
47907
  const insetLabelSeeds = [];
46895
- if (resolved.projection === "albers-usa" && usLayer && !resolved.directives.noInsets) {
47908
+ const akRef = resolved.regions.some((r) => r.iso === "US-AK") || resolved.pois.some((p) => inAlaska(p.lon, p.lat));
47909
+ const hiRef = resolved.regions.some((r) => r.iso === "US-HI") || resolved.pois.some((p) => inHawaii(p.lon, p.lat));
47910
+ if (resolved.projection === "albers-usa" && usLayer && (akRef || hiRef)) {
46896
47911
  const PAD = 8;
46897
47912
  const GAP = 12;
46898
47913
  const yB = height - FIT_PAD;
@@ -46957,8 +47972,18 @@ function layoutMap(resolved, data, size, opts) {
46957
47972
  );
46958
47973
  const d = geoPath(proj)(f) ?? "";
46959
47974
  if (!d) return xr;
47975
+ let contextLand;
47976
+ if (iso === "US-AK") {
47977
+ const can = worldLayer.get("CA");
47978
+ const cd = can ? geoPath(proj)(can) ?? "" : "";
47979
+ if (cd)
47980
+ contextLand = {
47981
+ d: cd,
47982
+ fill: colorizeActive ? colorByIso.get("CA") ?? foreignFill : foreignFill
47983
+ };
47984
+ }
46960
47985
  const r = regionById.get(iso);
46961
- let fill2 = neutralFill;
47986
+ let fill2 = colorizeActive ? colorByIso.get(iso) ?? neutralFill : neutralFill;
46962
47987
  let lineNumber = -1;
46963
47988
  if (r?.layer === "us-state") {
46964
47989
  fill2 = regionFill(r);
@@ -46977,13 +48002,14 @@ function layoutMap(resolved, data, size, opts) {
46977
48002
  ],
46978
48003
  // The FITTED inset projection (just fit to this box) — captured so the
46979
48004
  // geo-query can invert pixels inside the frame back to AK/HI coords.
46980
- projection: proj
48005
+ projection: proj,
48006
+ ...contextLand && { contextLand }
46981
48007
  });
46982
48008
  insetRegions.push({
46983
48009
  id: iso,
46984
48010
  d,
46985
48011
  fill: fill2,
46986
- stroke: regionStroke,
48012
+ stroke: colorizeActive ? colorizeStroke(fill2) : regionStroke,
46987
48013
  lineNumber,
46988
48014
  layer: "us-state",
46989
48015
  ...r?.value !== void 0 && { value: r.value },
@@ -46996,13 +48022,16 @@ function layoutMap(resolved, data, size, opts) {
46996
48022
  }
46997
48023
  return xr;
46998
48024
  };
46999
- const akRight = placeInset(
47000
- "US-AK",
47001
- alaskaProjection(),
47002
- FIT_PAD,
47003
- width * 0.15
47004
- );
47005
- placeInset("US-HI", hawaiiProjection(), akRight + 24, width * 0.1);
48025
+ let akRight = FIT_PAD;
48026
+ if (akRef)
48027
+ akRight = placeInset("US-AK", alaskaProjection(), FIT_PAD, width * 0.15);
48028
+ if (hiRef)
48029
+ placeInset(
48030
+ "US-HI",
48031
+ hawaiiProjection(),
48032
+ akRef ? akRight + 24 : FIT_PAD,
48033
+ width * 0.1
48034
+ );
47006
48035
  }
47007
48036
  const conusFit = resolved.projection === "albers-usa" && !!usLayer;
47008
48037
  const classifyExtent = conusFit ? geoBounds2(fitTarget) : resolved.extent;
@@ -47018,15 +48047,24 @@ function layoutMap(resolved, data, size, opts) {
47018
48047
  };
47019
48048
  const ringOverlapsView = (ring) => {
47020
48049
  let loMin = Infinity, loMax = -Infinity, rawMin = Infinity, rawMax = -Infinity;
48050
+ const lons = [];
47021
48051
  for (const [rawLon] of ring) {
47022
48052
  const lon = normLon(rawLon);
48053
+ lons.push(lon);
47023
48054
  if (lon < loMin) loMin = lon;
47024
48055
  if (lon > loMax) loMax = lon;
47025
48056
  if (rawLon < rawMin) rawMin = rawLon;
47026
48057
  if (rawLon > rawMax) rawMax = rawLon;
47027
48058
  }
47028
- if (loMax - loMin > 270) return false;
47029
- if (rawMax - rawMin > 180 && loMax - loMin < 90) return false;
48059
+ lons.sort((a, b) => a - b);
48060
+ let maxGap = 0;
48061
+ for (let i = 1; i < lons.length; i++)
48062
+ maxGap = Math.max(maxGap, lons[i] - lons[i - 1]);
48063
+ if (lons.length > 1)
48064
+ maxGap = Math.max(maxGap, lons[0] + 360 - lons[lons.length - 1]);
48065
+ const occupiedArc = 360 - maxGap;
48066
+ if (occupiedArc > 270) return false;
48067
+ if (rawMax - rawMin > 180 && occupiedArc < 90) return false;
47030
48068
  let px0 = Infinity, py0 = Infinity, px1 = -Infinity, py1 = -Infinity, anyFinite = false;
47031
48069
  for (const [lon, lat] of ring) {
47032
48070
  const p = project(lon, lat);
@@ -47099,7 +48137,7 @@ function layoutMap(resolved, data, size, opts) {
47099
48137
  const regions = [];
47100
48138
  const pushRegionLayer = (layerFeatures, layerKind, shouldCull) => {
47101
48139
  for (const [iso, f] of layerFeatures) {
47102
- if (layerKind === "us-state" && usContext && INSET_STATES.has(iso))
48140
+ if (layerKind === "us-state" && usContext && resolved.projection === "albers-usa" && INSET_STATES.has(iso))
47103
48141
  continue;
47104
48142
  if (layerKind === "country" && usContext && iso === "US") continue;
47105
48143
  if (layerKind === "country" && iso === "AQ" && !regionById.has("AQ"))
@@ -47107,11 +48145,13 @@ function layoutMap(resolved, data, size, opts) {
47107
48145
  const r = regionById.get(iso);
47108
48146
  const viewF = shouldCull ? cullFeatureToView(f) : dropFrameFillers(f);
47109
48147
  if (!viewF) continue;
47110
- const d = path(viewF) ?? "";
48148
+ const raw = path(viewF) ?? "";
48149
+ const d = fitIsGlobal ? dropAntimeridianWrapSlivers(raw, width, height) : raw;
47111
48150
  if (!d) continue;
47112
48151
  const isThisLayer = r?.layer === layerKind;
47113
48152
  const isForeign = layerKind === "country" && usContext && iso !== "US";
47114
- let fill2 = isForeign ? foreignFill : neutralFill;
48153
+ const baseFill = isForeign ? foreignFill : neutralFill;
48154
+ let fill2 = colorizeActive ? colorByIso.get(iso) ?? baseFill : baseFill;
47115
48155
  let label;
47116
48156
  let lineNumber = -1;
47117
48157
  let layer = "base";
@@ -47120,15 +48160,21 @@ function layoutMap(resolved, data, size, opts) {
47120
48160
  lineNumber = r.lineNumber;
47121
48161
  layer = layerKind;
47122
48162
  label = r.name;
48163
+ } else {
48164
+ label = f.properties?.name;
47123
48165
  }
48166
+ const labelAnchor = WORLD_LABEL_ANCHORS[iso];
48167
+ const c = labelAnchor ? project(labelAnchor[0], labelAnchor[1]) : path.centroid(viewF);
48168
+ const hasCentroid = c != null && Number.isFinite(c[0]) && Number.isFinite(c[1]);
47124
48169
  regions.push({
47125
48170
  id: iso,
47126
48171
  d,
47127
48172
  fill: fill2,
47128
- stroke: regionStroke,
48173
+ stroke: colorizeActive ? colorizeStroke(fill2) : regionStroke,
47129
48174
  lineNumber,
47130
48175
  layer,
47131
48176
  ...label !== void 0 && { label },
48177
+ ...hasCentroid && { labelX: c[0], labelY: c[1] },
47132
48178
  ...isThisLayer && r.value !== void 0 && { value: r.value },
47133
48179
  ...isThisLayer && Object.keys(r.tags).length > 0 && { tags: r.tags }
47134
48180
  });
@@ -47153,9 +48199,41 @@ function layoutMap(resolved, data, size, opts) {
47153
48199
  });
47154
48200
  }
47155
48201
  }
48202
+ const pointInRings = (px, py, rings) => {
48203
+ let inside = false;
48204
+ for (const ring of rings) {
48205
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
48206
+ const [xi, yi] = ring[i];
48207
+ const [xj, yj] = ring[j];
48208
+ if (yi > py !== yj > py && px < (xj - xi) * (py - yi) / (yj - yi) + xi)
48209
+ inside = !inside;
48210
+ }
48211
+ }
48212
+ return inside;
48213
+ };
48214
+ const fillHitTargets = [...regions, ...insetRegions].map((r) => ({
48215
+ fill: r.fill,
48216
+ rings: parsePathRings(r.d)
48217
+ }));
48218
+ const fillAt = (x, y) => {
48219
+ let hit = water;
48220
+ for (const t of fillHitTargets)
48221
+ if (pointInRings(x, y, t.rings)) hit = t.fill;
48222
+ return hit;
48223
+ };
48224
+ const labelOnFill = (fill2) => {
48225
+ const color = contrastRatio(fill2, palette.textOnFillDark) >= contrastRatio(fill2, palette.textOnFillLight) ? palette.textOnFillDark : palette.textOnFillLight;
48226
+ const haloColor = color === palette.textOnFillLight ? palette.textOnFillDark : palette.textOnFillLight;
48227
+ return {
48228
+ color,
48229
+ halo: contrastRatio(fill2, color) < REGION_LABEL_HALO_RATIO,
48230
+ haloColor
48231
+ };
48232
+ };
48233
+ const reliefAllowed = resolved.directives.noRelief !== true;
47156
48234
  const relief = [];
47157
48235
  let reliefHatch = null;
47158
- if (resolved.directives.relief === true && data.mountainRanges) {
48236
+ if (reliefAllowed && data.mountainRanges) {
47159
48237
  for (const [, f] of decodeLayer(data.mountainRanges)) {
47160
48238
  const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
47161
48239
  if (!viewF) continue;
@@ -47171,16 +48249,32 @@ function layoutMap(resolved, data, size, opts) {
47171
48249
  if (relief.length) {
47172
48250
  const darkTone = isDark ? palette.bg : palette.text;
47173
48251
  const lightTone = isDark ? palette.text : palette.bg;
47174
- const landLum = relativeLuminance(neutralFill);
48252
+ const reliefLandRef = colorizeActive ? isDark ? palette.surface : palette.bg : neutralFill;
48253
+ const landLum = relativeLuminance(reliefLandRef);
47175
48254
  const tone = Math.abs(landLum - relativeLuminance(darkTone)) > 0.04 ? darkTone : lightTone;
47176
48255
  reliefHatch = {
47177
- color: mix(tone, neutralFill, RELIEF_HATCH_STRENGTH),
48256
+ color: mix(tone, reliefLandRef, RELIEF_HATCH_STRENGTH),
47178
48257
  spacing: RELIEF_HATCH_SPACING,
47179
48258
  width: RELIEF_HATCH_WIDTH
47180
48259
  };
47181
48260
  }
47182
48261
  }
47183
- const riverColor = mix(water, regionStroke, 16);
48262
+ let coastlineStyle = null;
48263
+ if (resolved.directives.noCoastline !== true) {
48264
+ const minDim = Math.min(width, height);
48265
+ coastlineStyle = {
48266
+ color: mix(regionStroke, water, COASTLINE_STROKE_MIX),
48267
+ // N equal-width rings: distance steps outward by COASTLINE_STEP; opacity
48268
+ // fades linearly from NEAR (innermost) to FAR (outermost).
48269
+ lines: Array.from({ length: COASTLINE_RING_COUNT }, (_, k) => ({
48270
+ d: (COASTLINE_D0 + k * COASTLINE_STEP) * minDim,
48271
+ thickness: COASTLINE_THICKNESS * minDim,
48272
+ opacity: COASTLINE_OPACITY_NEAR + (COASTLINE_OPACITY_FAR - COASTLINE_OPACITY_NEAR) * k / (COASTLINE_RING_COUNT - 1)
48273
+ })),
48274
+ minExtent: (isGlobalView ? COASTLINE_MIN_EXTENT_GLOBAL : COASTLINE_MIN_EXTENT) * minDim
48275
+ };
48276
+ }
48277
+ const riverColor = mix(palette.colors.blue, water, 32);
47184
48278
  const rivers = [];
47185
48279
  if (data.rivers) {
47186
48280
  for (const [, f] of decodeLayer(data.rivers)) {
@@ -47236,38 +48330,108 @@ function layoutMap(resolved, data, size, opts) {
47236
48330
  const xy = project(p.lon, p.lat);
47237
48331
  if (xy) projected.push({ p, xy });
47238
48332
  }
47239
- const coloGroups = /* @__PURE__ */ new Map();
48333
+ const placePoi = (e, cx, cy, clusterId) => {
48334
+ const { fill: fill2, stroke: stroke2 } = poiFill(e.p);
48335
+ poiScreen.set(e.p.id, { cx, cy, r: radiusFor(e.p) });
48336
+ const num = routeNumberById.get(e.p.id);
48337
+ pois.push({
48338
+ id: e.p.id,
48339
+ cx,
48340
+ cy,
48341
+ r: radiusFor(e.p),
48342
+ fill: fill2,
48343
+ stroke: stroke2,
48344
+ lineNumber: e.p.lineNumber,
48345
+ implicit: !!e.p.implicit,
48346
+ isOrigin: originIds.has(e.p.id),
48347
+ ...num !== void 0 && { routeNumber: num },
48348
+ ...Object.keys(e.p.tags).length > 0 && { tags: e.p.tags },
48349
+ ...clusterId !== void 0 && { clusterId }
48350
+ });
48351
+ };
48352
+ const clusters = [];
48353
+ const connected = /* @__PURE__ */ new Set();
48354
+ for (const e of resolved.edges) {
48355
+ connected.add(e.fromId);
48356
+ connected.add(e.toId);
48357
+ }
48358
+ for (const rt of resolved.routes) {
48359
+ rt.stopIds.forEach((id) => connected.add(id));
48360
+ }
48361
+ const radiusOf = (e) => radiusFor(e.p);
47240
48362
  for (const e of projected) {
47241
- const key = `${Math.round(e.xy[0] / COLO_EPS)},${Math.round(e.xy[1] / COLO_EPS)}`;
47242
- const arr = coloGroups.get(key);
47243
- if (arr) arr.push(e);
47244
- else coloGroups.set(key, [e]);
47245
- }
47246
- for (const group of coloGroups.values()) {
47247
- group.forEach((e, i) => {
47248
- let cx = e.xy[0];
47249
- let cy = e.xy[1];
47250
- if (group.length > 1) {
47251
- const ang = i * GOLDEN_ANGLE;
47252
- cx += Math.cos(ang) * COLO_R;
47253
- cy += Math.sin(ang) * COLO_R;
47254
- }
47255
- const { fill: fill2, stroke: stroke2 } = poiFill(e.p);
47256
- poiScreen.set(e.p.id, { cx, cy, r: radiusFor(e.p) });
47257
- const num = routeNumberById.get(e.p.id);
47258
- pois.push({
47259
- id: e.p.id,
47260
- cx,
47261
- cy,
47262
- r: radiusFor(e.p),
47263
- fill: fill2,
47264
- stroke: stroke2,
47265
- lineNumber: e.p.lineNumber,
47266
- implicit: !!e.p.implicit,
47267
- isOrigin: originIds.has(e.p.id),
47268
- ...num !== void 0 && { routeNumber: num },
47269
- ...Object.keys(e.p.tags).length > 0 && { tags: e.p.tags }
47270
- });
48363
+ if (connected.has(e.p.id)) placePoi(e, e.xy[0], e.xy[1]);
48364
+ }
48365
+ const groups = [];
48366
+ for (const e of projected) {
48367
+ if (connected.has(e.p.id)) continue;
48368
+ const r = radiusOf(e);
48369
+ const near = groups.find(
48370
+ (g) => g.some(
48371
+ (q) => Math.hypot(q.xy[0] - e.xy[0], q.xy[1] - e.xy[1]) < (r + radiusOf(q)) * STACK_OVERLAP
48372
+ )
48373
+ );
48374
+ if (near) near.push(e);
48375
+ else groups.push([e]);
48376
+ }
48377
+ for (const g of groups) {
48378
+ if (g.length === 1) {
48379
+ placePoi(g[0], g[0].xy[0], g[0].xy[1]);
48380
+ continue;
48381
+ }
48382
+ const clusterId = g[0].p.id;
48383
+ const cx0 = g.reduce((s, e) => s + e.xy[0], 0) / g.length;
48384
+ const cy0 = g.reduce((s, e) => s + e.xy[1], 0) / g.length;
48385
+ const maxR = Math.max(...g.map(radiusOf));
48386
+ const sep = 2 * maxR + STACK_RING_GAP;
48387
+ const ringR = Math.max(
48388
+ COLO_R,
48389
+ sep / (2 * Math.sin(Math.PI / Math.max(g.length, 2)))
48390
+ );
48391
+ const positions = g.map((e, i) => {
48392
+ if (g.length <= STACK_RING_MAX) {
48393
+ const ang2 = -Math.PI / 2 + i * 2 * Math.PI / g.length;
48394
+ return {
48395
+ e,
48396
+ mx: cx0 + Math.cos(ang2) * ringR,
48397
+ my: cy0 + Math.sin(ang2) * ringR
48398
+ };
48399
+ }
48400
+ const ang = i * GOLDEN_ANGLE;
48401
+ const rr = ringR * Math.sqrt((i + 1) / g.length);
48402
+ return { e, mx: cx0 + Math.cos(ang) * rr, my: cy0 + Math.sin(ang) * rr };
48403
+ });
48404
+ let minX = cx0 - maxR;
48405
+ let maxX = cx0 + maxR;
48406
+ let minY = cy0 - maxR;
48407
+ let maxY = cy0 + maxR;
48408
+ for (const { mx, my, e } of positions) {
48409
+ const r = radiusOf(e);
48410
+ minX = Math.min(minX, mx - r);
48411
+ maxX = Math.max(maxX, mx + r);
48412
+ minY = Math.min(minY, my - r);
48413
+ maxY = Math.max(maxY, my + r);
48414
+ }
48415
+ let dx = 0;
48416
+ let dy = 0;
48417
+ if (minX + dx < 2) dx = 2 - minX;
48418
+ if (maxX + dx > width - 2) dx = width - 2 - maxX;
48419
+ if (minY + dy < 2) dy = 2 - minY;
48420
+ if (maxY + dy > height - 2) dy = height - 2 - maxY;
48421
+ const legsOut = [];
48422
+ for (const { e, mx, my } of positions) {
48423
+ const fx = mx + dx;
48424
+ const fy = my + dy;
48425
+ placePoi(e, fx, fy, clusterId);
48426
+ legsOut.push({ x2: fx, y2: fy, color: poiFill(e.p).fill });
48427
+ }
48428
+ clusters.push({
48429
+ id: clusterId,
48430
+ cx: cx0 + dx,
48431
+ cy: cy0 + dy,
48432
+ count: g.length,
48433
+ hitR: ringR + maxR + 6,
48434
+ legs: legsOut
47271
48435
  });
47272
48436
  }
47273
48437
  const legs = [];
@@ -47317,16 +48481,26 @@ function layoutMap(resolved, data, size, opts) {
47317
48481
  if (!a || !b) continue;
47318
48482
  const mx = (a.cx + b.cx) / 2;
47319
48483
  const my = (a.cy + b.cy) / 2;
48484
+ const bow = {
48485
+ curved: leg.style === "arc",
48486
+ offset: 0,
48487
+ labelX: mx,
48488
+ labelY: my - 4
48489
+ };
48490
+ const routeLabelStyle = leg.label !== void 0 ? labelOnFill(fillAt(bow.labelX, bow.labelY)) : void 0;
47320
48491
  legs.push({
47321
- d: legPath(a, b, leg.style === "arc", 0),
48492
+ d: legPath(a, b, bow.curved, bow.offset),
47322
48493
  width: routeWidthFor(Number(leg.value)),
47323
48494
  color: mix(palette.text, palette.bg, 72),
47324
48495
  arrow: true,
47325
48496
  lineNumber: leg.lineNumber,
47326
48497
  ...leg.label !== void 0 && {
47327
48498
  label: leg.label,
47328
- labelX: mx,
47329
- labelY: my - 4
48499
+ labelX: bow.labelX,
48500
+ labelY: bow.labelY,
48501
+ labelColor: routeLabelStyle.color,
48502
+ labelHalo: routeLabelStyle.halo,
48503
+ labelHaloColor: routeLabelStyle.haloColor
47330
48504
  }
47331
48505
  });
47332
48506
  }
@@ -47354,20 +48528,29 @@ function layoutMap(resolved, data, size, opts) {
47354
48528
  const a = poiScreen.get(e.fromId);
47355
48529
  const b = poiScreen.get(e.toId);
47356
48530
  if (!a || !b) return;
47357
- const curved = e.style === "arc" || n > 1;
47358
- const offset = n > 1 ? (i - (n - 1) / 2) * FAN_STEP : 0;
48531
+ const fanOffset = n > 1 ? (i - (n - 1) / 2) * FAN_STEP : 0;
47359
48532
  const mx = (a.cx + b.cx) / 2;
47360
48533
  const my = (a.cy + b.cy) / 2;
48534
+ const bow = {
48535
+ curved: e.style === "arc" || n > 1,
48536
+ offset: fanOffset,
48537
+ labelX: mx,
48538
+ labelY: my - 4
48539
+ };
48540
+ const edgeLabelStyle = e.label !== void 0 ? labelOnFill(fillAt(bow.labelX, bow.labelY)) : void 0;
47361
48541
  legs.push({
47362
- d: legPath(a, b, curved, offset),
48542
+ d: legPath(a, b, bow.curved, bow.offset),
47363
48543
  width: widthFor(e),
47364
48544
  color: mix(palette.text, palette.bg, 66),
47365
48545
  arrow: e.directed,
47366
48546
  lineNumber: e.lineNumber,
47367
48547
  ...e.label !== void 0 && {
47368
48548
  label: e.label,
47369
- labelX: mx,
47370
- labelY: my - 4
48549
+ labelX: bow.labelX,
48550
+ labelY: bow.labelY,
48551
+ labelColor: edgeLabelStyle.color,
48552
+ labelHalo: edgeLabelStyle.halo,
48553
+ labelHaloColor: edgeLabelStyle.haloColor
47371
48554
  }
47372
48555
  });
47373
48556
  });
@@ -47409,48 +48592,73 @@ function layoutMap(resolved, data, size, opts) {
47409
48592
  }
47410
48593
  }
47411
48594
  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));
47412
- const regionLabelMode = resolved.directives.regionLabels ?? "off";
48595
+ const showRegionLabels = resolved.directives.noRegionLabels !== true;
48596
+ const isCompact = width < COMPACT_WIDTH_PX;
47413
48597
  const LABEL_PADX = 6;
47414
48598
  const LABEL_PADY = 3;
47415
- const labelW = (text) => measureLegendText(text, FONT) + 2 * LABEL_PADX;
47416
- const labelH = FONT + 2 * LABEL_PADY;
48599
+ const labelW = (text) => measureLegendText(text, FONT2) + 2 * LABEL_PADX;
48600
+ const labelH = FONT2 + 2 * LABEL_PADY;
47417
48601
  const pushRegionLabel = (x, y, text, fill2, lineNumber) => {
47418
- const color = contrastText(
47419
- fill2,
47420
- palette.textOnFillLight,
47421
- palette.textOnFillDark
48602
+ const { color, haloColor } = labelOnFill(fill2);
48603
+ const halfW = measureLegendText(text, FONT2) / 2;
48604
+ const overflows = [y - FONT2 * 0.55, y - FONT2 * 0.1].some(
48605
+ (sy) => fillAt(x - halfW, sy) !== fill2 || fillAt(x + halfW, sy) !== fill2
47422
48606
  );
47423
- const haloColor = color === palette.textOnFillLight ? palette.textOnFillDark : palette.textOnFillLight;
47424
48607
  labels.push({
47425
48608
  x,
47426
48609
  y,
47427
48610
  text,
47428
48611
  anchor: "middle",
47429
48612
  color,
47430
- halo: true,
48613
+ halo: overflows,
47431
48614
  haloColor,
47432
48615
  lineNumber
47433
48616
  });
47434
48617
  };
47435
- const WORLD_LABEL_ANCHORS = {
47436
- US: [-98.5, 39.5]
47437
- // CONUS geographic centre (near Lebanon, Kansas)
48618
+ const REGION_LABEL_GAP = 2;
48619
+ const regionLabelRect = (cx, cy, text) => {
48620
+ const w = measureLegendText(text, FONT2) + 2 * REGION_LABEL_GAP;
48621
+ return { x: cx - w / 2, y: cy - FONT2 / 2, w, h: FONT2 };
47438
48622
  };
47439
- if (regionLabelMode === "full" || regionLabelMode === "abbrev") {
47440
- for (const r of regions) {
47441
- if (r.layer === "base" || r.label === void 0) continue;
47442
- const f = r.layer === "us-state" ? usLayer?.get(r.id) : worldLayer.get(r.id);
47443
- if (!f) continue;
48623
+ if (showRegionLabels) {
48624
+ const frameContainers = new Set(resolved.poiFrameContainers);
48625
+ const entries = regions.map((r) => {
48626
+ const isContainer = frameContainers.has(r.id);
48627
+ if (r.layer === "base" && !isContainer || r.label === void 0)
48628
+ return null;
48629
+ const isUsState = r.layer === "us-state" || r.id.startsWith("US-");
48630
+ const f = isUsState ? usLayer?.get(r.id) : worldLayer.get(r.id);
48631
+ if (!f) return null;
47444
48632
  const [[x0, y0], [x1, y1]] = path.bounds(f);
47445
- const text = regionLabelMode === "abbrev" ? r.id.replace(/^US-/, "") : r.label;
47446
- if (labelW(text) > x1 - x0 || labelH > y1 - y0) continue;
47447
- const anchor = r.layer !== "us-state" ? WORLD_LABEL_ANCHORS[r.id] : void 0;
48633
+ const boxW = x1 - x0;
48634
+ const boxH = y1 - y0;
48635
+ const abbrev = isUsState ? r.id.replace(/^US-/, "") : void 0;
48636
+ const candidates = abbrev !== void 0 ? isCompact ? [abbrev, r.label] : [r.label, abbrev] : [r.label];
48637
+ const anchor = !isUsState ? WORLD_LABEL_ANCHORS[r.id] : void 0;
47448
48638
  const c = anchor ? project(anchor[0], anchor[1]) : path.centroid(f);
47449
- if (!c || !Number.isFinite(c[0])) continue;
48639
+ if (!c || !Number.isFinite(c[0])) return null;
48640
+ return { r, c, boxW, boxH, area: boxW * boxH, candidates };
48641
+ }).filter((e) => e !== null).sort((a, b) => b.area - a.area || a.r.lineNumber - b.r.lineNumber);
48642
+ const placedRegionRects = [];
48643
+ const POI_LABEL_PAD = 14;
48644
+ const poiObstacles = pois.map((p) => ({
48645
+ x: p.cx - p.r - POI_LABEL_PAD,
48646
+ y: p.cy - p.r - POI_LABEL_PAD,
48647
+ w: 2 * (p.r + POI_LABEL_PAD),
48648
+ h: 2 * (p.r + POI_LABEL_PAD)
48649
+ }));
48650
+ for (const { r, c, boxW, boxH, candidates } of entries) {
48651
+ const text = candidates.find((t) => {
48652
+ if (labelW(t) > boxW || labelH > boxH) return false;
48653
+ const rect = regionLabelRect(c[0], c[1], t);
48654
+ return !placedRegionRects.some((p) => rectsOverlap(rect, p)) && !poiObstacles.some((o) => rectsOverlap(rect, o));
48655
+ });
48656
+ if (text === void 0) continue;
48657
+ placedRegionRects.push(regionLabelRect(c[0], c[1], text));
47450
48658
  pushRegionLabel(c[0], c[1], text, r.fill, r.lineNumber);
47451
48659
  }
47452
48660
  for (const seed of insetLabelSeeds) {
47453
- const text = regionLabelMode === "abbrev" ? seed.iso.replace(/^US-/, "") : seed.name;
48661
+ const text = isCompact ? seed.iso.replace(/^US-/, "") : seed.name;
47454
48662
  const src = regionById.get(seed.iso);
47455
48663
  pushRegionLabel(
47456
48664
  seed.x,
@@ -47461,22 +48669,26 @@ function layoutMap(resolved, data, size, opts) {
47461
48669
  );
47462
48670
  }
47463
48671
  }
47464
- const poiLabelMode = resolved.directives.poiLabels ?? "auto";
47465
- if (poiLabelMode !== "off") {
47466
- const ordered = [...pois].sort(
47467
- (a, b) => a.lineNumber - b.lineNumber || (a.id < b.id ? -1 : 1)
47468
- );
48672
+ if (resolved.directives.noPoiLabels !== true) {
48673
+ const ordered = [...pois].filter((p) => p.clusterId === void 0).sort((a, b) => a.lineNumber - b.lineNumber || (a.id < b.id ? -1 : 1));
47469
48674
  const poiById = new Map(resolved.pois.map((q) => [q.id, q]));
47470
48675
  const labelText = (p) => {
47471
48676
  const src = poiById.get(p.id);
47472
48677
  return src?.label ?? src?.name ?? p.id;
47473
48678
  };
47474
- const poiLabH = FONT * 1.25;
48679
+ const poiLabH = FONT2 * 1.25;
47475
48680
  const labelInfo = (p) => {
47476
48681
  const text = labelText(p);
47477
- return { text, w: measureLegendText(text, FONT) };
48682
+ return { text, w: measureLegendText(text, FONT2) };
47478
48683
  };
47479
48684
  const GAP = 3;
48685
+ const clusterMembersById = /* @__PURE__ */ new Map();
48686
+ for (const p of pois) {
48687
+ if (p.clusterId === void 0) continue;
48688
+ const arr = clusterMembersById.get(p.clusterId);
48689
+ if (arr) arr.push(p);
48690
+ else clusterMembersById.set(p.clusterId, [p]);
48691
+ }
47480
48692
  const inlineRect = (p, w, side) => {
47481
48693
  switch (side) {
47482
48694
  case "right":
@@ -47506,11 +48718,11 @@ function layoutMap(resolved, data, size, opts) {
47506
48718
  const x = side === "right" ? rect.x : side === "left" ? rect.x + w : p.cx;
47507
48719
  labels.push({
47508
48720
  x,
47509
- y: rect.y + poiLabH / 2 + FONT / 3,
48721
+ y: rect.y + poiLabH / 2 + FONT2 / 3,
47510
48722
  text,
47511
48723
  anchor,
47512
48724
  color: palette.text,
47513
- halo: true,
48725
+ halo: false,
47514
48726
  haloColor: palette.bg,
47515
48727
  poiId: p.id,
47516
48728
  lineNumber: p.lineNumber
@@ -47521,43 +48733,60 @@ function layoutMap(resolved, data, size, opts) {
47521
48733
  return rect.x >= 0 && rect.x + rect.w <= width && rect.y >= 0 && rect.y + rect.h <= height && !collides(rect);
47522
48734
  };
47523
48735
  const GROUP_R = 30;
47524
- const groups = [];
48736
+ const groups2 = [];
47525
48737
  for (const p of ordered) {
47526
- const near = groups.find(
48738
+ const near = groups2.find(
47527
48739
  (g) => g.some((q) => Math.hypot(q.cx - p.cx, q.cy - p.cy) < GROUP_R)
47528
48740
  );
47529
48741
  if (near) near.push(p);
47530
- else groups.push([p]);
48742
+ else groups2.push([p]);
47531
48743
  }
47532
48744
  const ROW_GAP2 = 3;
47533
48745
  const step = poiLabH + ROW_GAP2;
47534
48746
  const COL_GAP = 16;
47535
- const placeColumn = (group) => {
47536
- const items = group.map((p) => ({ p, ...labelInfo(p) })).sort((a, b) => a.p.cy - b.p.cy || (a.text < b.text ? -1 : 1));
48747
+ const makeItems = (group) => group.map((p) => ({ p, ...labelInfo(p) })).sort((a, b) => a.p.cy - b.p.cy || (a.text < b.text ? -1 : 1));
48748
+ const columnRows = (items, side) => {
47537
48749
  const left = Math.min(...items.map((o) => o.p.cx - o.p.r));
47538
48750
  const right = Math.max(...items.map((o) => o.p.cx + o.p.r));
47539
- const cyMid = (Math.min(...items.map((o) => o.p.cy)) + Math.max(...items.map((o) => o.p.cy))) / 2;
47540
48751
  const maxW = Math.max(...items.map((o) => o.w));
47541
- const side = right + COL_GAP + maxW <= width - 2 ? "right" : "left";
47542
- const colX = side === "right" ? right + COL_GAP : left - COL_GAP;
48752
+ const cyMid = (Math.min(...items.map((o) => o.p.cy)) + Math.max(...items.map((o) => o.p.cy))) / 2;
48753
+ const colX = side === "right" ? Math.min(right + COL_GAP, width - 2 - maxW) : Math.max(left - COL_GAP, 2 + maxW);
47543
48754
  const totalH = items.length * step;
47544
48755
  let startY = cyMid - totalH / 2;
47545
48756
  startY = Math.max(2, Math.min(startY, height - totalH - 2));
47546
- items.forEach((o, i) => {
48757
+ return items.map((o, i) => {
47547
48758
  const rowCy = startY + i * step + step / 2;
47548
- obstacles.push({
47549
- x: side === "right" ? colX : colX - o.w,
47550
- y: rowCy - poiLabH / 2,
47551
- w: o.w,
47552
- h: poiLabH
47553
- });
48759
+ return {
48760
+ o,
48761
+ colX,
48762
+ rowCy,
48763
+ rect: {
48764
+ x: side === "right" ? colX : colX - o.w,
48765
+ y: rowCy - poiLabH / 2,
48766
+ w: o.w,
48767
+ h: poiLabH
48768
+ }
48769
+ };
48770
+ });
48771
+ };
48772
+ const wouldColumnBeClean = (items, side) => columnRows(items, side).every(
48773
+ ({ rect }) => rect.x >= 0 && rect.x + rect.w <= width && rect.y >= 0 && rect.y + rect.h <= height && !collides(rect)
48774
+ );
48775
+ const defaultColumnSide = (items) => {
48776
+ const right = Math.max(...items.map((o) => o.p.cx + o.p.r));
48777
+ const maxW = Math.max(...items.map((o) => o.w));
48778
+ return right + COL_GAP + maxW <= width - 2 ? "right" : "left";
48779
+ };
48780
+ const commitColumn = (items, side, clusterId) => {
48781
+ for (const { o, colX, rowCy, rect } of columnRows(items, side)) {
48782
+ obstacles.push(rect);
47554
48783
  labels.push({
47555
48784
  x: colX,
47556
- y: rowCy + FONT / 3,
48785
+ y: rowCy + FONT2 / 3,
47557
48786
  text: o.text,
47558
48787
  anchor: side === "right" ? "start" : "end",
47559
48788
  color: palette.text,
47560
- halo: true,
48789
+ halo: false,
47561
48790
  haloColor: palette.bg,
47562
48791
  leader: {
47563
48792
  x1: o.p.cx,
@@ -47567,24 +48796,141 @@ function layoutMap(resolved, data, size, opts) {
47567
48796
  },
47568
48797
  leaderColor: o.p.fill,
47569
48798
  poiId: o.p.id,
47570
- lineNumber: o.p.lineNumber
48799
+ lineNumber: o.p.lineNumber,
48800
+ ...clusterId !== void 0 && { clusterMember: clusterId }
47571
48801
  });
48802
+ }
48803
+ };
48804
+ const pushHidden = (p) => {
48805
+ const { text, w } = labelInfo(p);
48806
+ let x = p.cx + p.r + GAP;
48807
+ let anchor = "start";
48808
+ if (x + w > width) {
48809
+ x = p.cx - p.r - GAP - w;
48810
+ anchor = "end";
48811
+ }
48812
+ const y = Math.max(0, Math.min(p.cy - poiLabH / 2, height - poiLabH));
48813
+ labels.push({
48814
+ x: anchor === "start" ? x : x + w,
48815
+ y: y + poiLabH / 2 + FONT2 / 3,
48816
+ text,
48817
+ anchor,
48818
+ color: palette.text,
48819
+ halo: false,
48820
+ haloColor: palette.bg,
48821
+ poiId: p.id,
48822
+ hidden: true,
48823
+ lineNumber: p.lineNumber
47572
48824
  });
47573
48825
  };
47574
- for (const g of groups) {
48826
+ for (const [clusterId, members] of clusterMembersById) {
48827
+ if (members.length === 0) continue;
48828
+ const items = makeItems(members);
48829
+ const side = wouldColumnBeClean(items, "right") ? "right" : wouldColumnBeClean(items, "left") ? "left" : defaultColumnSide(items);
48830
+ commitColumn(items, side, clusterId);
48831
+ }
48832
+ const maxExtent = MAX_CLUSTER_EXTENT_FACTOR * Math.min(width, height);
48833
+ const clusterPending = [];
48834
+ for (const g of groups2) {
48835
+ const items = makeItems(g);
47575
48836
  if (g.length === 1) {
47576
- const p = g[0];
47577
- const { text, w } = labelInfo(p);
48837
+ const { p, text, w } = items[0];
47578
48838
  const side = ["right", "left", "above", "below"].find(
47579
48839
  (s) => inlineFits(p, w, s)
47580
48840
  );
47581
- if (side) {
47582
- pushInline(p, text, w, side);
47583
- continue;
48841
+ if (side) pushInline(p, text, w, side);
48842
+ else commitColumn(items, defaultColumnSide(items));
48843
+ continue;
48844
+ }
48845
+ const left = Math.min(...items.map((o) => o.p.cx - o.p.r));
48846
+ const right = Math.max(...items.map((o) => o.p.cx + o.p.r));
48847
+ const minCy = Math.min(...items.map((o) => o.p.cy));
48848
+ const maxCy = Math.max(...items.map((o) => o.p.cy));
48849
+ const diag = Math.hypot(right - left, maxCy - minCy);
48850
+ if (diag > maxExtent || items.length > MAX_COLUMN_ROWS) {
48851
+ items.forEach((o) => pushHidden(o.p));
48852
+ } else {
48853
+ clusterPending.push(items);
48854
+ }
48855
+ }
48856
+ for (const items of clusterPending) {
48857
+ const side = ["right", "left"].find(
48858
+ (s) => wouldColumnBeClean(items, s)
48859
+ );
48860
+ if (side) commitColumn(items, side);
48861
+ else items.forEach((o) => pushHidden(o.p));
48862
+ }
48863
+ }
48864
+ if (resolved.directives.noContextLabels !== true) {
48865
+ for (const l of labels) {
48866
+ if (l.hidden) continue;
48867
+ const w = labelW(l.text);
48868
+ const x = l.anchor === "start" ? l.x : l.anchor === "end" ? l.x - w : l.x - w / 2;
48869
+ obstacles.push({ x, y: l.y - labelH / 2, w, h: labelH });
48870
+ }
48871
+ for (const box of insets)
48872
+ obstacles.push({ x: box.x, y: box.y, w: box.w, h: box.h });
48873
+ const countryCandidates = [];
48874
+ for (const f of worldLayer.values()) {
48875
+ const iso = typeof f.id === "string" ? f.id : String(f.id ?? "");
48876
+ if (!iso || regionById.has(iso)) continue;
48877
+ let hasReferencedSub = false;
48878
+ for (const k of regionById.keys())
48879
+ if (k.startsWith(iso + "-")) {
48880
+ hasReferencedSub = true;
48881
+ break;
47584
48882
  }
48883
+ if (hasReferencedSub) continue;
48884
+ const b = path.bounds(f);
48885
+ const [x0, y0] = b[0];
48886
+ const [x1, y1] = b[1];
48887
+ if (!Number.isFinite(x0) || !Number.isFinite(x1)) continue;
48888
+ const anchorLngLat = WORLD_LABEL_ANCHORS[iso];
48889
+ const a = anchorLngLat ? project(anchorLngLat[0], anchorLngLat[1]) : path.centroid(f);
48890
+ countryCandidates.push({
48891
+ name: f.properties?.name ?? iso,
48892
+ bbox: [x0, y0, x1, y1],
48893
+ anchor: a && Number.isFinite(a[0]) ? [a[0], a[1]] : null
48894
+ });
48895
+ }
48896
+ const framedStateContainers = (resolved.poiFrameContainers ?? []).some(
48897
+ (id) => id.startsWith("US-")
48898
+ );
48899
+ if (usLayer && framedStateContainers) {
48900
+ const containerSet = new Set(resolved.poiFrameContainers);
48901
+ for (const [iso, f] of usLayer) {
48902
+ if (containerSet.has(iso) || regionById.has(iso)) continue;
48903
+ const viewF = cullFeatureToView(f);
48904
+ if (!viewF) continue;
48905
+ const b = path.bounds(viewF);
48906
+ const [x0, y0] = b[0];
48907
+ const [x1, y1] = b[1];
48908
+ if (!Number.isFinite(x0) || !Number.isFinite(x1)) continue;
48909
+ const a = path.centroid(viewF);
48910
+ countryCandidates.push({
48911
+ name: f.properties?.name ?? iso,
48912
+ bbox: [x0, y0, x1, y1],
48913
+ anchor: a && Number.isFinite(a[0]) ? [a[0], a[1]] : null
48914
+ });
47585
48915
  }
47586
- placeColumn(g);
47587
48916
  }
48917
+ const contextLabels = placeContextLabels({
48918
+ projection: resolved.projection,
48919
+ dLonSpan,
48920
+ dLatSpan,
48921
+ width,
48922
+ height,
48923
+ waterBodies: data.waterBodies,
48924
+ countries: countryCandidates,
48925
+ palette,
48926
+ project,
48927
+ collides,
48928
+ // Water labels must stay over open water — `fillAt` returns the ocean
48929
+ // backdrop colour off-land and a region fill on-land (lakes/states count
48930
+ // as land here, which is the safe side for an ocean name).
48931
+ overLand: (x, y) => fillAt(x, y) !== water
48932
+ });
48933
+ labels.push(...contextLabels);
47588
48934
  }
47589
48935
  let legend = null;
47590
48936
  if (!resolved.directives.noLegend) {
@@ -47621,58 +48967,102 @@ function layoutMap(resolved, data, size, opts) {
47621
48967
  rivers,
47622
48968
  relief,
47623
48969
  reliefHatch,
48970
+ coastlineStyle,
47624
48971
  legs,
47625
48972
  pois,
48973
+ clusters,
47626
48974
  labels,
47627
48975
  legend,
47628
48976
  insets,
47629
48977
  insetRegions,
47630
48978
  projection,
47631
- stretch: stretchParams
48979
+ stretch: stretchParams,
48980
+ diagnostics: []
47632
48981
  };
47633
48982
  }
47634
- var FIT_PAD, RAMP_FLOOR, R_DEFAULT, R_MIN, R_MAX, W_MIN, W_MAX, FONT, COLO_EPS, LAND_TINT_LIGHT, LAND_TINT_DARK, TAG_TINT_LIGHT, TAG_TINT_DARK, WATER_TINT_LIGHT, WATER_TINT_DARK, RIVER_WIDTH, RELIEF_MIN_AREA, RELIEF_MIN_DIM, RELIEF_HATCH_SPACING, RELIEF_HATCH_WIDTH, RELIEF_HATCH_STRENGTH, FOREIGN_TINT_LIGHT, FOREIGN_TINT_DARK, MUTED_FOREIGN_LIGHT, MUTED_FOREIGN_DARK, COLO_R, GOLDEN_ANGLE, FAN_STEP, ARC_CURVE_FRAC, usConusProjection, alaskaProjection, hawaiiProjection, INSET_STATES, US_NON_CONUS;
48983
+ var FIT_PAD, RAMP_FLOOR2, R_DEFAULT, R_MIN, R_MAX, W_MIN, W_MAX, FONT2, WORLD_LABEL_ANCHORS, MAX_CLUSTER_EXTENT_FACTOR, MAX_COLUMN_ROWS, REGION_LABEL_HALO_RATIO, LAND_TINT_LIGHT, LAND_TINT_DARK, TAG_TINT_LIGHT, TAG_TINT_DARK, WATER_TINT_LIGHT, WATER_TINT_DARK, RIVER_WIDTH, COMPACT_WIDTH_PX, RELIEF_MIN_AREA, RELIEF_MIN_DIM, RELIEF_HATCH_SPACING, RELIEF_HATCH_WIDTH, RELIEF_HATCH_STRENGTH, COASTLINE_RING_COUNT, COASTLINE_D0, COASTLINE_STEP, COASTLINE_THICKNESS, COASTLINE_OPACITY_NEAR, COASTLINE_OPACITY_FAR, COASTLINE_MIN_EXTENT, COASTLINE_MIN_EXTENT_GLOBAL, COASTLINE_STROKE_MIX, FOREIGN_TINT_LIGHT, FOREIGN_TINT_DARK, MUTED_FOREIGN_LIGHT, MUTED_FOREIGN_DARK, COLO_R, GOLDEN_ANGLE, STACK_OVERLAP, STACK_RING_MAX, STACK_RING_GAP, FAN_STEP, ARC_CURVE_FRAC, decodeCache, usConusProjection, alaskaProjection, hawaiiProjection, INSET_STATES, inAlaska, inHawaii, FOREIGN_BORDER, US_NON_CONUS;
47635
48984
  var init_layout15 = __esm({
47636
48985
  "src/map/layout.ts"() {
47637
48986
  "use strict";
47638
48987
  init_color_utils();
48988
+ init_geo();
48989
+ init_colorize();
47639
48990
  init_colors();
47640
48991
  init_label_layout();
47641
48992
  init_legend_constants();
47642
48993
  init_title_constants();
48994
+ init_context_labels();
47643
48995
  FIT_PAD = 24;
47644
- RAMP_FLOOR = 15;
48996
+ RAMP_FLOOR2 = 15;
47645
48997
  R_DEFAULT = 6;
47646
48998
  R_MIN = 4;
47647
48999
  R_MAX = 22;
47648
49000
  W_MIN = 1.25;
47649
49001
  W_MAX = 8;
47650
- FONT = 11;
47651
- COLO_EPS = 1.5;
49002
+ FONT2 = 11;
49003
+ WORLD_LABEL_ANCHORS = {
49004
+ US: [-98.5, 39.5]
49005
+ // CONUS geographic centre (near Lebanon, Kansas)
49006
+ };
49007
+ MAX_CLUSTER_EXTENT_FACTOR = 0.18;
49008
+ MAX_COLUMN_ROWS = 7;
49009
+ REGION_LABEL_HALO_RATIO = 4.5;
47652
49010
  LAND_TINT_LIGHT = 12;
47653
49011
  LAND_TINT_DARK = 24;
47654
49012
  TAG_TINT_LIGHT = 60;
47655
49013
  TAG_TINT_DARK = 68;
47656
- WATER_TINT_LIGHT = 13;
47657
- WATER_TINT_DARK = 14;
49014
+ WATER_TINT_LIGHT = 24;
49015
+ WATER_TINT_DARK = 24;
47658
49016
  RIVER_WIDTH = 1.3;
49017
+ COMPACT_WIDTH_PX = 480;
47659
49018
  RELIEF_MIN_AREA = 12;
47660
49019
  RELIEF_MIN_DIM = 2;
47661
- RELIEF_HATCH_SPACING = 3;
47662
- RELIEF_HATCH_WIDTH = 0.25;
47663
- RELIEF_HATCH_STRENGTH = 32;
49020
+ RELIEF_HATCH_SPACING = 1.5;
49021
+ RELIEF_HATCH_WIDTH = 0.2;
49022
+ RELIEF_HATCH_STRENGTH = 26;
49023
+ COASTLINE_RING_COUNT = 5;
49024
+ COASTLINE_D0 = 16e-4;
49025
+ COASTLINE_STEP = 28e-4;
49026
+ COASTLINE_THICKNESS = 14e-4;
49027
+ COASTLINE_OPACITY_NEAR = 0.5;
49028
+ COASTLINE_OPACITY_FAR = 0.1;
49029
+ COASTLINE_MIN_EXTENT = 6e-4;
49030
+ COASTLINE_MIN_EXTENT_GLOBAL = 6e-4;
49031
+ COASTLINE_STROKE_MIX = 32;
47664
49032
  FOREIGN_TINT_LIGHT = 30;
47665
49033
  FOREIGN_TINT_DARK = 62;
47666
49034
  MUTED_FOREIGN_LIGHT = 28;
47667
49035
  MUTED_FOREIGN_DARK = 16;
47668
49036
  COLO_R = 9;
47669
49037
  GOLDEN_ANGLE = 2.399963229728653;
49038
+ STACK_OVERLAP = 1;
49039
+ STACK_RING_MAX = 8;
49040
+ STACK_RING_GAP = 4;
47670
49041
  FAN_STEP = 16;
47671
49042
  ARC_CURVE_FRAC = 0.18;
49043
+ decodeCache = /* @__PURE__ */ new WeakMap();
47672
49044
  usConusProjection = () => geoConicEqualArea().parallels([29.5, 45.5]).rotate([96, 0]);
47673
49045
  alaskaProjection = () => geoConicEqualArea().rotate([154, 0]).center([-2, 58.5]).parallels([55, 65]);
47674
49046
  hawaiiProjection = () => geoMercator();
47675
49047
  INSET_STATES = /* @__PURE__ */ new Set(["US-AK", "US-HI"]);
49048
+ inAlaska = (lon, lat) => lat >= 51 && (lon <= -129 || lon >= 172);
49049
+ inHawaii = (lon, lat) => lat >= 18 && lat <= 23 && lon >= -161 && lon <= -154;
49050
+ FOREIGN_BORDER = {
49051
+ CA: [
49052
+ "US-AK",
49053
+ "US-WA",
49054
+ "US-ID",
49055
+ "US-MT",
49056
+ "US-ND",
49057
+ "US-MN",
49058
+ "US-MI",
49059
+ "US-NY",
49060
+ "US-VT",
49061
+ "US-NH",
49062
+ "US-ME"
49063
+ ],
49064
+ MX: ["US-CA", "US-AZ", "US-NM", "US-TX"]
49065
+ };
47676
49066
  US_NON_CONUS = /* @__PURE__ */ new Set([
47677
49067
  "US-AK",
47678
49068
  "US-HI",
@@ -47692,6 +49082,98 @@ __export(renderer_exports16, {
47692
49082
  renderMapForExport: () => renderMapForExport
47693
49083
  });
47694
49084
  import * as d3Selection18 from "d3-selection";
49085
+ function pointInRing2(px, py, ring) {
49086
+ let inside = false;
49087
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
49088
+ const [xi, yi] = ring[i];
49089
+ const [xj, yj] = ring[j];
49090
+ if (yi > py !== yj > py && px < (xj - xi) * (py - yi) / (yj - yi) + xi)
49091
+ inside = !inside;
49092
+ }
49093
+ return inside;
49094
+ }
49095
+ function ringToPath(ring) {
49096
+ let d = "";
49097
+ for (let i = 0; i < ring.length; i++)
49098
+ d += (i ? "L" : "M") + ring[i][0] + "," + ring[i][1];
49099
+ return d + "Z";
49100
+ }
49101
+ function polylineToPath(pts) {
49102
+ let d = "";
49103
+ for (let i = 0; i < pts.length; i++)
49104
+ d += (i ? "L" : "M") + pts[i][0] + "," + pts[i][1];
49105
+ return d;
49106
+ }
49107
+ function ringToCoastPaths(ring, frame) {
49108
+ if (!frame) return [ringToPath(ring)];
49109
+ const n = ring.length;
49110
+ const eps = 0.75;
49111
+ const onL = (x) => Math.abs(x) <= eps;
49112
+ const onR = (x) => Math.abs(x - frame.w) <= eps;
49113
+ const onT = (y) => Math.abs(y) <= eps;
49114
+ const onB = (y) => Math.abs(y - frame.h) <= eps;
49115
+ const isFrameEdge = (a, b) => onL(a[0]) && onL(b[0]) || onR(a[0]) && onR(b[0]) || onT(a[1]) && onT(b[1]) || onB(a[1]) && onB(b[1]);
49116
+ let firstBreak = -1;
49117
+ for (let i = 0; i < n; i++)
49118
+ if (isFrameEdge(ring[i], ring[(i + 1) % n])) {
49119
+ firstBreak = i;
49120
+ break;
49121
+ }
49122
+ if (firstBreak === -1) return [ringToPath(ring)];
49123
+ const paths = [];
49124
+ let cur = [];
49125
+ const start = (firstBreak + 1) % n;
49126
+ for (let k = 0; k < n; k++) {
49127
+ const i = (start + k) % n;
49128
+ const a = ring[i];
49129
+ const b = ring[(i + 1) % n];
49130
+ if (isFrameEdge(a, b)) {
49131
+ if (cur.length >= 2) paths.push(polylineToPath(cur));
49132
+ cur = [];
49133
+ continue;
49134
+ }
49135
+ if (cur.length === 0) cur.push(a);
49136
+ cur.push(b);
49137
+ }
49138
+ if (cur.length >= 2) paths.push(polylineToPath(cur));
49139
+ return paths;
49140
+ }
49141
+ function coastlineOuterRings(regions, minExtent, frame) {
49142
+ const paths = [];
49143
+ for (const r of regions) {
49144
+ const rings = parsePathRings(r.d);
49145
+ for (let i = 0; i < rings.length; i++) {
49146
+ const ring = rings[i];
49147
+ if (ring.length < 3) continue;
49148
+ let minX = Infinity;
49149
+ let minY = Infinity;
49150
+ let maxX = -Infinity;
49151
+ let maxY = -Infinity;
49152
+ for (const [x, y] of ring) {
49153
+ if (x < minX) minX = x;
49154
+ if (x > maxX) maxX = x;
49155
+ if (y < minY) minY = y;
49156
+ if (y > maxY) maxY = y;
49157
+ }
49158
+ if (Math.max(maxX - minX, maxY - minY) < minExtent) continue;
49159
+ const [fx, fy] = ring[0];
49160
+ let depth = 0;
49161
+ for (let j = 0; j < rings.length; j++)
49162
+ if (j !== i && pointInRing2(fx, fy, rings[j])) depth++;
49163
+ if (depth % 2 === 1) continue;
49164
+ paths.push(...ringToCoastPaths(ring, frame));
49165
+ }
49166
+ }
49167
+ return paths;
49168
+ }
49169
+ function appendWaterLines(g, outerRings, style, flatWater) {
49170
+ const d = outerRings.join(" ");
49171
+ const linesOuterFirst = [...style.lines].sort((a, b) => b.d - a.d);
49172
+ for (const line12 of linesOuterFirst) {
49173
+ g.append("path").attr("d", d).attr("stroke", style.color).attr("stroke-width", 2 * (line12.d + line12.thickness)).attr("stroke-opacity", line12.opacity).attr("stroke-linejoin", "round").attr("stroke-linecap", "round");
49174
+ g.append("path").attr("d", d).attr("stroke", flatWater).attr("stroke-width", 2 * line12.d).attr("stroke-linejoin", "round").attr("stroke-linecap", "round");
49175
+ }
49176
+ }
47695
49177
  function renderMap(container, resolved, data, palette, isDark, onClickItem, exportDims, activeGroupOverride) {
47696
49178
  d3Selection18.select(container).selectAll(":not([data-d3-tooltip])").remove();
47697
49179
  const width = exportDims?.width ?? container.clientWidth;
@@ -47704,6 +49186,11 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47704
49186
  {
47705
49187
  palette,
47706
49188
  isDark,
49189
+ // Export-only: forward the contain-fit request from mapExportDimensions so a
49190
+ // clamped/floored (off-aspect) export canvas letterboxes instead of
49191
+ // stretch-distorting. The in-app preview pane passes no exportDims → unset →
49192
+ // keeps the global stretch-fill.
49193
+ preferContain: exportDims?.preferContain ?? false,
47707
49194
  ...activeGroupOverride !== void 0 && {
47708
49195
  activeGroup: activeGroupOverride
47709
49196
  }
@@ -47717,6 +49204,11 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47717
49204
  const gRegions = svg.append("g").attr("class", "dgmo-map-regions");
47718
49205
  const drawRegion = (g, r, strokeWidth) => {
47719
49206
  const p = g.append("path").attr("d", r.d).attr("fill", r.fill).attr("stroke", r.stroke).attr("stroke-width", strokeWidth);
49207
+ if (r.label) p.attr("data-region-name", r.label);
49208
+ if (r.id && r.id !== "lake") p.attr("data-iso", r.id);
49209
+ if (r.labelX !== void 0 && r.labelY !== void 0) {
49210
+ p.attr("data-label-x", r.labelX).attr("data-label-y", r.labelY);
49211
+ }
47720
49212
  if (r.layer !== "base") {
47721
49213
  p.classed("dgmo-map-region", true).attr("data-region", r.id);
47722
49214
  if (r.value !== void 0) p.attr("data-value", r.value);
@@ -47746,28 +49238,112 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47746
49238
  const landClip = defs.append("clipPath").attr("id", landClipId);
47747
49239
  for (const r of layout.regions)
47748
49240
  if (r.id !== "lake") landClip.append("path").attr("d", r.d);
47749
- const gRelief = svg.append("g").attr("clip-path", `url(#${landClipId})`).append("g").attr("class", "dgmo-map-relief").attr("clip-path", `url(#${rangeClipId})`).attr("stroke", h.color).attr("stroke-width", h.width).attr("vector-effect", "non-scaling-stroke");
49241
+ const gRelief = svg.append("g").attr("clip-path", `url(#${landClipId})`).style("pointer-events", "none").append("g").attr("class", "dgmo-map-relief").attr("clip-path", `url(#${rangeClipId})`).attr("stroke", h.color).attr("stroke-width", h.width).attr("vector-effect", "non-scaling-stroke");
47750
49242
  for (let y = h.spacing; y < height; y += h.spacing) {
47751
49243
  gRelief.append("line").attr("x1", 0).attr("y1", y).attr("x2", width).attr("y2", y);
47752
49244
  }
47753
49245
  }
49246
+ if (layout.coastlineStyle) {
49247
+ const cs = layout.coastlineStyle;
49248
+ const maskId = "dgmo-map-water-mask";
49249
+ const mask = defs.append("mask").attr("id", maskId).attr("maskUnits", "userSpaceOnUse").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height);
49250
+ mask.append("rect").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height).attr("fill", "white");
49251
+ const landD = layout.regions.filter((r) => r.id !== "lake").map((r) => r.d).join(" ");
49252
+ const lakeD = layout.regions.filter((r) => r.id === "lake").map((r) => r.d).join(" ");
49253
+ if (landD) mask.append("path").attr("d", landD).attr("fill", "black");
49254
+ if (lakeD) mask.append("path").attr("d", lakeD).attr("fill", "white");
49255
+ if (layout.insets.length) {
49256
+ const reach = Math.max(0, ...cs.lines.map((l) => l.d + l.thickness));
49257
+ for (const box of layout.insets) {
49258
+ const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
49259
+ mask.append("path").attr("d", d).attr("fill", "black").attr("stroke", "black").attr("stroke-width", 2 * reach).attr("stroke-linejoin", "round");
49260
+ }
49261
+ }
49262
+ const gWater = svg.append("g").attr("class", "dgmo-map-water-lines").attr("fill", "none").style("pointer-events", "none").attr("mask", `url(#${maskId})`);
49263
+ appendWaterLines(
49264
+ gWater,
49265
+ // Pass the canvas frame so edges collinear with it (the antimeridian on a
49266
+ // world map, regional clipExtent cuts) don't get ringed as fake coast —
49267
+ // land runs cleanly to the render-area edge.
49268
+ coastlineOuterRings(layout.regions, cs.minExtent, {
49269
+ w: width,
49270
+ h: height
49271
+ }),
49272
+ cs,
49273
+ layout.background
49274
+ );
49275
+ const byStroke = /* @__PURE__ */ new Map();
49276
+ for (const r of layout.regions) {
49277
+ const arr = byStroke.get(r.stroke);
49278
+ if (arr) arr.push(r.d);
49279
+ else byStroke.set(r.stroke, [r.d]);
49280
+ }
49281
+ for (const [stroke2, ds] of byStroke)
49282
+ gWater.append("path").attr("d", ds.join(" ")).attr("stroke", stroke2).attr("stroke-width", 0.5).attr("stroke-linejoin", "round");
49283
+ }
47754
49284
  if (layout.rivers.length) {
47755
- const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none");
49285
+ const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none").style("pointer-events", "none");
47756
49286
  for (const r of layout.rivers) {
47757
49287
  gRivers.append("path").attr("d", r.d).attr("stroke", r.color).attr("stroke-width", r.width).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
47758
49288
  }
47759
49289
  }
47760
49290
  if (layout.insets.length) {
47761
49291
  const insetG = svg.append("g").attr("class", "dgmo-map-insets");
47762
- for (const box of layout.insets) {
49292
+ layout.insets.forEach((box, bi) => {
47763
49293
  const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
47764
49294
  insetG.append("path").attr("d", d).attr("fill", layout.background).attr("stroke", mix(palette.text, palette.bg, 55)).attr("stroke-width", 1).attr("stroke-linejoin", "round");
47765
- }
49295
+ if (box.contextLand) {
49296
+ const clipId = `dgmo-map-inset-clip-${bi}`;
49297
+ defs.append("clipPath").attr("id", clipId).append("path").attr("d", d);
49298
+ insetG.append("path").attr("d", box.contextLand.d).attr("fill", box.contextLand.fill).attr("clip-path", `url(#${clipId})`);
49299
+ }
49300
+ });
47766
49301
  for (const r of layout.insetRegions) drawRegion(insetG, r, 0.5);
47767
- }
49302
+ if (layout.coastlineStyle) {
49303
+ const cs = layout.coastlineStyle;
49304
+ const maskId = "dgmo-map-inset-water-mask";
49305
+ const mask = defs.append("mask").attr("id", maskId).attr("maskUnits", "userSpaceOnUse").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height);
49306
+ for (const box of layout.insets) {
49307
+ const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
49308
+ mask.append("path").attr("d", d).attr("fill", "white");
49309
+ }
49310
+ layout.insets.forEach((box, bi) => {
49311
+ if (box.contextLand)
49312
+ mask.append("path").attr("d", box.contextLand.d).attr("fill", "black").attr("clip-path", `url(#dgmo-map-inset-clip-${bi})`);
49313
+ });
49314
+ for (const r of layout.insetRegions)
49315
+ if (r.id !== "lake")
49316
+ mask.append("path").attr("d", r.d).attr("fill", "black");
49317
+ for (const r of layout.insetRegions)
49318
+ if (r.id === "lake")
49319
+ mask.append("path").attr("d", r.d).attr("fill", "white");
49320
+ const clipId = "dgmo-map-inset-water-clip";
49321
+ const clip = defs.append("clipPath").attr("id", clipId);
49322
+ for (const box of layout.insets) {
49323
+ const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
49324
+ clip.append("path").attr("d", d);
49325
+ }
49326
+ const gInsetWater = insetG.append("g").attr("clip-path", `url(#${clipId})`).append("g").attr("class", "dgmo-map-inset-water-lines").attr("fill", "none").style("pointer-events", "none").attr("mask", `url(#${maskId})`);
49327
+ appendWaterLines(
49328
+ gInsetWater,
49329
+ coastlineOuterRings(layout.insetRegions, cs.minExtent),
49330
+ cs,
49331
+ layout.background
49332
+ );
49333
+ for (const r of layout.insetRegions)
49334
+ gInsetWater.append("path").attr("d", r.d).attr("stroke", r.stroke).attr("stroke-width", 0.5).attr("stroke-linejoin", "round");
49335
+ }
49336
+ }
49337
+ const wireSync = (sel, lineNumber) => {
49338
+ if (lineNumber < 1) return;
49339
+ sel.attr("data-line-number", lineNumber);
49340
+ if (onClickItem)
49341
+ sel.style("cursor", "pointer").on("click", () => onClickItem(lineNumber));
49342
+ };
47768
49343
  const gLegs = svg.append("g").attr("class", "dgmo-map-legs").attr("fill", "none");
47769
49344
  layout.legs.forEach((leg, i) => {
47770
49345
  const p = gLegs.append("path").attr("d", leg.d).attr("stroke", leg.color).attr("stroke-width", leg.width).attr("stroke-linecap", "round");
49346
+ wireSync(p, leg.lineNumber);
47771
49347
  if (leg.arrow) {
47772
49348
  const id = `dgmo-map-arrow-${i}`;
47773
49349
  const s = arrowSize(leg.width);
@@ -47775,25 +49351,38 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47775
49351
  p.attr("marker-end", `url(#${id})`);
47776
49352
  }
47777
49353
  if (leg.label !== void 0 && leg.labelX !== void 0) {
47778
- emitText(
49354
+ const lt = emitText(
47779
49355
  gLegs,
47780
49356
  leg.labelX,
47781
49357
  leg.labelY ?? 0,
47782
49358
  leg.label,
47783
49359
  "middle",
47784
- palette.textMuted,
47785
- haloColor,
47786
- true,
49360
+ leg.labelColor ?? palette.textMuted,
49361
+ leg.labelHaloColor ?? haloColor,
49362
+ leg.labelHalo ?? true,
47787
49363
  LABEL_FONT - 1
47788
49364
  );
49365
+ wireSync(lt, leg.lineNumber);
47789
49366
  }
47790
49367
  });
49368
+ const gSpider = svg.append("g").attr("class", "dgmo-map-spider");
49369
+ for (const cl of layout.clusters) {
49370
+ if (!exportDims) {
49371
+ gSpider.append("circle").attr("cx", cl.cx).attr("cy", cl.cy).attr("r", cl.hitR).attr("fill", "transparent").attr("data-cluster-hit", cl.id).style("cursor", "pointer");
49372
+ }
49373
+ for (const leg of cl.legs) {
49374
+ gSpider.append("line").attr("x1", cl.cx).attr("y1", cl.cy).attr("x2", leg.x2).attr("y2", leg.y2).attr("stroke", leg.color).attr("stroke-width", 1).attr("data-cluster-deco", cl.id).style("pointer-events", "none");
49375
+ }
49376
+ gSpider.append("circle").attr("cx", cl.cx).attr("cy", cl.cy).attr("r", 2).attr("fill", mix(palette.textMuted, palette.bg, 40)).attr("data-cluster-deco", cl.id).style("pointer-events", "none");
49377
+ }
47791
49378
  const gPois = svg.append("g").attr("class", "dgmo-map-pois");
47792
49379
  for (const poi of layout.pois) {
47793
49380
  if (poi.isOrigin) {
47794
49381
  gPois.append("circle").attr("cx", poi.cx).attr("cy", poi.cy).attr("r", poi.r + 3).attr("fill", "none").attr("stroke", poi.stroke).attr("stroke-width", 1.5);
47795
49382
  }
47796
49383
  const c = gPois.append("circle").attr("cx", poi.cx).attr("cy", poi.cy).attr("r", poi.r).attr("fill", poi.fill).attr("stroke", poi.stroke).attr("stroke-width", 1).attr("data-line-number", poi.lineNumber).attr("data-poi", poi.id);
49384
+ if (poi.clusterId !== void 0)
49385
+ c.attr("data-cluster-member", poi.clusterId);
47797
49386
  if (poi.tags) {
47798
49387
  for (const [group, value] of Object.entries(poi.tags)) {
47799
49388
  c.attr(`data-tag-${group.toLowerCase()}`, value.toLowerCase());
@@ -47821,12 +49410,32 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47821
49410
  }
47822
49411
  const gLabels = svg.append("g").attr("class", "dgmo-map-labels");
47823
49412
  for (const lab of layout.labels) {
49413
+ if (lab.hidden) {
49414
+ if (exportDims) continue;
49415
+ emitText(
49416
+ gLabels,
49417
+ lab.x,
49418
+ lab.y,
49419
+ lab.text,
49420
+ lab.anchor,
49421
+ lab.color,
49422
+ lab.haloColor,
49423
+ lab.halo,
49424
+ LABEL_FONT,
49425
+ lab.italic,
49426
+ lab.letterSpacing
49427
+ ).attr("data-poi", lab.poiId ?? null).attr("data-poi-hidden", "").style("opacity", 0).style("pointer-events", "none");
49428
+ continue;
49429
+ }
47824
49430
  if (lab.leader) {
47825
49431
  const line12 = gLabels.append("line").attr("x1", lab.leader.x1).attr("y1", lab.leader.y1).attr("x2", lab.leader.x2).attr("y2", lab.leader.y2).attr(
47826
49432
  "stroke",
47827
49433
  lab.leaderColor ?? mix(palette.textMuted, palette.bg, 60)
47828
49434
  ).attr("stroke-width", lab.leaderColor ? 1 : 0.75);
47829
49435
  if (lab.poiId !== void 0) line12.attr("data-poi", lab.poiId);
49436
+ if (lab.clusterMember !== void 0)
49437
+ line12.attr("data-cluster-member", lab.clusterMember);
49438
+ wireSync(line12, lab.lineNumber);
47830
49439
  }
47831
49440
  const t = emitText(
47832
49441
  gLabels,
@@ -47837,11 +49446,38 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47837
49446
  lab.color,
47838
49447
  lab.haloColor,
47839
49448
  lab.halo,
47840
- LABEL_FONT
49449
+ LABEL_FONT,
49450
+ lab.italic,
49451
+ lab.letterSpacing,
49452
+ lab.lines
47841
49453
  );
47842
49454
  if (lab.poiId !== void 0) {
47843
49455
  t.attr("data-poi", lab.poiId).style("cursor", "default");
47844
49456
  }
49457
+ if (lab.clusterMember !== void 0) {
49458
+ t.attr("data-cluster-member", lab.clusterMember);
49459
+ }
49460
+ wireSync(t, lab.lineNumber);
49461
+ }
49462
+ if (!exportDims && layout.clusters.length) {
49463
+ const gBadge = svg.append("g").attr("class", "dgmo-map-cluster-badges");
49464
+ for (const cl of layout.clusters) {
49465
+ const g = gBadge.append("g").attr("data-cluster", cl.id).style("opacity", 0).style("pointer-events", "none");
49466
+ const R = 9;
49467
+ g.append("circle").attr("cx", cl.cx).attr("cy", cl.cy).attr("r", R).attr("fill", mix(palette.textMuted, palette.bg, 35)).attr("stroke", palette.textMuted).attr("stroke-width", 1);
49468
+ g.append("circle").attr("cx", cl.cx).attr("cy", cl.cy).attr("r", R + 2.5).attr("fill", "none").attr("stroke", palette.textMuted).attr("stroke-width", 1);
49469
+ emitText(
49470
+ g,
49471
+ cl.cx,
49472
+ cl.cy + 3,
49473
+ String(cl.count),
49474
+ "middle",
49475
+ palette.text,
49476
+ palette.bg,
49477
+ false,
49478
+ LABEL_FONT
49479
+ );
49480
+ }
47845
49481
  }
47846
49482
  if (layout.legend) {
47847
49483
  const legendY = (layout.title ? TITLE_Y + TITLE_FONT_SIZE : 0) + (layout.subtitle ? TITLE_FONT_SIZE : 0) + 8;
@@ -47878,7 +49514,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47878
49514
  svg.append("text").attr("class", "dgmo-map-title").attr("x", width / 2).attr("y", TITLE_Y).attr("text-anchor", "middle").attr("font-size", TITLE_FONT_SIZE).attr("font-weight", TITLE_FONT_WEIGHT).attr("fill", palette.text).attr("paint-order", "stroke fill").attr("stroke", palette.bg).attr("stroke-width", 4).attr("stroke-linejoin", "round").attr("stroke-opacity", 0.7).text(layout.title);
47879
49515
  }
47880
49516
  if (layout.subtitle) {
47881
- svg.append("text").attr("x", width / 2).attr("y", TITLE_Y + TITLE_FONT_SIZE).attr("text-anchor", "middle").attr("font-size", LABEL_FONT + 1).attr("fill", palette.textMuted).attr("paint-order", "stroke fill").attr("stroke", palette.bg).attr("stroke-width", 3).attr("stroke-linejoin", "round").attr("stroke-opacity", 0.7).text(layout.subtitle);
49517
+ svg.append("text").attr("class", "dgmo-map-subtitle").attr("x", width / 2).attr("y", TITLE_Y + TITLE_FONT_SIZE).attr("text-anchor", "middle").attr("font-size", LABEL_FONT + 1).attr("fill", palette.textMuted).attr("paint-order", "stroke fill").attr("stroke", palette.bg).attr("stroke-width", 3).attr("stroke-linejoin", "round").attr("stroke-opacity", 0.7).text(layout.subtitle);
47882
49518
  }
47883
49519
  if (layout.caption) {
47884
49520
  svg.append("text").attr("x", width / 2).attr("y", height - 8).attr("text-anchor", "middle").attr("font-size", LABEL_FONT).attr("fill", palette.textMuted).attr("paint-order", "stroke fill").attr("stroke", palette.bg).attr("stroke-width", 3).attr("stroke-linejoin", "round").attr("stroke-opacity", 0.7).text(layout.caption);
@@ -47887,10 +49523,21 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47887
49523
  function renderMapForExport(container, resolved, data, palette, isDark, exportDims) {
47888
49524
  renderMap(container, resolved, data, palette, isDark, void 0, exportDims);
47889
49525
  }
47890
- function emitText(g, x, y, text, anchor, color, halo, withHalo, fontSize) {
47891
- const t = g.append("text").attr("x", x).attr("y", y).attr("text-anchor", anchor).attr("font-size", fontSize).attr("fill", color).text(text);
49526
+ function emitText(g, x, y, text, anchor, color, halo, withHalo, fontSize, italic, letterSpacing, lines) {
49527
+ const t = g.append("text").attr("x", x).attr("y", y).attr("text-anchor", anchor).attr("font-size", fontSize).attr("fill", color);
49528
+ if (lines && lines.length > 1) {
49529
+ const lineHeight = fontSize + 2;
49530
+ const startDy = -((lines.length - 1) / 2) * lineHeight;
49531
+ lines.forEach((ln, i) => {
49532
+ t.append("tspan").attr("x", x).attr("dy", i === 0 ? startDy : lineHeight).text(ln);
49533
+ });
49534
+ } else {
49535
+ t.text(text);
49536
+ }
49537
+ if (italic) t.attr("font-style", "italic");
49538
+ if (letterSpacing) t.attr("letter-spacing", letterSpacing);
47892
49539
  if (withHalo) {
47893
- t.attr("paint-order", "stroke fill").attr("stroke", halo).attr("stroke-width", 3).attr("stroke-linejoin", "round").attr("stroke-opacity", 0.7);
49540
+ t.attr("paint-order", "stroke fill").attr("stroke", halo).attr("stroke-width", 2).attr("stroke-linejoin", "round").attr("stroke-linecap", "round").attr("stroke-opacity", 0.55);
47894
49541
  }
47895
49542
  return t;
47896
49543
  }
@@ -47907,6 +49554,56 @@ var init_renderer16 = __esm({
47907
49554
  }
47908
49555
  });
47909
49556
 
49557
+ // src/map/dimensions.ts
49558
+ var dimensions_exports = {};
49559
+ __export(dimensions_exports, {
49560
+ mapContentAspect: () => mapContentAspect,
49561
+ mapExportDimensions: () => mapExportDimensions
49562
+ });
49563
+ import { geoPath as geoPath2 } from "d3-geo";
49564
+ function mapContentAspect(resolved, data, ref = REF) {
49565
+ const { projection, fitTarget } = buildMapProjection(resolved, data);
49566
+ projection.fitSize([ref, ref], fitTarget);
49567
+ const b = geoPath2(projection).bounds(fitTarget);
49568
+ const w = b[1][0] - b[0][0];
49569
+ const h = b[1][1] - b[0][1];
49570
+ const aspect = w / h;
49571
+ return Number.isFinite(aspect) && aspect > 0 ? aspect : FALLBACK_ASPECT;
49572
+ }
49573
+ function mapExportDimensions(resolved, data, baseWidth = 1200) {
49574
+ const raw = mapContentAspect(resolved, data);
49575
+ const clamped = Math.max(ASPECT_MIN, Math.min(ASPECT_MAX, raw));
49576
+ const width = baseWidth;
49577
+ let height = Math.round(width / clamped);
49578
+ let chromeReserve = 0;
49579
+ if (resolved.title && resolved.pois.length > 0) {
49580
+ const bannerBottom = (resolved.subtitle ? TITLE_Y + TITLE_FONT_SIZE : TITLE_Y) + TITLE_FONT_SIZE / 2;
49581
+ chromeReserve += Math.max(FIT_PAD2, bannerBottom + TITLE_GAP) - FIT_PAD2;
49582
+ }
49583
+ let floored = false;
49584
+ if (height - chromeReserve < MIN_MAP_BAND) {
49585
+ height = Math.round(chromeReserve + MIN_MAP_BAND);
49586
+ floored = true;
49587
+ }
49588
+ const preferContain = clamped !== raw || floored;
49589
+ return { width, height, preferContain };
49590
+ }
49591
+ var FIT_PAD2, TITLE_GAP, ASPECT_MAX, ASPECT_MIN, MIN_MAP_BAND, FALLBACK_ASPECT, REF;
49592
+ var init_dimensions = __esm({
49593
+ "src/map/dimensions.ts"() {
49594
+ "use strict";
49595
+ init_title_constants();
49596
+ init_layout15();
49597
+ FIT_PAD2 = 24;
49598
+ TITLE_GAP = 16;
49599
+ ASPECT_MAX = 3;
49600
+ ASPECT_MIN = 0.9;
49601
+ MIN_MAP_BAND = 200;
49602
+ FALLBACK_ASPECT = 1.5;
49603
+ REF = 1e3;
49604
+ }
49605
+ });
49606
+
47910
49607
  // src/map/load-data.ts
47911
49608
  var load_data_exports = {};
47912
49609
  __export(load_data_exports, {
@@ -47965,12 +49662,17 @@ function loadMapData() {
47965
49662
  mountainRanges,
47966
49663
  naLand,
47967
49664
  naLakes,
49665
+ waterBodies,
47968
49666
  gazetteer
47969
49667
  ] = await Promise.all([
49668
+ // worldCoarse (110m) is LOAD-BEARING but NOT a render source: the world
49669
+ // basemap renders from worldDetail (50m) at all scales (resolver pins
49670
+ // basemaps.world = 'detail'). Coarse stays as the authoritative region
49671
+ // name index + dominant-landmass bbox source in resolver.ts. Do not drop it.
47970
49672
  readJson(nb, dir, FILES.worldCoarse),
47971
49673
  readJson(nb, dir, FILES.worldDetail),
47972
49674
  readJson(nb, dir, FILES.usStates),
47973
- // Lakes/rivers/mountain/NA assets are optional — older bundles may predate them.
49675
+ // Lakes/rivers/mountain/NA/water assets are optional — older bundles may predate them.
47974
49676
  readJson(nb, dir, FILES.lakes).catch(() => void 0),
47975
49677
  readJson(nb, dir, FILES.rivers).catch(() => void 0),
47976
49678
  readJson(nb, dir, FILES.mountainRanges).catch(
@@ -47978,6 +49680,7 @@ function loadMapData() {
47978
49680
  ),
47979
49681
  readJson(nb, dir, FILES.naLand).catch(() => void 0),
47980
49682
  readJson(nb, dir, FILES.naLakes).catch(() => void 0),
49683
+ readJson(nb, dir, FILES.waterBodies).catch(() => void 0),
47981
49684
  readJson(nb, dir, FILES.gazetteer)
47982
49685
  ]);
47983
49686
  return validate({
@@ -47989,7 +49692,8 @@ function loadMapData() {
47989
49692
  ...rivers && { rivers },
47990
49693
  ...mountainRanges && { mountainRanges },
47991
49694
  ...naLand && { naLand },
47992
- ...naLakes && { naLakes }
49695
+ ...naLakes && { naLakes },
49696
+ ...waterBodies && { waterBodies }
47993
49697
  });
47994
49698
  })().catch((e) => {
47995
49699
  cache = void 0;
@@ -48010,6 +49714,7 @@ var init_load_data = __esm({
48010
49714
  mountainRanges: "mountain-ranges.json",
48011
49715
  naLand: "na-land.json",
48012
49716
  naLakes: "na-lakes.json",
49717
+ waterBodies: "water-bodies.json",
48013
49718
  gazetteer: "gazetteer.json"
48014
49719
  };
48015
49720
  CANDIDATE_DIRS = [
@@ -50023,8 +51728,8 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
50023
51728
  const lines = splitParticipantLabel(p.label, LABEL_MAX_CHARS);
50024
51729
  if (lines.length === 0) continue;
50025
51730
  const widest = Math.max(...lines.map((l) => l.length));
50026
- const labelWidth = widest * LABEL_CHAR_WIDTH + 10;
50027
- uniformBoxWidth = Math.max(uniformBoxWidth, labelWidth);
51731
+ const labelWidth2 = widest * LABEL_CHAR_WIDTH + 10;
51732
+ uniformBoxWidth = Math.max(uniformBoxWidth, labelWidth2);
50028
51733
  }
50029
51734
  uniformBoxWidth = Math.min(MAX_BOX_WIDTH, uniformBoxWidth);
50030
51735
  const effectiveGap = Math.max(PARTICIPANT_GAP, uniformBoxWidth + 30);
@@ -52723,15 +54428,15 @@ function renderArcDiagram(container, parsed, palette, _isDark, onClickItem, expo
52723
54428
  textColor,
52724
54429
  onClickItem
52725
54430
  );
52726
- const neighbors = /* @__PURE__ */ new Map();
52727
- for (const node of nodes) neighbors.set(node, /* @__PURE__ */ new Set());
54431
+ const neighbors2 = /* @__PURE__ */ new Map();
54432
+ for (const node of nodes) neighbors2.set(node, /* @__PURE__ */ new Set());
52728
54433
  for (const link of links) {
52729
- neighbors.get(link.source).add(link.target);
52730
- neighbors.get(link.target).add(link.source);
54434
+ neighbors2.get(link.source).add(link.target);
54435
+ neighbors2.get(link.target).add(link.source);
52731
54436
  }
52732
54437
  const FADE_OPACITY3 = 0.1;
52733
54438
  function handleMouseEnter(hovered) {
52734
- const connected = neighbors.get(hovered);
54439
+ const connected = neighbors2.get(hovered);
52735
54440
  g.selectAll(".arc-link").each(function() {
52736
54441
  const el = d3Selection23.select(this);
52737
54442
  const src = el.attr("data-source");
@@ -53639,10 +55344,12 @@ function renderTimelineHorizontalTimeSort(container, parsed, palette, isDark, se
53639
55344
  const markerLabelY = markerReserve ? -(topScaleH + MARKER_ROW_H / 2) : 0;
53640
55345
  const eraLabelY = eraReserve ? -(topScaleH + markerReserve + ERA_ROW_H / 2) : 0;
53641
55346
  const innerWidth = width - margin.left - margin.right;
53642
- const innerHeight = height - margin.top - margin.bottom;
53643
- const rowH = Math.min(ctx.structural(28), innerHeight / sorted.length);
55347
+ const availInnerHeight = height - margin.top - margin.bottom;
55348
+ const rowH = Math.min(ctx.structural(28), availInnerHeight / sorted.length);
55349
+ const innerHeight = rowH * sorted.length;
55350
+ const usedHeight = margin.top + innerHeight + margin.bottom;
53644
55351
  const xScale = d3Scale2.scaleLinear().domain([minDate - datePadding, maxDate + datePadding]).range([0, innerWidth]);
53645
- const svg = d3Selection23.select(container).append("svg").attr("width", width).attr("height", height).attr("viewBox", `0 0 ${width} ${height}`).attr("preserveAspectRatio", "xMidYMin meet").style("background", bgColor);
55352
+ const svg = d3Selection23.select(container).append("svg").attr("width", width).attr("height", usedHeight).attr("viewBox", `0 0 ${width} ${usedHeight}`).attr("preserveAspectRatio", "xMidYMin meet").style("background", bgColor);
53646
55353
  if (ctx.isBelowFloor) {
53647
55354
  svg.attr("width", "100%");
53648
55355
  }
@@ -54722,7 +56429,7 @@ function renderVenn(container, parsed, palette, _isDark, onClickItem, exportDims
54722
56429
  8,
54723
56430
  Math.floor(OVERLAP_WRAP_TARGET_W / OVERLAP_CH_W)
54724
56431
  );
54725
- function wrapLabel2(text, maxChars) {
56432
+ function wrapLabel3(text, maxChars) {
54726
56433
  const words = text.split(/\s+/).filter(Boolean);
54727
56434
  const lines = [];
54728
56435
  let cur = "";
@@ -54768,7 +56475,7 @@ function renderVenn(container, parsed, palette, _isDark, onClickItem, exportDims
54768
56475
  if (!ov.label) continue;
54769
56476
  const idxs = ov.sets.map((s) => vennSets.findIndex((vs) => vs.name === s));
54770
56477
  if (idxs.some((idx) => idx < 0)) continue;
54771
- const lines = wrapLabel2(ov.label, MAX_WRAP_CHARS);
56478
+ const lines = wrapLabel3(ov.label, MAX_WRAP_CHARS);
54772
56479
  wrappedOverlapLabels.set(ov, lines);
54773
56480
  const dir = predictOverlapDirRaw(idxs);
54774
56481
  const longest = lines.reduce((m, l) => Math.max(m, l.length), 0);
@@ -56206,6 +57913,7 @@ async function renderForExport(content, theme, palette, viewState, options) {
56206
57913
  const { parseMap: parseMap2 } = await Promise.resolve().then(() => (init_parser12(), parser_exports11));
56207
57914
  const { resolveMap: resolveMap2 } = await Promise.resolve().then(() => (init_resolver2(), resolver_exports));
56208
57915
  const { renderMapForExport: renderMapForExport2 } = await Promise.resolve().then(() => (init_renderer16(), renderer_exports16));
57916
+ const { mapExportDimensions: mapExportDimensions2 } = await Promise.resolve().then(() => (init_dimensions(), dimensions_exports));
56209
57917
  const effectivePalette2 = await resolveExportPalette(theme, palette);
56210
57918
  const mapParsed = parseMap2(content);
56211
57919
  let mapData = options?.mapData;
@@ -56218,14 +57926,15 @@ async function renderForExport(content, theme, palette, viewState, options) {
56218
57926
  }
56219
57927
  }
56220
57928
  const mapResolved = resolveMap2(mapParsed, mapData);
56221
- const container2 = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
57929
+ const dims2 = mapExportDimensions2(mapResolved, mapData, EXPORT_WIDTH);
57930
+ const container2 = createExportContainer(dims2.width, dims2.height);
56222
57931
  renderMapForExport2(
56223
57932
  container2,
56224
57933
  mapResolved,
56225
57934
  mapData,
56226
57935
  effectivePalette2,
56227
57936
  theme === "dark",
56228
- { width: EXPORT_WIDTH, height: EXPORT_HEIGHT }
57937
+ dims2
56229
57938
  );
56230
57939
  return finalizeSvgExport(container2, theme, effectivePalette2);
56231
57940
  }
@@ -57072,7 +58781,8 @@ async function render(content, options) {
57072
58781
  ...options?.c4Container !== void 0 && {
57073
58782
  c4Container: options.c4Container
57074
58783
  },
57075
- ...options?.tagGroup !== void 0 && { tagGroup: options.tagGroup }
58784
+ ...options?.tagGroup !== void 0 && { tagGroup: options.tagGroup },
58785
+ ...options?.mapData !== void 0 && { mapData: options.mapData }
57076
58786
  });
57077
58787
  if (chartType === "map") {
57078
58788
  try {
@@ -57083,7 +58793,7 @@ async function render(content, options) {
57083
58793
  Promise.resolve().then(() => (init_load_data(), load_data_exports))
57084
58794
  ]
57085
58795
  );
57086
- const data = await loadMapData2();
58796
+ const data = options?.mapData ?? await loadMapData2();
57087
58797
  diagnostics = [...resolveMap2(parseMap2(content), data).diagnostics];
57088
58798
  } catch {
57089
58799
  }
@@ -57322,8 +59032,8 @@ function detectCycles(parsed) {
57322
59032
  const parent = /* @__PURE__ */ new Map();
57323
59033
  function dfs(nodeId3) {
57324
59034
  color.set(nodeId3, 1);
57325
- const neighbors = adj.get(nodeId3) ?? [];
57326
- for (const next of neighbors) {
59035
+ const neighbors2 = adj.get(nodeId3) ?? [];
59036
+ for (const next of neighbors2) {
57327
59037
  const c = color.get(next) ?? 0;
57328
59038
  if (c === 1) {
57329
59039
  const lineKey = `${nodeId3}->${next}`;
@@ -57508,6 +59218,7 @@ init_resolver2();
57508
59218
  init_load_data();
57509
59219
  init_layout15();
57510
59220
  init_renderer16();
59221
+ init_dimensions();
57511
59222
 
57512
59223
  // src/map/geo-query.ts
57513
59224
  init_parser12();
@@ -57581,7 +59292,9 @@ function nearestCity(lonLat, gazetteer) {
57581
59292
  name: c[4],
57582
59293
  iso: c[2],
57583
59294
  ...c[5] !== void 0 && { sub: c[5] },
57584
- distanceKm: best.dist
59295
+ distanceKm: best.dist,
59296
+ lat: c[0],
59297
+ lon: c[1]
57585
59298
  };
57586
59299
  }
57587
59300
  function roundCoord(n) {
@@ -57660,7 +59373,7 @@ function createMapGeoQuery(opts) {
57660
59373
  }
57661
59374
  return out;
57662
59375
  };
57663
- return { invert, project, locate, cities };
59376
+ return { invert, project, locate, cities, diagnostics: layout.diagnostics };
57664
59377
  }
57665
59378
 
57666
59379
  // src/map/completion.ts
@@ -58386,9 +60099,12 @@ var GLOBAL_DIRECTIVES = {
58386
60099
  "gruvbox",
58387
60100
  "tokyo-night",
58388
60101
  "one-dark",
58389
- "bold",
58390
60102
  "dracula",
58391
- "monokai"
60103
+ "monokai",
60104
+ "atlas",
60105
+ "blueprint",
60106
+ "slate",
60107
+ "tidewater"
58392
60108
  ]
58393
60109
  },
58394
60110
  theme: {
@@ -58719,7 +60435,9 @@ var COMPLETION_REGISTRY = /* @__PURE__ */ new Map([
58719
60435
  withGlobals({
58720
60436
  direction: { description: "Layout direction", values: ["LR", "TB"] },
58721
60437
  "active-tag": { description: "Active tag group name" },
58722
- hide: { description: "Hide tag:value pairs" }
60438
+ hide: { description: "Hide tag:value pairs" },
60439
+ "box-metric": { description: "Metric label for the value ramp" },
60440
+ "show-values": { description: "Print box values as text" }
58723
60441
  })
58724
60442
  ],
58725
60443
  [
@@ -58784,18 +60502,12 @@ var COMPLETION_REGISTRY = /* @__PURE__ */ new Map([
58784
60502
  ],
58785
60503
  [
58786
60504
  "map",
58787
- // Geographic map directives (§24B.2/.7). `poi`/`route` are content
58788
- // keywords, not directives; metadata keys (value/label/style) live in the
58789
- // reserved-key registry.
60505
+ // Geographic map directives (§24B.2/.7). Cosmetics are ON by default — the
60506
+ // only switches are bare `no-*` opt-outs, surfaced proactively so a
60507
+ // zero-config map still hints at what can be turned off. `poi`/`route` are
60508
+ // content keywords, not directives; metadata keys (value/label/style) live
60509
+ // in the reserved-key registry.
58790
60510
  withGlobals({
58791
- region: {
58792
- description: "Basemap: us-states (force US state mesh + scoping) | world (inert \u2014 already the default)",
58793
- values: ["us-states", "world"]
58794
- },
58795
- projection: {
58796
- description: "Override the auto projection",
58797
- values: ["equirectangular", "natural-earth", "albers-usa", "mercator"]
58798
- },
58799
60511
  "region-metric": { description: "Label for the region value ramp" },
58800
60512
  "poi-metric": {
58801
60513
  description: "Label for the POI value (marker size) channel"
@@ -58803,21 +60515,30 @@ var COMPLETION_REGISTRY = /* @__PURE__ */ new Map([
58803
60515
  "flow-metric": {
58804
60516
  description: "Label for the edge/leg value (thickness) channel"
58805
60517
  },
58806
- scale: { description: "Override value ramp anchors: scale <min> <max>" },
58807
- "region-labels": {
58808
- description: "Subdivision name labels",
58809
- values: ["full", "abbrev", "off"]
60518
+ locale: {
60519
+ description: "Default country/state for bare place names, e.g. locale US-GA"
58810
60520
  },
58811
- "poi-labels": {
58812
- description: "POI labels/values",
58813
- values: ["off", "auto", "all"]
60521
+ "active-tag": {
60522
+ description: "Which tag group leads when several are present"
58814
60523
  },
58815
- "default-country": { description: "ISO scope for bare city resolution" },
58816
- "default-state": { description: "ISO subdivision scope" },
60524
+ caption: { description: "Caption line (data-source attribution)" },
58817
60525
  "no-legend": { description: "Suppress the legend" },
58818
- relief: { description: "Subtle mountain-range relief shading" },
58819
- subtitle: { description: "Subtitle line" },
58820
- caption: { description: "Caption line" }
60526
+ "no-coastline": {
60527
+ description: "Turn off coastal water-lines (on by default)"
60528
+ },
60529
+ "no-relief": {
60530
+ description: "Turn off mountain-range relief shading (on by default)"
60531
+ },
60532
+ "no-context-labels": {
60533
+ description: "Turn off orientation labels for water + nearby countries"
60534
+ },
60535
+ "no-region-labels": {
60536
+ description: "Turn off subdivision name labels (on by default)"
60537
+ },
60538
+ "no-poi-labels": { description: "Turn off POI labels (on by default)" },
60539
+ "no-colorize": {
60540
+ description: "Force plain green-land reference dress (regions are auto-coloured by default)"
60541
+ }
58821
60542
  })
58822
60543
  ]
58823
60544
  ]);
@@ -58944,13 +60665,10 @@ var PIPE_METADATA = /* @__PURE__ */ new Map([
58944
60665
  "boxes-and-lines",
58945
60666
  {
58946
60667
  node: {
58947
- description: { description: "Node description text" }
60668
+ description: { description: "Node description text" },
60669
+ value: { description: "Numeric value for the metric ramp" }
58948
60670
  },
58949
- edge: {
58950
- width: { description: "Edge stroke width in pixels" },
58951
- split: { description: "Traffic split percentage" },
58952
- fanout: { description: "Fanout multiplier (integer >= 1)" }
58953
- }
60671
+ edge: {}
58954
60672
  }
58955
60673
  ],
58956
60674
  [
@@ -60292,7 +62010,8 @@ export {
60292
62010
  applyCollapseProjection,
60293
62011
  applyGroupOrdering,
60294
62012
  applyPositionOverrides,
60295
- boldPalette,
62013
+ atlasPalette,
62014
+ blueprintPalette,
60296
62015
  buildExtendedChartOption,
60297
62016
  buildNoteMessageMap,
60298
62017
  buildRenderSequence,
@@ -60395,6 +62114,8 @@ export {
60395
62114
  looksLikeState,
60396
62115
  makeDgmoError,
60397
62116
  mapBackgroundColor,
62117
+ mapContentAspect,
62118
+ mapExportDimensions,
60398
62119
  mapNeutralLandColor,
60399
62120
  matchesContiguously,
60400
62121
  measurePertAnalysisBlock,
@@ -60530,9 +62251,11 @@ export {
60530
62251
  shapeFill,
60531
62252
  simulateCanonical,
60532
62253
  simulateFast,
62254
+ slatePalette,
60533
62255
  solarizedPalette,
60534
62256
  suggestChartTypes,
60535
62257
  themes,
62258
+ tidewaterPalette,
60536
62259
  tint,
60537
62260
  tokyoNightPalette,
60538
62261
  transformLine,