@diagrammo/dgmo 0.19.0 → 0.20.1

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 (48) hide show
  1. package/dist/advanced.cjs +948 -321
  2. package/dist/advanced.d.cts +148 -54
  3. package/dist/advanced.d.ts +148 -54
  4. package/dist/advanced.js +949 -321
  5. package/dist/auto.cjs +930 -317
  6. package/dist/auto.js +117 -117
  7. package/dist/auto.mjs +934 -318
  8. package/dist/cli.cjs +160 -160
  9. package/dist/index.cjs +929 -316
  10. package/dist/index.js +933 -317
  11. package/dist/internal.cjs +948 -321
  12. package/dist/internal.d.cts +148 -54
  13. package/dist/internal.d.ts +148 -54
  14. package/dist/internal.js +949 -321
  15. package/dist/map-data/PROVENANCE.json +1 -1
  16. package/dist/map-data/lakes.json +1 -0
  17. package/dist/map-data/na-lakes.json +1 -0
  18. package/dist/map-data/na-land.json +1 -0
  19. package/dist/map-data/rivers.json +1 -0
  20. package/docs/language-reference.md +12 -7
  21. package/gallery/fixtures/map-region-scope.dgmo +15 -0
  22. package/package.json +4 -4
  23. package/src/advanced.ts +7 -6
  24. package/src/c4/parser.ts +6 -6
  25. package/src/completion.ts +6 -2
  26. package/src/echarts.ts +1 -1
  27. package/src/infra/parser.ts +10 -10
  28. package/src/journey-map/parser.ts +1 -1
  29. package/src/label-layout.ts +36 -0
  30. package/src/map/data/PROVENANCE.json +1 -1
  31. package/src/map/data/README.md +2 -0
  32. package/src/map/data/lakes.json +1 -0
  33. package/src/map/data/na-lakes.json +1 -0
  34. package/src/map/data/na-land.json +1 -0
  35. package/src/map/data/rivers.json +1 -0
  36. package/src/map/layout.ts +1022 -205
  37. package/src/map/load-data.ts +73 -17
  38. package/src/map/parser.ts +22 -13
  39. package/src/map/renderer.ts +200 -219
  40. package/src/map/resolved-types.ts +18 -1
  41. package/src/map/resolver.ts +79 -7
  42. package/src/map/types.ts +4 -0
  43. package/src/mindmap/parser.ts +1 -1
  44. package/src/sitemap/parser.ts +1 -1
  45. package/src/utils/legend-d3.ts +42 -0
  46. package/src/utils/legend-layout.ts +83 -3
  47. package/src/utils/legend-svg.ts +1 -8
  48. package/src/utils/legend-types.ts +44 -1
package/dist/index.js CHANGED
@@ -51,6 +51,33 @@ function rectCircleOverlap(rect, circle) {
51
51
  const dy = nearestY - circle.cy;
52
52
  return dx * dx + dy * dy < circle.r * circle.r;
53
53
  }
54
+ function segmentRectOverlap(x0, y0, x1, y1, rect) {
55
+ const dx = x1 - x0;
56
+ const dy = y1 - y0;
57
+ let t0 = 0;
58
+ let t1 = 1;
59
+ const edges = [
60
+ [-dx, x0 - rect.x],
61
+ [dx, rect.x + rect.w - x0],
62
+ [-dy, y0 - rect.y],
63
+ [dy, rect.y + rect.h - y0]
64
+ ];
65
+ for (const [p, q] of edges) {
66
+ if (p === 0) {
67
+ if (q < 0) return false;
68
+ } else {
69
+ const t = q / p;
70
+ if (p < 0) {
71
+ if (t > t1) return false;
72
+ if (t > t0) t0 = t;
73
+ } else {
74
+ if (t < t0) return false;
75
+ if (t < t1) t1 = t;
76
+ }
77
+ }
78
+ }
79
+ return true;
80
+ }
54
81
  function computeQuadrantPointLabels(points, chartBounds, obstacles, pointRadius, fontSize) {
55
82
  const labelHeight = fontSize + 4;
56
83
  const stepSize = labelHeight + 2;
@@ -3254,6 +3281,57 @@ var init_legend_constants = __esm({
3254
3281
  });
3255
3282
 
3256
3283
  // src/utils/legend-layout.ts
3284
+ function fmtRamp(n) {
3285
+ return Number.isInteger(n) ? String(n) : String(Math.round(n * 10) / 10);
3286
+ }
3287
+ function gradientCapsuleWidth(name, gradient) {
3288
+ const pw = pillWidth(name);
3289
+ const minW = measureLegendText(fmtRamp(gradient.min), LEGEND_ENTRY_FONT_SIZE);
3290
+ const maxW = measureLegendText(fmtRamp(gradient.max), LEGEND_ENTRY_FONT_SIZE);
3291
+ return LEGEND_CAPSULE_PAD + pw + 4 + minW + RAMP_LABEL_GAP + RAMP_LEGEND_W + RAMP_LABEL_GAP + maxW + LEGEND_CAPSULE_PAD;
3292
+ }
3293
+ function buildGradientCapsuleLayout(group, gradient) {
3294
+ const pw = pillWidth(group.name);
3295
+ const minText = fmtRamp(gradient.min);
3296
+ const maxText = fmtRamp(gradient.max);
3297
+ const minW = measureLegendText(minText, LEGEND_ENTRY_FONT_SIZE);
3298
+ const gx = LEGEND_CAPSULE_PAD + pw + 4;
3299
+ const minX = gx;
3300
+ const rampX = gx + minW + RAMP_LABEL_GAP;
3301
+ const maxX = rampX + RAMP_LEGEND_W + RAMP_LABEL_GAP;
3302
+ const width = gradientCapsuleWidth(group.name, gradient);
3303
+ return {
3304
+ groupName: group.name,
3305
+ x: 0,
3306
+ y: 0,
3307
+ width,
3308
+ height: LEGEND_HEIGHT,
3309
+ pill: {
3310
+ groupName: group.name,
3311
+ x: LEGEND_CAPSULE_PAD,
3312
+ y: LEGEND_CAPSULE_PAD,
3313
+ width: pw,
3314
+ height: LEGEND_HEIGHT - LEGEND_CAPSULE_PAD * 2,
3315
+ isActive: true
3316
+ },
3317
+ entries: [],
3318
+ gradient: {
3319
+ rampX,
3320
+ rampY: (LEGEND_HEIGHT - RAMP_LEGEND_H) / 2,
3321
+ rampW: RAMP_LEGEND_W,
3322
+ rampH: RAMP_LEGEND_H,
3323
+ min: gradient.min,
3324
+ max: gradient.max,
3325
+ minText,
3326
+ minX,
3327
+ maxText,
3328
+ maxX,
3329
+ textY: LEGEND_HEIGHT / 2,
3330
+ hue: gradient.hue,
3331
+ base: gradient.base
3332
+ }
3333
+ };
3334
+ }
3257
3335
  function pillWidth(name) {
3258
3336
  return measureLegendText(name, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD;
3259
3337
  }
@@ -3396,7 +3474,7 @@ function computeLegendLayout(config, state, containerWidth) {
3396
3474
  };
3397
3475
  }
3398
3476
  const controlsGroupLayout = isExport ? void 0 : buildControlsGroupLayout(config, state);
3399
- const visibleGroups = config.showEmptyGroups ? groups : groups.filter((g) => g.entries.length > 0);
3477
+ const visibleGroups = config.showEmptyGroups ? groups : groups.filter((g) => g.entries.length > 0 || !!g.gradient);
3400
3478
  if (visibleGroups.length === 0 && (!configControls || configControls.length === 0) && !controlsGroupLayout) {
3401
3479
  return {
3402
3480
  height: 0,
@@ -3475,7 +3553,7 @@ function computeLegendLayout(config, state, containerWidth) {
3475
3553
  groupAvailW,
3476
3554
  config.capsulePillAddonWidth ?? 0
3477
3555
  );
3478
- } else if (!activeGroupName) {
3556
+ } else if (!activeGroupName || config.showInactivePills) {
3479
3557
  const pw = pillWidth(g.name);
3480
3558
  pills.push({
3481
3559
  groupName: g.name,
@@ -3513,6 +3591,7 @@ function computeLegendLayout(config, state, containerWidth) {
3513
3591
  };
3514
3592
  }
3515
3593
  function buildCapsuleLayout(group, containerWidth, addonWidth = 0) {
3594
+ if (group.gradient) return buildGradientCapsuleLayout(group, group.gradient);
3516
3595
  const pw = pillWidth(group.name);
3517
3596
  const info = capsuleWidth(
3518
3597
  group.name,
@@ -3683,7 +3762,7 @@ function getMaxLegendReservedHeight(config, containerWidth) {
3683
3762
  }
3684
3763
  return max;
3685
3764
  }
3686
- var CONTROL_PILL_PAD, CONTROL_FONT_SIZE, CONTROL_ICON_GAP, CONTROL_GAP;
3765
+ var CONTROL_PILL_PAD, CONTROL_FONT_SIZE, CONTROL_ICON_GAP, CONTROL_GAP, RAMP_LEGEND_W, RAMP_LEGEND_H, RAMP_LABEL_GAP;
3687
3766
  var init_legend_layout = __esm({
3688
3767
  "src/utils/legend-layout.ts"() {
3689
3768
  "use strict";
@@ -3692,6 +3771,9 @@ var init_legend_layout = __esm({
3692
3771
  CONTROL_FONT_SIZE = 11;
3693
3772
  CONTROL_ICON_GAP = 4;
3694
3773
  CONTROL_GAP = 8;
3774
+ RAMP_LEGEND_W = 80;
3775
+ RAMP_LEGEND_H = 8;
3776
+ RAMP_LABEL_GAP = 6;
3695
3777
  }
3696
3778
  });
3697
3779
 
@@ -3779,6 +3861,16 @@ function renderCapsule(parent, capsule, palette, groupBg, pillBorder, _isDark, c
3779
3861
  g.append("rect").attr("x", pill.x).attr("y", pill.y).attr("width", pill.width).attr("height", pill.height).attr("rx", pill.height / 2).attr("fill", palette.bg);
3780
3862
  g.append("rect").attr("x", pill.x).attr("y", pill.y).attr("width", pill.width).attr("height", pill.height).attr("rx", pill.height / 2).attr("fill", "none").attr("stroke", pillBorder).attr("stroke-width", 0.75);
3781
3863
  g.append("text").attr("x", pill.x + pill.width / 2).attr("y", LEGEND_HEIGHT / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", LEGEND_PILL_FONT_SIZE).attr("font-weight", 500).attr("fill", palette.text).attr("pointer-events", "none").attr("font-family", FONT_FAMILY).text(capsule.groupName);
3864
+ if (capsule.gradient) {
3865
+ const gr = capsule.gradient;
3866
+ const gradId = `dgmo-legend-ramp-${capsule.groupName.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
3867
+ const def = g.append("defs").append("linearGradient").attr("id", gradId);
3868
+ def.append("stop").attr("offset", "0%").attr("stop-color", mix(gr.hue, gr.base, 15));
3869
+ def.append("stop").attr("offset", "100%").attr("stop-color", gr.hue);
3870
+ g.append("text").attr("x", gr.minX).attr("y", gr.textY).attr("dominant-baseline", "central").attr("font-size", LEGEND_ENTRY_FONT_SIZE).attr("fill", palette.textMuted).attr("pointer-events", "none").attr("font-family", FONT_FAMILY).text(gr.minText);
3871
+ g.append("rect").attr("class", "dgmo-legend-gradient-ramp").attr("data-ramp-min", gr.min).attr("data-ramp-max", gr.max).attr("x", gr.rampX).attr("y", gr.rampY).attr("width", gr.rampW).attr("height", gr.rampH).attr("rx", 2).attr("fill", `url(#${gradId})`);
3872
+ g.append("text").attr("x", gr.maxX).attr("y", gr.textY).attr("dominant-baseline", "central").attr("font-size", LEGEND_ENTRY_FONT_SIZE).attr("fill", palette.textMuted).attr("pointer-events", "none").attr("font-family", FONT_FAMILY).text(gr.maxText);
3873
+ }
3782
3874
  for (const entry of capsule.entries) {
3783
3875
  const entryG = g.append("g").attr("data-legend-entry", entry.value.toLowerCase()).attr("data-series-name", entry.value).style("cursor", "pointer");
3784
3876
  entryG.append("circle").attr("cx", entry.dotCx).attr("cy", entry.dotCy).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
@@ -11270,23 +11362,22 @@ function parseC4(content, palette) {
11270
11362
  }
11271
11363
  }
11272
11364
  const parent = findParentElement(indent, stack);
11273
- if (parent) {
11274
- const descResult = tryStripDescriptionKeyword(trimmed);
11365
+ const descResult = tryStripDescriptionKeyword(trimmed);
11366
+ if (parent && descResult.isKeyword) {
11275
11367
  if (descResult.needsColon) {
11276
11368
  pushError(
11277
11369
  lineNumber,
11278
- `Use "description: ${descResult.text}" \u2014 colon is required.`,
11370
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`,
11279
11371
  "warning"
11280
11372
  );
11281
11373
  }
11282
- const descText = descResult.isKeyword ? descResult.text : trimmed;
11283
11374
  let desc = elementDescription.get(parent.element);
11284
11375
  if (!desc) {
11285
11376
  desc = [];
11286
11377
  elementDescription.set(parent.element, desc);
11287
11378
  parent.element.description = desc;
11288
11379
  }
11289
- desc.push(descText);
11380
+ desc.push(descResult.text);
11290
11381
  } else {
11291
11382
  pushError(lineNumber, `Unexpected content: "${trimmed}"`);
11292
11383
  }
@@ -11753,7 +11844,7 @@ function parseSitemap(content, palette) {
11753
11844
  if (descResult.needsColon) {
11754
11845
  pushWarning(
11755
11846
  lineNumber,
11756
- `Use "description: ${descResult.text}" \u2014 colon is required.`
11847
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`
11757
11848
  );
11758
11849
  }
11759
11850
  const parent = findParentNode(indent, indentStack);
@@ -12581,23 +12672,22 @@ function parseInfra(content) {
12581
12672
  }
12582
12673
  }
12583
12674
  const descResult = tryStripDescriptionKeyword(trimmed);
12584
- if (descResult.isKeyword && currentNode.isEdge) {
12585
- continue;
12586
- }
12587
- if (!currentNode.isEdge) {
12675
+ if (descResult.isKeyword) {
12676
+ if (currentNode.isEdge) {
12677
+ continue;
12678
+ }
12588
12679
  if (descResult.needsColon) {
12589
12680
  warn(
12590
12681
  lineNumber,
12591
- `Use "description: ${descResult.text}" \u2014 colon is required.`
12682
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`
12592
12683
  );
12593
12684
  }
12594
- const descText = descResult.isKeyword ? descResult.text : trimmed;
12595
- pushDescription(currentNode, descText);
12685
+ pushDescription(currentNode, descResult.text);
12596
12686
  continue;
12597
12687
  }
12598
12688
  warn(
12599
12689
  lineNumber,
12600
- `Unexpected line inside component '${currentNode.label}'. Expected a property (key: value), connection (-> Target), or description text.`
12690
+ `Unexpected line inside component '${currentNode.label}'. Expected a property (key: value), connection (-> Target), or a description (description: text).`
12601
12691
  );
12602
12692
  continue;
12603
12693
  }
@@ -15734,9 +15824,6 @@ function parseMap(content) {
15734
15824
  const pushWarning = (line12, message) => {
15735
15825
  diagnostics.push(makeDgmoError(line12, message, "warning"));
15736
15826
  };
15737
- const pushInfo = (line12, message) => {
15738
- diagnostics.push(makeDgmoError(line12, message, "warning"));
15739
- };
15740
15827
  const lines = content.split("\n");
15741
15828
  let firstIdx = 0;
15742
15829
  while (firstIdx < lines.length) {
@@ -15866,10 +15953,15 @@ function parseMap(content) {
15866
15953
  break;
15867
15954
  case "projection":
15868
15955
  dup(d.projection);
15869
- if (value && !["natural-earth", "albers-usa", "mercator"].includes(value))
15956
+ if (value && ![
15957
+ "equirectangular",
15958
+ "natural-earth",
15959
+ "albers-usa",
15960
+ "mercator"
15961
+ ].includes(value))
15870
15962
  pushWarning(
15871
15963
  line12,
15872
- `Unknown projection "${value}" (expected natural-earth | albers-usa | mercator).`
15964
+ `Unknown projection "${value}" (expected equirectangular | natural-earth | albers-usa | mercator).`
15873
15965
  );
15874
15966
  d.projection = value;
15875
15967
  break;
@@ -16008,17 +16100,21 @@ function parseMap(content) {
16008
16100
  scoreNum = void 0;
16009
16101
  }
16010
16102
  }
16011
- if (scoreNum !== void 0 && Object.keys(tags).length)
16012
- pushInfo(
16013
- line12,
16014
- "A region has both `score:` and a tag value \u2014 v1 renders only the score (bivariate is a future seam)."
16015
- );
16103
+ let regionName = split.name;
16104
+ let regionScope;
16105
+ const rToks = regionName.split(/\s+/);
16106
+ const rLast = rToks[rToks.length - 1];
16107
+ if (rToks.length > 1 && SCOPE_RE.test(rLast)) {
16108
+ regionName = rToks.slice(0, -1).join(" ");
16109
+ regionScope = rLast;
16110
+ }
16016
16111
  const region = {
16017
- name: split.name,
16112
+ name: regionName,
16018
16113
  tags,
16019
16114
  meta,
16020
16115
  lineNumber: line12
16021
16116
  };
16117
+ if (regionScope !== void 0) region.scope = regionScope;
16022
16118
  if (scoreNum !== void 0) region.score = scoreNum;
16023
16119
  regions.push(region);
16024
16120
  }
@@ -17142,7 +17238,7 @@ function parseMindmap(content, palette) {
17142
17238
  if (descResult.needsColon) {
17143
17239
  pushWarning(
17144
17240
  lineNumber,
17145
- `Use "description: ${descResult.text}" \u2014 colon is required.`
17241
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`
17146
17242
  );
17147
17243
  }
17148
17244
  const parent = findMetadataParent2(indent, indentStack);
@@ -18930,7 +19026,7 @@ function parseJourneyMap(content, palette) {
18930
19026
  if (descResult.needsColon) {
18931
19027
  warn(
18932
19028
  lineNumber,
18933
- `Use "description: ${descResult.text}" \u2014 colon is required.`
19029
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`
18934
19030
  );
18935
19031
  }
18936
19032
  currentStep.description = descResult.text;
@@ -45519,7 +45615,9 @@ function resolveMap(parsed, data) {
45519
45615
  const usScoped = parsed.directives.region === "us-states" || parsed.directives.defaultCountry?.toUpperCase() === "US" || parsed.regions.some((r) => {
45520
45616
  const f = fold(r.name);
45521
45617
  return usStateIndex.has(f) && !countryIndex.has(f);
45522
- }) || parsed.pois.some(
45618
+ }) || parsed.regions.some(
45619
+ (r) => r.scope === "US" || r.scope?.startsWith("US-")
45620
+ ) || parsed.pois.some(
45523
45621
  (p) => p.pos.kind === "name" && p.pos.scope?.startsWith("US-")
45524
45622
  );
45525
45623
  const regions = [];
@@ -45531,7 +45629,30 @@ function resolveMap(parsed, data) {
45531
45629
  const inCountry = countryIndex.get(f) ?? (REGION_ALIASES[f] ? countryIndex.get(REGION_ALIASES[f]) : void 0);
45532
45630
  const inState = usStateIndex.get(f);
45533
45631
  let chosen = null;
45534
- if (inCountry && inState) {
45632
+ const scope = r.scope;
45633
+ if (scope) {
45634
+ const wantsState = scope === "US" || scope.startsWith("US-");
45635
+ if (wantsState && inState) {
45636
+ if (scope.startsWith("US-") && inState.id !== scope) {
45637
+ err(
45638
+ r.lineNumber,
45639
+ `No subdivision "${r.name}" in scope ${scope} (it is ${inState.id}).`,
45640
+ "E_MAP_SCOPE_MISS"
45641
+ );
45642
+ continue;
45643
+ }
45644
+ chosen = { ...inState, layer: "us-state" };
45645
+ } else if (!wantsState && inCountry) {
45646
+ chosen = { ...inCountry, layer: "country" };
45647
+ } else {
45648
+ err(
45649
+ r.lineNumber,
45650
+ `No region "${r.name}" found in scope ${scope}.`,
45651
+ "E_MAP_SCOPE_MISS"
45652
+ );
45653
+ continue;
45654
+ }
45655
+ } else if (inCountry && inState) {
45535
45656
  if (usScoped) {
45536
45657
  chosen = { ...inState, layer: "us-state" };
45537
45658
  } else {
@@ -45539,7 +45660,7 @@ function resolveMap(parsed, data) {
45539
45660
  }
45540
45661
  warn(
45541
45662
  r.lineNumber,
45542
- `"${r.name}" is both a country and a US state \u2014 resolved as ${chosen.layer} (${chosen.id}).`,
45663
+ `"${r.name}" is both a country and a US state \u2014 resolved as ${chosen.layer} (${chosen.id}). Pin it with an ISO code (${inState.id} / ${inCountry.id}) or name + scope ("${r.name} US" / "${r.name} ${inCountry.id}").`,
45543
45664
  "W_MAP_REGION_AMBIGUOUS"
45544
45665
  );
45545
45666
  } else if (inState) {
@@ -45711,9 +45832,17 @@ function resolveMap(parsed, data) {
45711
45832
  };
45712
45833
  registerPoi(id, poi, p.lineNumber);
45713
45834
  }
45835
+ const declaredByName = /* @__PURE__ */ new Map();
45836
+ for (const p of pois) {
45837
+ const fn = p.name ? fold(p.name) : void 0;
45838
+ if (fn && fn !== p.id && !declaredByName.has(fn))
45839
+ declaredByName.set(fn, p.id);
45840
+ }
45714
45841
  const resolveEndpoint2 = (ref, line12) => {
45715
45842
  const f = fold(ref);
45716
45843
  if (registry.has(f)) return f;
45844
+ const aliased = declaredByName.get(f);
45845
+ if (aliased) return aliased;
45717
45846
  const got = lookupName(ref, void 0, line12, inferredCountry, true);
45718
45847
  if (got.kind !== "ok") return null;
45719
45848
  noteCountry(got.iso);
@@ -45793,23 +45922,29 @@ function resolveMap(parsed, data) {
45793
45922
  [-180, -85],
45794
45923
  [180, 85]
45795
45924
  ];
45796
- const extent2 = unioned ? pad(unioned, PAD_FRACTION) : DEFAULT_EXTENT;
45925
+ let extent2 = unioned ? pad(unioned, PAD_FRACTION) : DEFAULT_EXTENT;
45797
45926
  const lonSpan = extent2[1][0] - extent2[0][0];
45798
45927
  const latSpan = extent2[1][1] - extent2[0][1];
45799
45928
  const span = Math.max(lonSpan, latSpan);
45800
45929
  const usDominant = (inferredCountry === "US" || subdivisions.includes("us-states")) && !regions.some((r) => r.layer === "country" && r.iso !== "US") && !anyNonUsPoi;
45801
45930
  let projection;
45802
45931
  const override = parsed.directives.projection;
45803
- if (override === "natural-earth" || override === "albers-usa" || override === "mercator") {
45932
+ if (override === "equirectangular" || override === "natural-earth" || override === "albers-usa" || override === "mercator") {
45804
45933
  projection = override;
45805
45934
  } else if (usDominant) {
45806
45935
  projection = "albers-usa";
45807
45936
  } else if (span > WORLD_SPAN) {
45808
- projection = "natural-earth";
45937
+ projection = "equirectangular";
45809
45938
  } else if (span < MERCATOR_MAX_SPAN) {
45810
45939
  projection = "mercator";
45811
45940
  } else {
45812
- projection = "natural-earth";
45941
+ projection = "equirectangular";
45942
+ }
45943
+ if (lonSpan >= 180) {
45944
+ extent2 = [
45945
+ [-180, Math.min(extent2[0][1], WORLD_LAT_SOUTH)],
45946
+ [180, Math.max(extent2[1][1], WORLD_LAT_NORTH)]
45947
+ ];
45813
45948
  }
45814
45949
  result.regions = regions;
45815
45950
  result.pois = pois;
@@ -45857,7 +45992,7 @@ function firstError(diags) {
45857
45992
  const e = diags.find((d) => d.severity === "error");
45858
45993
  return e ? formatDgmoError(e) : null;
45859
45994
  }
45860
- var WORLD_SPAN, MERCATOR_MAX_SPAN, PAD_FRACTION, REGION_ALIASES;
45995
+ var WORLD_SPAN, MERCATOR_MAX_SPAN, PAD_FRACTION, WORLD_LAT_SOUTH, WORLD_LAT_NORTH, REGION_ALIASES;
45861
45996
  var init_resolver2 = __esm({
45862
45997
  "src/map/resolver.ts"() {
45863
45998
  "use strict";
@@ -45866,6 +46001,8 @@ var init_resolver2 = __esm({
45866
46001
  WORLD_SPAN = 90;
45867
46002
  MERCATOR_MAX_SPAN = 25;
45868
46003
  PAD_FRACTION = 0.05;
46004
+ WORLD_LAT_SOUTH = -58;
46005
+ WORLD_LAT_NORTH = 78;
45869
46006
  REGION_ALIASES = {
45870
46007
  // Common everyday names → the Natural-Earth display name actually shipped.
45871
46008
  "united states": "united states of america",
@@ -45895,17 +46032,22 @@ var load_data_exports = {};
45895
46032
  __export(load_data_exports, {
45896
46033
  loadMapData: () => loadMapData
45897
46034
  });
45898
- import { readFile } from "fs/promises";
45899
- import { fileURLToPath } from "url";
45900
- import { dirname, resolve } from "path";
45901
- async function readJson(dir, name) {
45902
- return JSON.parse(await readFile(resolve(dir, name), "utf8"));
46035
+ async function loadNodeBuiltins() {
46036
+ const [{ readFile }, { fileURLToPath }, { dirname, resolve }] = await Promise.all([
46037
+ import("fs/promises"),
46038
+ import("url"),
46039
+ import("path")
46040
+ ]);
46041
+ return { readFile, fileURLToPath, dirname, resolve };
46042
+ }
46043
+ async function readJson(nb, dir, name) {
46044
+ return JSON.parse(await nb.readFile(nb.resolve(dir, name), "utf8"));
45903
46045
  }
45904
- async function firstExistingDir(baseDir) {
46046
+ async function firstExistingDir(nb, baseDir) {
45905
46047
  for (const rel of CANDIDATE_DIRS) {
45906
- const dir = resolve(baseDir, rel);
46048
+ const dir = nb.resolve(baseDir, rel);
45907
46049
  try {
45908
- await readFile(resolve(dir, FILES.gazetteer), "utf8");
46050
+ await nb.readFile(nb.resolve(dir, FILES.gazetteer), "utf8");
45909
46051
  return dir;
45910
46052
  } catch {
45911
46053
  }
@@ -45921,10 +46063,10 @@ function validate(data) {
45921
46063
  }
45922
46064
  return data;
45923
46065
  }
45924
- function moduleBaseDir() {
46066
+ function moduleBaseDir(nb) {
45925
46067
  try {
45926
46068
  const url = import.meta.url;
45927
- if (url) return dirname(fileURLToPath(url));
46069
+ if (url) return nb.dirname(nb.fileURLToPath(url));
45928
46070
  } catch {
45929
46071
  }
45930
46072
  if (typeof __dirname !== "undefined") return __dirname;
@@ -45932,14 +46074,38 @@ function moduleBaseDir() {
45932
46074
  }
45933
46075
  function loadMapData() {
45934
46076
  cache ??= (async () => {
45935
- const dir = await firstExistingDir(moduleBaseDir());
45936
- const [worldCoarse, worldDetail, usStates, gazetteer] = await Promise.all([
45937
- readJson(dir, FILES.worldCoarse),
45938
- readJson(dir, FILES.worldDetail),
45939
- readJson(dir, FILES.usStates),
45940
- readJson(dir, FILES.gazetteer)
46077
+ const nb = await loadNodeBuiltins();
46078
+ const dir = await firstExistingDir(nb, moduleBaseDir(nb));
46079
+ const [
46080
+ worldCoarse,
46081
+ worldDetail,
46082
+ usStates,
46083
+ lakes,
46084
+ rivers,
46085
+ naLand,
46086
+ naLakes,
46087
+ gazetteer
46088
+ ] = await Promise.all([
46089
+ readJson(nb, dir, FILES.worldCoarse),
46090
+ readJson(nb, dir, FILES.worldDetail),
46091
+ readJson(nb, dir, FILES.usStates),
46092
+ // Lakes/rivers/NA assets are optional — older bundles may predate them.
46093
+ readJson(nb, dir, FILES.lakes).catch(() => void 0),
46094
+ readJson(nb, dir, FILES.rivers).catch(() => void 0),
46095
+ readJson(nb, dir, FILES.naLand).catch(() => void 0),
46096
+ readJson(nb, dir, FILES.naLakes).catch(() => void 0),
46097
+ readJson(nb, dir, FILES.gazetteer)
45941
46098
  ]);
45942
- return validate({ worldCoarse, worldDetail, usStates, gazetteer });
46099
+ return validate({
46100
+ worldCoarse,
46101
+ worldDetail,
46102
+ usStates,
46103
+ gazetteer,
46104
+ ...lakes && { lakes },
46105
+ ...rivers && { rivers },
46106
+ ...naLand && { naLand },
46107
+ ...naLakes && { naLakes }
46108
+ });
45943
46109
  })().catch((e) => {
45944
46110
  cache = void 0;
45945
46111
  throw e;
@@ -45954,6 +46120,10 @@ var init_load_data = __esm({
45954
46120
  worldCoarse: "world-coarse.json",
45955
46121
  worldDetail: "world-detail.json",
45956
46122
  usStates: "us-states.json",
46123
+ lakes: "lakes.json",
46124
+ rivers: "rivers.json",
46125
+ naLand: "na-land.json",
46126
+ naLakes: "na-lakes.json",
45957
46127
  gazetteer: "gazetteer.json"
45958
46128
  };
45959
46129
  CANDIDATE_DIRS = [
@@ -45969,8 +46139,11 @@ var init_load_data = __esm({
45969
46139
  import {
45970
46140
  geoPath,
45971
46141
  geoNaturalEarth1,
45972
- geoAlbersUsa,
45973
- geoMercator
46142
+ geoEquirectangular,
46143
+ geoConicEqualArea,
46144
+ geoMercator,
46145
+ geoBounds as geoBounds2,
46146
+ geoTransform
45974
46147
  } from "d3-geo";
45975
46148
  import { feature as feature2 } from "topojson-client";
45976
46149
  function geomObject2(topo) {
@@ -45988,36 +46161,67 @@ function decodeLayer(topo) {
45988
46161
  function projectionFor(family) {
45989
46162
  switch (family) {
45990
46163
  case "albers-usa":
45991
- return geoAlbersUsa();
46164
+ return usConusProjection();
45992
46165
  case "mercator":
45993
46166
  return geoMercator();
45994
46167
  case "natural-earth":
45995
- default:
45996
46168
  return geoNaturalEarth1();
46169
+ case "equirectangular":
46170
+ default:
46171
+ return geoEquirectangular();
45997
46172
  }
45998
46173
  }
46174
+ function mapBackgroundColor(palette) {
46175
+ return mix(palette.colors.blue, palette.bg, WATER_TINT);
46176
+ }
45999
46177
  function layoutMap(resolved, data, size, opts) {
46000
46178
  const { palette, isDark } = opts;
46001
46179
  const { width, height } = size;
46002
- const worldTopo = resolved.basemaps.world === "detail" ? data.worldDetail : data.worldCoarse;
46180
+ const wantsUsStates = resolved.basemaps.subdivisions.includes("us-states");
46181
+ const usCrisp = resolved.projection === "albers-usa" && wantsUsStates && !!data.naLand;
46182
+ const worldTopo = usCrisp ? data.naLand : resolved.basemaps.world === "detail" ? data.worldDetail : data.worldCoarse;
46003
46183
  const worldLayer = decodeLayer(worldTopo);
46004
- const usLayer = resolved.basemaps.subdivisions.includes("us-states") ? decodeLayer(data.usStates) : null;
46005
- const neutralFill = mix(palette.border, palette.bg, 32);
46006
- const regionStroke = mix(palette.border, palette.bg, 70);
46184
+ const usLayer = wantsUsStates ? decodeLayer(data.usStates) : null;
46185
+ const landTint = isDark ? LAND_TINT_DARK : LAND_TINT_LIGHT;
46186
+ const neutralFill = mix(palette.colors.green, palette.bg, landTint);
46187
+ const water = mapBackgroundColor(palette);
46188
+ const usContext = usLayer !== null;
46189
+ const foreignFill = mix(
46190
+ palette.colors.gray,
46191
+ palette.bg,
46192
+ isDark ? FOREIGN_TINT_DARK : FOREIGN_TINT_LIGHT
46193
+ );
46194
+ const regionStroke = isDark ? mix(palette.bg, palette.text, 78) : mix(palette.text, palette.bg, 78);
46007
46195
  const scores = resolved.regions.filter((r) => r.score !== void 0).map((r) => r.score);
46008
46196
  const scaleOverride = resolved.directives.scale;
46009
46197
  const rampMin = scaleOverride ? scaleOverride.min : Math.min(...scores);
46010
46198
  const rampMax = scaleOverride ? scaleOverride.max : Math.max(...scores);
46011
- const rampHue = palette.primary;
46199
+ const rampHue = palette.colors.red;
46012
46200
  const hasRamp = scores.length > 0;
46013
- const activeGroup = resolveActiveTagGroup(
46014
- resolved.tagGroups,
46015
- resolved.directives.activeTag
46016
- );
46201
+ const SCORE_NAME = hasRamp ? resolved.directives.metric?.trim() || "Score" : null;
46202
+ const matchColorGroup = (v) => {
46203
+ const lv = v.trim().toLowerCase();
46204
+ if (lv === "none") return null;
46205
+ if (SCORE_NAME && (lv === "score" || lv === SCORE_NAME.toLowerCase()))
46206
+ return SCORE_NAME;
46207
+ const tg = resolved.tagGroups.find((g) => g.name.toLowerCase() === lv);
46208
+ return tg ? tg.name : v;
46209
+ };
46210
+ const override = opts.activeGroup;
46211
+ let activeGroup;
46212
+ if (override !== void 0) {
46213
+ activeGroup = override === null ? null : matchColorGroup(override);
46214
+ } else if (resolved.directives.activeTag !== void 0) {
46215
+ activeGroup = matchColorGroup(resolved.directives.activeTag);
46216
+ } else {
46217
+ activeGroup = SCORE_NAME ?? (resolved.tagGroups.length > 0 ? resolved.tagGroups[0].name : null);
46218
+ }
46219
+ const activeIsScore = SCORE_NAME !== null && activeGroup === SCORE_NAME;
46220
+ const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
46017
46221
  const fillForScore = (s) => {
46018
46222
  const t = rampMax > rampMin ? (s - rampMin) / (rampMax - rampMin) : 1;
46019
46223
  const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
46020
- return mix(rampHue, isDark ? palette.surface : palette.bg, pct);
46224
+ return mix(rampHue, rampBase, pct);
46021
46225
  };
46022
46226
  const tagFill = (tags, groupName) => {
46023
46227
  if (!groupName) return null;
@@ -46031,40 +46235,40 @@ function layoutMap(resolved, data, size, opts) {
46031
46235
  (e) => e.value.toLowerCase() === val.toLowerCase()
46032
46236
  );
46033
46237
  if (!entry?.color) return null;
46034
- return shapeFill(palette, entry.color, isDark);
46238
+ return mix(
46239
+ entry.color,
46240
+ palette.bg,
46241
+ isDark ? TAG_TINT_DARK : TAG_TINT_LIGHT
46242
+ );
46243
+ };
46244
+ const regionFill = (r) => {
46245
+ if (activeIsScore) {
46246
+ return r.score !== void 0 ? fillForScore(r.score) : neutralFill;
46247
+ }
46248
+ return tagFill(r.tags, activeGroup) ?? neutralFill;
46035
46249
  };
46036
46250
  const regionById = new Map(resolved.regions.map((r) => [r.iso, r]));
46037
- const regionFeatures = [];
46038
- for (const r of resolved.regions) {
46039
- const f = r.layer === "us-state" ? usLayer?.get(r.iso) : worldLayer.get(r.iso);
46040
- if (f) regionFeatures.push(f);
46041
- }
46042
- const extentCorners = () => {
46251
+ const extentOutline = () => {
46043
46252
  const [[w, s], [e, n]] = resolved.extent;
46253
+ const N = 16;
46254
+ const coords = [];
46255
+ for (let i = 0; i <= N; i++) {
46256
+ const t = i / N;
46257
+ const lon = w + (e - w) * t;
46258
+ const lat = s + (n - s) * t;
46259
+ coords.push([lon, s], [lon, n], [w, lat], [e, lat]);
46260
+ }
46044
46261
  return {
46045
46262
  type: "Feature",
46046
46263
  properties: {},
46047
- geometry: {
46048
- type: "MultiPoint",
46049
- coordinates: [
46050
- [w, s],
46051
- [e, s],
46052
- [e, n],
46053
- [w, n]
46054
- ]
46055
- }
46264
+ geometry: { type: "MultiPoint", coordinates: coords }
46056
46265
  };
46057
46266
  };
46058
46267
  let fitFeatures;
46059
- if (resolved.projection === "albers-usa") {
46060
- if (regionFeatures.length > 0) fitFeatures = regionFeatures;
46061
- else if (usLayer) fitFeatures = [...usLayer.values()];
46062
- else {
46063
- const us = worldLayer.get("US");
46064
- fitFeatures = us ? [us] : [...worldLayer.values()];
46065
- }
46268
+ if (resolved.projection === "albers-usa" && usLayer) {
46269
+ fitFeatures = [...usLayer.entries()].filter(([iso]) => !US_NON_CONUS.has(iso)).map(([, f]) => f);
46066
46270
  } else {
46067
- fitFeatures = [extentCorners()];
46271
+ fitFeatures = [extentOutline()];
46068
46272
  }
46069
46273
  const fitTarget = { type: "FeatureCollection", features: fitFeatures };
46070
46274
  const projection = projectionFor(resolved.projection);
@@ -46073,32 +46277,311 @@ function layoutMap(resolved, data, size, opts) {
46073
46277
  if (centerLon > 180) centerLon -= 360;
46074
46278
  projection.rotate([-centerLon, 0]);
46075
46279
  }
46076
- projection.fitExtent(
46280
+ const TITLE_GAP = 16;
46281
+ let topPad = FIT_PAD;
46282
+ if (resolved.title && resolved.pois.length > 0) {
46283
+ const bannerBottom = (resolved.subtitle ? TITLE_Y + TITLE_FONT_SIZE : TITLE_Y) + TITLE_FONT_SIZE / 2;
46284
+ topPad = Math.max(FIT_PAD, bannerBottom + TITLE_GAP);
46285
+ }
46286
+ const fitBox = [
46287
+ [FIT_PAD, topPad],
46077
46288
  [
46078
- [FIT_PAD, FIT_PAD],
46079
- [
46080
- Math.max(FIT_PAD + 1, width - FIT_PAD),
46081
- Math.max(FIT_PAD + 1, height - FIT_PAD)
46082
- ]
46083
- ],
46084
- fitTarget
46085
- );
46086
- const path = geoPath(projection);
46087
- const project = (lon, lat) => projection([lon, lat]) ?? null;
46289
+ Math.max(FIT_PAD + 1, width - FIT_PAD),
46290
+ Math.max(topPad + 1, height - FIT_PAD)
46291
+ ]
46292
+ ];
46293
+ projection.fitExtent(fitBox, fitTarget);
46294
+ const fitGB = geoBounds2(fitTarget);
46295
+ const fitIsGlobal = fitGB[1][0] - fitGB[0][0] >= 270 || fitGB[1][1] - fitGB[0][1] >= 130;
46296
+ let path;
46297
+ let project;
46298
+ if (fitIsGlobal) {
46299
+ const cb = geoPath(projection).bounds(fitTarget);
46300
+ const bx0 = cb[0][0];
46301
+ const by0 = cb[0][1];
46302
+ const cw = cb[1][0] - bx0;
46303
+ const ch = cb[1][1] - by0;
46304
+ const ox = fitBox[0][0];
46305
+ const oy = fitBox[0][1];
46306
+ const sx = cw > 0 ? (fitBox[1][0] - ox) / cw : 1;
46307
+ const sy = ch > 0 ? (fitBox[1][1] - oy) / ch : 1;
46308
+ const stretch = (x, y) => [
46309
+ ox + (x - bx0) * sx,
46310
+ oy + (y - by0) * sy
46311
+ ];
46312
+ const baseProjection = projection;
46313
+ const tx = geoTransform({
46314
+ point(x, y) {
46315
+ const [px, py] = stretch(x, y);
46316
+ this.stream.point(px, py);
46317
+ }
46318
+ });
46319
+ path = geoPath({
46320
+ stream: (s) => baseProjection.stream(
46321
+ tx.stream(s)
46322
+ )
46323
+ });
46324
+ project = (lon, lat) => {
46325
+ const p = baseProjection([lon, lat]);
46326
+ return p ? stretch(p[0], p[1]) : null;
46327
+ };
46328
+ } else {
46329
+ path = geoPath(projection);
46330
+ project = (lon, lat) => projection([lon, lat]) ?? null;
46331
+ }
46332
+ const insets = [];
46333
+ const insetRegions = [];
46334
+ const insetLabelSeeds = [];
46335
+ if (resolved.projection === "albers-usa" && usLayer) {
46336
+ const PAD = 8;
46337
+ const GAP = 12;
46338
+ const yB = height - FIT_PAD;
46339
+ const BW = 8;
46340
+ const coast = /* @__PURE__ */ new Map();
46341
+ const addPt = (lon, lat) => {
46342
+ const p = projection([lon, lat]);
46343
+ if (!p) return;
46344
+ const bi = Math.floor(p[0] / BW);
46345
+ const cur = coast.get(bi);
46346
+ if (cur === void 0 || p[1] > cur) coast.set(bi, p[1]);
46347
+ };
46348
+ const walk = (co) => {
46349
+ if (Array.isArray(co) && typeof co[0] === "number")
46350
+ addPt(co[0], co[1]);
46351
+ else if (Array.isArray(co)) for (const c of co) walk(c);
46352
+ };
46353
+ for (const [iso, f] of usLayer) {
46354
+ if (US_NON_CONUS.has(iso)) continue;
46355
+ walk(f.geometry.coordinates);
46356
+ }
46357
+ const at = (x) => {
46358
+ const bi = Math.floor(x / BW);
46359
+ let y = -Infinity;
46360
+ for (let k = bi - 1; k <= bi + 1; k++) {
46361
+ const v = coast.get(k);
46362
+ if (v !== void 0 && v > y) y = v;
46363
+ }
46364
+ return y;
46365
+ };
46366
+ const coastTop = (x0, xr) => {
46367
+ const n = 24;
46368
+ const pts = [];
46369
+ let maxY = -Infinity;
46370
+ for (let i = 0; i <= n; i++) {
46371
+ const x = x0 + (xr - x0) * i / n;
46372
+ const y = at(x);
46373
+ if (y > -Infinity) {
46374
+ pts.push([x, y]);
46375
+ if (y > maxY) maxY = y;
46376
+ }
46377
+ }
46378
+ if (pts.length === 0) return () => yB - height * 0.42;
46379
+ let m = 0;
46380
+ if (pts.length >= 2) {
46381
+ let sx = 0, sy = 0, sxx = 0, sxy = 0;
46382
+ for (const [x, y] of pts) {
46383
+ sx += x;
46384
+ sy += y;
46385
+ sxx += x * x;
46386
+ sxy += x * y;
46387
+ }
46388
+ const den = pts.length * sxx - sx * sx;
46389
+ if (den !== 0) m = (pts.length * sxy - sx * sy) / den;
46390
+ }
46391
+ m = Math.max(-0.35, Math.min(0.35, m));
46392
+ let c = -Infinity;
46393
+ for (const [x, y] of pts) {
46394
+ const need = y - m * x + GAP;
46395
+ if (need > c) c = need;
46396
+ }
46397
+ return (x) => m * x + c;
46398
+ };
46399
+ const placeInset = (iso, proj, boxX, iwReq) => {
46400
+ const f = usLayer.get(iso);
46401
+ if (!f) return boxX;
46402
+ const x0 = boxX;
46403
+ const iw = Math.min(iwReq, width - FIT_PAD - x0 - 2 * PAD);
46404
+ if (iw < 24) return boxX;
46405
+ const xr = x0 + iw + 2 * PAD;
46406
+ const top = coastTop(x0, xr);
46407
+ const yL = top(x0);
46408
+ const yR = top(xr);
46409
+ proj.fitWidth(iw, f);
46410
+ const bb = geoPath(proj).bounds(f);
46411
+ const sh = Number.isFinite(bb[0][0]) ? bb[1][1] - bb[0][1] : iw;
46412
+ const needH = sh + 2 * PAD;
46413
+ let topFit = Math.max(yL, yR);
46414
+ const bottom = Math.min(topFit + needH, yB);
46415
+ if (bottom - topFit < needH) topFit = bottom - needH;
46416
+ const lift = topFit - Math.max(yL, yR);
46417
+ const topL = yL + lift;
46418
+ const topR = yR + lift;
46419
+ proj.fitExtent(
46420
+ [
46421
+ [x0 + PAD, topFit + PAD],
46422
+ [xr - PAD, bottom - PAD]
46423
+ ],
46424
+ f
46425
+ );
46426
+ const d = geoPath(proj)(f) ?? "";
46427
+ if (!d) return xr;
46428
+ const r = regionById.get(iso);
46429
+ let fill2 = neutralFill;
46430
+ let lineNumber = -1;
46431
+ if (r?.layer === "us-state") {
46432
+ fill2 = regionFill(r);
46433
+ lineNumber = r.lineNumber;
46434
+ }
46435
+ insets.push({
46436
+ x: x0,
46437
+ y: Math.min(topL, topR),
46438
+ w: xr - x0,
46439
+ h: bottom - Math.min(topL, topR),
46440
+ points: [
46441
+ [x0, topL],
46442
+ [xr, topR],
46443
+ [xr, bottom],
46444
+ [x0, bottom]
46445
+ ]
46446
+ });
46447
+ insetRegions.push({
46448
+ id: iso,
46449
+ d,
46450
+ fill: fill2,
46451
+ stroke: regionStroke,
46452
+ lineNumber,
46453
+ layer: "us-state",
46454
+ ...r?.score !== void 0 && { score: r.score },
46455
+ ...r && Object.keys(r.tags).length > 0 && { tags: r.tags }
46456
+ });
46457
+ const ctr = geoPath(proj).centroid(f);
46458
+ if (Number.isFinite(ctr[0])) {
46459
+ const name = f.properties?.name ?? iso;
46460
+ insetLabelSeeds.push({ x: ctr[0], y: ctr[1], iso, name, lineNumber });
46461
+ }
46462
+ return xr;
46463
+ };
46464
+ const akRight = placeInset(
46465
+ "US-AK",
46466
+ alaskaProjection(),
46467
+ FIT_PAD,
46468
+ width * 0.15
46469
+ );
46470
+ placeInset("US-HI", hawaiiProjection(), akRight + 24, width * 0.1);
46471
+ }
46472
+ const conusFit = resolved.projection === "albers-usa" && !!usLayer;
46473
+ const cullExtent = conusFit ? geoBounds2(fitTarget) : resolved.extent;
46474
+ const [[exW, exS], [exE, exN]] = cullExtent;
46475
+ const lonSpan = exE - exW;
46476
+ const latSpan = exN - exS;
46477
+ const isGlobalView = lonSpan >= 270 || latSpan >= 130;
46478
+ const padLon = Math.max(8, lonSpan * 0.35);
46479
+ const padLat = Math.max(8, latSpan * 0.35);
46480
+ const vW = exW - padLon;
46481
+ const vE = exE + padLon;
46482
+ const vS = exS - padLat;
46483
+ const vN = exN + padLat;
46484
+ const vLonCenter = (exW + exE) / 2;
46485
+ const normLon = (lon) => {
46486
+ let L = lon;
46487
+ while (L < vLonCenter - 180) L += 360;
46488
+ while (L > vLonCenter + 180) L -= 360;
46489
+ return L;
46490
+ };
46491
+ const ringOverlapsView = (ring) => {
46492
+ let anyIn = false;
46493
+ let loMin = Infinity, loMax = -Infinity, laMin = Infinity, laMax = -Infinity, rawMin = Infinity, rawMax = -Infinity;
46494
+ for (const [rawLon, lat] of ring) {
46495
+ const lon = normLon(rawLon);
46496
+ if (lon >= vW && lon <= vE && lat >= vS && lat <= vN) anyIn = true;
46497
+ if (lon < loMin) loMin = lon;
46498
+ if (lon > loMax) loMax = lon;
46499
+ if (rawLon < rawMin) rawMin = rawLon;
46500
+ if (rawLon > rawMax) rawMax = rawLon;
46501
+ if (lat < laMin) laMin = lat;
46502
+ if (lat > laMax) laMax = lat;
46503
+ }
46504
+ if (loMax - loMin > 270) return false;
46505
+ if (rawMax - rawMin > 180 && loMax - loMin < 90) return false;
46506
+ if (anyIn) return true;
46507
+ if (loMax - loMin > 180) return false;
46508
+ return !(loMax < vW || loMin > vE || laMax < vS || laMin > vN);
46509
+ };
46510
+ const cullFeatureToView = (f) => {
46511
+ if (isGlobalView) return f;
46512
+ const g = f.geometry;
46513
+ if (!g) return f;
46514
+ if (g.type === "Polygon") {
46515
+ const ring = g.coordinates[0];
46516
+ return ringOverlapsView(ring) ? f : null;
46517
+ }
46518
+ if (g.type === "MultiPolygon") {
46519
+ const polys = g.coordinates;
46520
+ const keep = polys.filter(
46521
+ (p) => ringOverlapsView(p[0])
46522
+ );
46523
+ if (!keep.length) return null;
46524
+ if (keep.length === polys.length) return f;
46525
+ return { ...f, geometry: { ...g, coordinates: keep } };
46526
+ }
46527
+ return f;
46528
+ };
46529
+ const SEAM_SLIVER_MAX_SPAN = 100;
46530
+ const ringIsFrameFiller = (ring) => {
46531
+ const lons = ring.map(([lon]) => lon).sort((a, b) => a - b);
46532
+ if (lons.length < 2) return false;
46533
+ let maxGap = -1;
46534
+ let gapIdx = 0;
46535
+ for (let i = 1; i < lons.length; i++) {
46536
+ const g = lons[i] - lons[i - 1];
46537
+ if (g > maxGap) {
46538
+ maxGap = g;
46539
+ gapIdx = i;
46540
+ }
46541
+ }
46542
+ const wrapGap = lons[0] + 360 - lons[lons.length - 1];
46543
+ if (wrapGap >= maxGap) return false;
46544
+ const span = 360 - maxGap;
46545
+ const east = lons[gapIdx - 1] + 360;
46546
+ return east > 180 && span < SEAM_SLIVER_MAX_SPAN;
46547
+ };
46548
+ const dropFrameFillers = (f) => {
46549
+ const g = f.geometry;
46550
+ if (!g) return f;
46551
+ if (g.type === "Polygon") {
46552
+ const ring = g.coordinates[0];
46553
+ return ringIsFrameFiller(ring) ? null : f;
46554
+ }
46555
+ if (g.type === "MultiPolygon") {
46556
+ const polys = g.coordinates;
46557
+ const keep = polys.filter(
46558
+ (p) => !ringIsFrameFiller(p[0])
46559
+ );
46560
+ if (!keep.length) return null;
46561
+ if (keep.length === polys.length) return f;
46562
+ return { ...f, geometry: { ...g, coordinates: keep } };
46563
+ }
46564
+ return f;
46565
+ };
46088
46566
  const regions = [];
46089
- const pushRegionLayer = (layerFeatures, layerKind) => {
46567
+ const pushRegionLayer = (layerFeatures, layerKind, shouldCull) => {
46090
46568
  for (const [iso, f] of layerFeatures) {
46091
- const d = path(f) ?? "";
46092
- if (!d) continue;
46569
+ if (layerKind === "us-state" && usContext && INSET_STATES.has(iso))
46570
+ continue;
46571
+ if (layerKind === "country" && usContext && iso === "US") continue;
46093
46572
  const r = regionById.get(iso);
46573
+ const viewF = shouldCull ? cullFeatureToView(f) : dropFrameFillers(f);
46574
+ if (!viewF) continue;
46575
+ const d = path(viewF) ?? "";
46576
+ if (!d) continue;
46094
46577
  const isThisLayer = r?.layer === layerKind;
46095
- let fill2 = neutralFill;
46578
+ const isForeign = layerKind === "country" && usContext && iso !== "US";
46579
+ let fill2 = isForeign ? foreignFill : neutralFill;
46096
46580
  let label;
46097
46581
  let lineNumber = -1;
46098
46582
  let layer = "base";
46099
46583
  if (isThisLayer) {
46100
- if (r.score !== void 0) fill2 = fillForScore(r.score);
46101
- else fill2 = tagFill(r.tags, activeGroup) ?? neutralFill;
46584
+ fill2 = regionFill(r);
46102
46585
  lineNumber = r.lineNumber;
46103
46586
  layer = layerKind;
46104
46587
  label = r.name;
@@ -46110,12 +46593,42 @@ function layoutMap(resolved, data, size, opts) {
46110
46593
  stroke: regionStroke,
46111
46594
  lineNumber,
46112
46595
  layer,
46113
- ...label !== void 0 && { label }
46596
+ ...label !== void 0 && { label },
46597
+ ...isThisLayer && r.score !== void 0 && { score: r.score },
46598
+ ...isThisLayer && Object.keys(r.tags).length > 0 && { tags: r.tags }
46114
46599
  });
46115
46600
  }
46116
46601
  };
46117
- pushRegionLayer(worldLayer, "country");
46118
- if (usLayer) pushRegionLayer(usLayer, "us-state");
46602
+ pushRegionLayer(worldLayer, "country", !isGlobalView);
46603
+ if (usLayer) pushRegionLayer(usLayer, "us-state", !conusFit && !isGlobalView);
46604
+ const lakesTopo = usCrisp && data.naLakes ? data.naLakes : data.lakes;
46605
+ if (lakesTopo) {
46606
+ for (const [, f] of decodeLayer(lakesTopo)) {
46607
+ const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
46608
+ if (!viewF) continue;
46609
+ const d = path(viewF) ?? "";
46610
+ if (!d) continue;
46611
+ regions.push({
46612
+ id: "lake",
46613
+ d,
46614
+ fill: water,
46615
+ stroke: "none",
46616
+ lineNumber: -1,
46617
+ layer: "base"
46618
+ });
46619
+ }
46620
+ }
46621
+ const riverColor = water;
46622
+ const rivers = [];
46623
+ if (data.rivers) {
46624
+ for (const [, f] of decodeLayer(data.rivers)) {
46625
+ const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
46626
+ if (!viewF) continue;
46627
+ const d = path(viewF) ?? "";
46628
+ if (!d) continue;
46629
+ rivers.push({ d, color: riverColor, width: RIVER_WIDTH });
46630
+ }
46631
+ }
46119
46632
  const sizeVals = resolved.pois.map((p) => Number(p.meta["size"])).filter((n) => Number.isFinite(n) && n > 0);
46120
46633
  const sizeMin = sizeVals.length ? Math.min(...sizeVals) : 0;
46121
46634
  const sizeMax = sizeVals.length ? Math.max(...sizeVals) : 0;
@@ -46136,8 +46649,8 @@ function layoutMap(resolved, data, size, opts) {
46136
46649
  if (hex) return { fill: hex, stroke: mix(hex, palette.text, 18) };
46137
46650
  }
46138
46651
  return {
46139
- fill: palette.accent,
46140
- stroke: mix(palette.accent, palette.text, 18)
46652
+ fill: palette.colors.orange,
46653
+ stroke: mix(palette.colors.orange, palette.text, 18)
46141
46654
  };
46142
46655
  };
46143
46656
  const routeNumberById = /* @__PURE__ */ new Map();
@@ -46175,7 +46688,7 @@ function layoutMap(resolved, data, size, opts) {
46175
46688
  cy += Math.sin(ang) * COLO_R;
46176
46689
  }
46177
46690
  const { fill: fill2, stroke: stroke2 } = poiFill(e.p);
46178
- poiScreen.set(e.p.id, { cx, cy });
46691
+ poiScreen.set(e.p.id, { cx, cy, r: radiusFor(e.p) });
46179
46692
  const num = routeNumberById.get(e.p.id);
46180
46693
  pois.push({
46181
46694
  id: e.p.id,
@@ -46192,17 +46705,36 @@ function layoutMap(resolved, data, size, opts) {
46192
46705
  });
46193
46706
  }
46194
46707
  const legs = [];
46708
+ const RIM_GAP = 1.5;
46195
46709
  const legPath = (a, b, curved, offset) => {
46196
- if (!curved && offset === 0) return `M${a.cx},${a.cy}L${b.cx},${b.cy}`;
46197
46710
  const mx = (a.cx + b.cx) / 2;
46198
46711
  const my = (a.cy + b.cy) / 2;
46199
46712
  const dx = b.cx - a.cx;
46200
46713
  const dy = b.cy - a.cy;
46201
46714
  const len = Math.hypot(dx, dy) || 1;
46715
+ const trimA = Math.min(a.r + RIM_GAP, len * 0.45);
46716
+ const trimB = Math.min(b.r + RIM_GAP, len * 0.45);
46717
+ if (!curved && offset === 0) {
46718
+ const ux = dx / len;
46719
+ const uy = dy / len;
46720
+ const ax2 = a.cx + ux * trimA;
46721
+ const ay2 = a.cy + uy * trimA;
46722
+ const bx2 = b.cx - ux * trimB;
46723
+ const by2 = b.cy - uy * trimB;
46724
+ return `M${ax2},${ay2}L${bx2},${by2}`;
46725
+ }
46202
46726
  const nx = -dy / len;
46203
46727
  const ny = dx / len;
46204
46728
  const bow = offset !== 0 ? offset : len * ARC_CURVE_FRAC;
46205
- return `M${a.cx},${a.cy}Q${mx + nx * bow},${my + ny * bow} ${b.cx},${b.cy}`;
46729
+ const px = mx + nx * bow;
46730
+ const py = my + ny * bow;
46731
+ const ta = Math.hypot(px - a.cx, py - a.cy) || 1;
46732
+ const tb = Math.hypot(b.cx - px, b.cy - py) || 1;
46733
+ const ax = a.cx + (px - a.cx) / ta * trimA;
46734
+ const ay = a.cy + (py - a.cy) / ta * trimA;
46735
+ const bx = b.cx - (b.cx - px) / tb * trimB;
46736
+ const by = b.cy - (b.cy - py) / tb * trimB;
46737
+ return `M${ax},${ay}Q${px},${py} ${bx},${by}`;
46206
46738
  };
46207
46739
  for (const rt of resolved.routes) {
46208
46740
  const curved = rt.meta["style"] === "arc";
@@ -46213,7 +46745,7 @@ function layoutMap(resolved, data, size, opts) {
46213
46745
  legs.push({
46214
46746
  d: legPath(a, b, curved, 0),
46215
46747
  width: W_MIN,
46216
- color: mix(palette.text, palette.bg, 55),
46748
+ color: mix(palette.text, palette.bg, 72),
46217
46749
  arrow: true,
46218
46750
  lineNumber: rt.lineNumber
46219
46751
  });
@@ -46249,7 +46781,7 @@ function layoutMap(resolved, data, size, opts) {
46249
46781
  legs.push({
46250
46782
  d: legPath(a, b, curved, offset),
46251
46783
  width: widthFor(e),
46252
- color: mix(palette.text, palette.bg, 45),
46784
+ color: mix(palette.text, palette.bg, 66),
46253
46785
  arrow: e.directed,
46254
46786
  lineNumber: e.lineNumber,
46255
46787
  ...e.label !== void 0 && {
@@ -46261,38 +46793,92 @@ function layoutMap(resolved, data, size, opts) {
46261
46793
  });
46262
46794
  }
46263
46795
  const labels = [];
46264
- const pinList = [];
46265
46796
  const obstacles = [];
46266
46797
  const markers = pois.map((p) => ({
46267
46798
  cx: p.cx,
46268
46799
  cy: p.cy,
46269
46800
  r: p.r
46270
46801
  }));
46271
- const collides = (rect) => markers.some((m) => rectCircleOverlap(rect, m)) || obstacles.some((o) => rectsOverlap(rect, o));
46802
+ const legSegments = [];
46803
+ for (const leg of legs) {
46804
+ const m = /^M(-?[\d.]+),(-?[\d.]+)(?:L(-?[\d.]+),(-?[\d.]+)|Q(-?[\d.]+),(-?[\d.]+) (-?[\d.]+),(-?[\d.]+))$/.exec(
46805
+ leg.d
46806
+ );
46807
+ if (!m) continue;
46808
+ const x0 = +m[1];
46809
+ const y0 = +m[2];
46810
+ if (m[3] !== void 0) {
46811
+ legSegments.push([x0, y0, +m[3], +m[4]]);
46812
+ } else {
46813
+ const cx = +m[5];
46814
+ const cy = +m[6];
46815
+ const ex = +m[7];
46816
+ const ey = +m[8];
46817
+ const N = 8;
46818
+ let px = x0;
46819
+ let py = y0;
46820
+ for (let i = 1; i <= N; i++) {
46821
+ const t = i / N;
46822
+ const u = 1 - t;
46823
+ const qx = u * u * x0 + 2 * u * t * cx + t * t * ex;
46824
+ const qy = u * u * y0 + 2 * u * t * cy + t * t * ey;
46825
+ legSegments.push([px, py, qx, qy]);
46826
+ px = qx;
46827
+ py = qy;
46828
+ }
46829
+ }
46830
+ }
46831
+ 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));
46272
46832
  const regionLabelMode = resolved.directives.regionLabels ?? "off";
46833
+ const LABEL_PADX = 6;
46834
+ const LABEL_PADY = 3;
46835
+ const labelW = (text) => measureLegendText(text, FONT) + 2 * LABEL_PADX;
46836
+ const labelH = FONT + 2 * LABEL_PADY;
46837
+ const pushRegionLabel = (x, y, text, fill2, lineNumber) => {
46838
+ const color = contrastText(
46839
+ fill2,
46840
+ palette.textOnFillLight,
46841
+ palette.textOnFillDark
46842
+ );
46843
+ const haloColor = color === palette.textOnFillLight ? palette.textOnFillDark : palette.textOnFillLight;
46844
+ labels.push({
46845
+ x,
46846
+ y,
46847
+ text,
46848
+ anchor: "middle",
46849
+ color,
46850
+ halo: true,
46851
+ haloColor,
46852
+ lineNumber
46853
+ });
46854
+ };
46855
+ const WORLD_LABEL_ANCHORS = {
46856
+ US: [-98.5, 39.5]
46857
+ // CONUS geographic centre (near Lebanon, Kansas)
46858
+ };
46273
46859
  if (regionLabelMode === "full" || regionLabelMode === "abbrev") {
46274
46860
  for (const r of regions) {
46275
46861
  if (r.layer === "base" || r.label === void 0) continue;
46276
46862
  const f = r.layer === "us-state" ? usLayer?.get(r.id) : worldLayer.get(r.id);
46277
46863
  if (!f) continue;
46278
46864
  const [[x0, y0], [x1, y1]] = path.bounds(f);
46279
- if ((x1 - x0) * (y1 - y0) < TINY_REGION_AREA) continue;
46280
- const c = path.centroid(f);
46281
- if (!Number.isFinite(c[0])) continue;
46282
46865
  const text = regionLabelMode === "abbrev" ? r.id.replace(/^US-/, "") : r.label;
46283
- labels.push({
46284
- x: c[0],
46285
- y: c[1],
46866
+ if (labelW(text) > x1 - x0 || labelH > y1 - y0) continue;
46867
+ const anchor = r.layer !== "us-state" ? WORLD_LABEL_ANCHORS[r.id] : void 0;
46868
+ const c = anchor ? project(anchor[0], anchor[1]) : path.centroid(f);
46869
+ if (!c || !Number.isFinite(c[0])) continue;
46870
+ pushRegionLabel(c[0], c[1], text, r.fill, r.lineNumber);
46871
+ }
46872
+ for (const seed of insetLabelSeeds) {
46873
+ const text = regionLabelMode === "abbrev" ? seed.iso.replace(/^US-/, "") : seed.name;
46874
+ const src = regionById.get(seed.iso);
46875
+ pushRegionLabel(
46876
+ seed.x,
46877
+ seed.y,
46286
46878
  text,
46287
- anchor: "middle",
46288
- color: contrastText(
46289
- r.fill,
46290
- palette.textOnFillLight,
46291
- palette.textOnFillDark
46292
- ),
46293
- halo: true,
46294
- lineNumber: r.lineNumber
46295
- });
46879
+ src ? regionFill(src) : neutralFill,
46880
+ seed.lineNumber
46881
+ );
46296
46882
  }
46297
46883
  }
46298
46884
  const poiLabelMode = resolved.directives.poiLabels ?? "auto";
@@ -46305,68 +46891,106 @@ function layoutMap(resolved, data, size, opts) {
46305
46891
  const src = poiById.get(p.id);
46306
46892
  return src?.label ?? src?.name ?? p.id;
46307
46893
  };
46308
- let pinCounter = 0;
46309
- for (const p of ordered) {
46894
+ const poiLabH = FONT * 1.25;
46895
+ const labelInfo = (p) => {
46310
46896
  const text = labelText(p);
46311
- const w = measureLegendText(text, FONT);
46312
- const h = FONT * 1.25;
46313
- const inline = { x: p.cx + p.r + 3, y: p.cy - h / 2, w, h };
46314
- if (!collides(inline)) {
46315
- obstacles.push(inline);
46897
+ return { text, w: measureLegendText(text, FONT) };
46898
+ };
46899
+ const pushInline = (p, text, w, side) => {
46900
+ const tx = side === "right" ? p.cx + p.r + 3 : p.cx - p.r - 3;
46901
+ obstacles.push({
46902
+ x: side === "right" ? tx : tx - w,
46903
+ y: p.cy - poiLabH / 2,
46904
+ w,
46905
+ h: poiLabH
46906
+ });
46907
+ labels.push({
46908
+ x: tx,
46909
+ y: p.cy + FONT / 3,
46910
+ text,
46911
+ anchor: side === "right" ? "start" : "end",
46912
+ color: palette.text,
46913
+ halo: true,
46914
+ haloColor: palette.bg,
46915
+ poiId: p.id,
46916
+ lineNumber: p.lineNumber
46917
+ });
46918
+ };
46919
+ const inlineFits = (p, w, side) => {
46920
+ const tx = side === "right" ? p.cx + p.r + 3 : p.cx - p.r - 3;
46921
+ const rect = {
46922
+ x: side === "right" ? tx : tx - w,
46923
+ y: p.cy - poiLabH / 2,
46924
+ w,
46925
+ h: poiLabH
46926
+ };
46927
+ return rect.x >= 0 && rect.x + rect.w <= width && !collides(rect);
46928
+ };
46929
+ const GROUP_R = 30;
46930
+ const groups = [];
46931
+ for (const p of ordered) {
46932
+ const near = groups.find(
46933
+ (g) => g.some((q) => Math.hypot(q.cx - p.cx, q.cy - p.cy) < GROUP_R)
46934
+ );
46935
+ if (near) near.push(p);
46936
+ else groups.push([p]);
46937
+ }
46938
+ const ROW_GAP2 = 3;
46939
+ const step = poiLabH + ROW_GAP2;
46940
+ const COL_GAP = 16;
46941
+ const placeColumn = (group) => {
46942
+ const items = group.map((p) => ({ p, ...labelInfo(p) })).sort((a, b) => a.p.cy - b.p.cy || (a.text < b.text ? -1 : 1));
46943
+ const left = Math.min(...items.map((o) => o.p.cx - o.p.r));
46944
+ const right = Math.max(...items.map((o) => o.p.cx + o.p.r));
46945
+ const cyMid = (Math.min(...items.map((o) => o.p.cy)) + Math.max(...items.map((o) => o.p.cy))) / 2;
46946
+ const maxW = Math.max(...items.map((o) => o.w));
46947
+ const side = right + COL_GAP + maxW <= width - 2 ? "right" : "left";
46948
+ const colX = side === "right" ? right + COL_GAP : left - COL_GAP;
46949
+ const totalH = items.length * step;
46950
+ let startY = cyMid - totalH / 2;
46951
+ startY = Math.max(2, Math.min(startY, height - totalH - 2));
46952
+ items.forEach((o, i) => {
46953
+ const rowCy = startY + i * step + step / 2;
46954
+ obstacles.push({
46955
+ x: side === "right" ? colX : colX - o.w,
46956
+ y: rowCy - poiLabH / 2,
46957
+ w: o.w,
46958
+ h: poiLabH
46959
+ });
46316
46960
  labels.push({
46317
- x: inline.x,
46318
- y: p.cy + FONT / 3,
46319
- text,
46320
- anchor: "start",
46961
+ x: colX,
46962
+ y: rowCy + FONT / 3,
46963
+ text: o.text,
46964
+ anchor: side === "right" ? "start" : "end",
46321
46965
  color: palette.text,
46322
46966
  halo: true,
46323
- lineNumber: p.lineNumber
46967
+ haloColor: palette.bg,
46968
+ leader: {
46969
+ x1: o.p.cx,
46970
+ y1: o.p.cy,
46971
+ x2: side === "right" ? colX - 2 : colX + 2,
46972
+ y2: rowCy
46973
+ },
46974
+ leaderColor: o.p.fill,
46975
+ poiId: o.p.id,
46976
+ lineNumber: o.p.lineNumber
46324
46977
  });
46325
- continue;
46326
- }
46327
- let placed = false;
46328
- for (let k = 1; k <= 2 && !placed; k++) {
46329
- for (const [dx, dy] of RING_DIRS) {
46330
- const cx = p.cx + dx * LEADER_STEP * k;
46331
- const cy = p.cy + dy * LEADER_STEP * k;
46332
- const rect = {
46333
- x: dx >= 0 ? cx : cx - w,
46334
- y: cy - h / 2,
46335
- w,
46336
- h
46337
- };
46338
- if (rect.x < 0 || rect.x + rect.w > width || rect.y < 0 || rect.y + rect.h > height) {
46339
- continue;
46340
- }
46341
- if (collides(rect)) continue;
46342
- obstacles.push(rect);
46343
- labels.push({
46344
- x: cx,
46345
- y: cy + FONT / 3,
46346
- text,
46347
- anchor: dx >= 0 ? "start" : "end",
46348
- color: palette.text,
46349
- halo: true,
46350
- leader: { x1: p.cx, y1: p.cy, x2: cx, y2: cy },
46351
- lineNumber: p.lineNumber
46352
- });
46353
- placed = true;
46354
- break;
46978
+ });
46979
+ };
46980
+ for (const g of groups) {
46981
+ if (g.length === 1) {
46982
+ const p = g[0];
46983
+ const { text, w } = labelInfo(p);
46984
+ if (inlineFits(p, w, "right")) {
46985
+ pushInline(p, text, w, "right");
46986
+ continue;
46987
+ }
46988
+ if (inlineFits(p, w, "left")) {
46989
+ pushInline(p, text, w, "left");
46990
+ continue;
46355
46991
  }
46356
46992
  }
46357
- if (placed) continue;
46358
- pinCounter += 1;
46359
- pinList.push({ pin: pinCounter, label: text });
46360
- labels.push({
46361
- x: p.cx + p.r + 2,
46362
- y: p.cy - p.r,
46363
- text: String(pinCounter),
46364
- anchor: "start",
46365
- color: palette.text,
46366
- halo: true,
46367
- pin: pinCounter,
46368
- lineNumber: p.lineNumber
46369
- });
46993
+ placeColumn(g);
46370
46994
  }
46371
46995
  }
46372
46996
  let legend = null;
@@ -46375,8 +46999,7 @@ function layoutMap(resolved, data, size, opts) {
46375
46999
  name: g.name,
46376
47000
  entries: g.entries.map((e) => ({ value: e.value, color: e.color }))
46377
47001
  }));
46378
- const hasAnything = tagGroups.length > 0 || hasRamp || sizeVals.length > 0 || weightVals.length > 0;
46379
- if (hasAnything) {
47002
+ if (tagGroups.length > 0 || hasRamp) {
46380
47003
  legend = {
46381
47004
  tagGroups,
46382
47005
  activeGroup,
@@ -46387,20 +47010,9 @@ function layoutMap(resolved, data, size, opts) {
46387
47010
  },
46388
47011
  min: rampMin,
46389
47012
  max: rampMax,
46390
- hue: rampHue
46391
- }
46392
- },
46393
- ...sizeVals.length > 0 && {
46394
- size: {
46395
- ...resolved.directives.sizeMetric !== void 0 && {
46396
- metric: resolved.directives.sizeMetric
46397
- },
46398
- min: sizeMin,
46399
- max: sizeMax
47013
+ hue: rampHue,
47014
+ base: rampBase
46400
47015
  }
46401
- },
46402
- ...weightVals.length > 0 && {
46403
- weight: { min: wMin, max: wMax }
46404
47016
  }
46405
47017
  };
46406
47018
  }
@@ -46408,26 +47020,28 @@ function layoutMap(resolved, data, size, opts) {
46408
47020
  return {
46409
47021
  width,
46410
47022
  height,
46411
- background: palette.bg,
47023
+ background: water,
46412
47024
  title: resolved.title,
46413
47025
  ...resolved.subtitle !== void 0 && { subtitle: resolved.subtitle },
46414
47026
  ...resolved.caption !== void 0 && { caption: resolved.caption },
46415
47027
  regions,
47028
+ rivers,
46416
47029
  legs,
46417
47030
  pois,
46418
47031
  labels,
46419
- pinList,
46420
- legend
47032
+ legend,
47033
+ insets,
47034
+ insetRegions
46421
47035
  };
46422
47036
  }
46423
- var FIT_PAD, RAMP_FLOOR, R_DEFAULT, R_MIN, R_MAX, W_MIN, W_MAX, FONT, LEADER_STEP, COLO_EPS, COLO_R, GOLDEN_ANGLE, FAN_STEP, TINY_REGION_AREA, ARC_CURVE_FRAC, RING_DIRS;
47037
+ 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, RIVER_WIDTH, FOREIGN_TINT_LIGHT, FOREIGN_TINT_DARK, COLO_R, GOLDEN_ANGLE, FAN_STEP, ARC_CURVE_FRAC, usConusProjection, alaskaProjection, hawaiiProjection, INSET_STATES, US_NON_CONUS;
46424
47038
  var init_layout15 = __esm({
46425
47039
  "src/map/layout.ts"() {
46426
47040
  "use strict";
46427
47041
  init_color_utils();
46428
- init_tag_groups();
46429
47042
  init_label_layout();
46430
47043
  init_legend_constants();
47044
+ init_title_constants();
46431
47045
  FIT_PAD = 24;
46432
47046
  RAMP_FLOOR = 15;
46433
47047
  R_DEFAULT = 6;
@@ -46436,23 +47050,32 @@ var init_layout15 = __esm({
46436
47050
  W_MIN = 1.25;
46437
47051
  W_MAX = 8;
46438
47052
  FONT = 11;
46439
- LEADER_STEP = 14;
46440
47053
  COLO_EPS = 1.5;
47054
+ LAND_TINT_LIGHT = 58;
47055
+ LAND_TINT_DARK = 75;
47056
+ TAG_TINT_LIGHT = 60;
47057
+ TAG_TINT_DARK = 68;
47058
+ WATER_TINT = 55;
47059
+ RIVER_WIDTH = 1.3;
47060
+ FOREIGN_TINT_LIGHT = 30;
47061
+ FOREIGN_TINT_DARK = 62;
46441
47062
  COLO_R = 9;
46442
47063
  GOLDEN_ANGLE = 2.399963229728653;
46443
47064
  FAN_STEP = 16;
46444
- TINY_REGION_AREA = 600;
46445
47065
  ARC_CURVE_FRAC = 0.18;
46446
- RING_DIRS = [
46447
- [1, 0],
46448
- [0, 1],
46449
- [-1, 0],
46450
- [0, -1],
46451
- [1, 1],
46452
- [-1, 1],
46453
- [-1, -1],
46454
- [1, -1]
46455
- ];
47066
+ usConusProjection = () => geoConicEqualArea().parallels([29.5, 45.5]).rotate([96, 0]);
47067
+ alaskaProjection = () => geoConicEqualArea().rotate([154, 0]).center([-2, 58.5]).parallels([55, 65]);
47068
+ hawaiiProjection = () => geoMercator();
47069
+ INSET_STATES = /* @__PURE__ */ new Set(["US-AK", "US-HI"]);
47070
+ US_NON_CONUS = /* @__PURE__ */ new Set([
47071
+ "US-AK",
47072
+ "US-HI",
47073
+ "US-AS",
47074
+ "US-GU",
47075
+ "US-MP",
47076
+ "US-PR",
47077
+ "US-VI"
47078
+ ]);
46456
47079
  }
46457
47080
  });
46458
47081
 
@@ -46463,7 +47086,7 @@ __export(renderer_exports16, {
46463
47086
  renderMapForExport: () => renderMapForExport
46464
47087
  });
46465
47088
  import * as d3Selection18 from "d3-selection";
46466
- function renderMap(container, resolved, data, palette, isDark, onClickItem, exportDims) {
47089
+ function renderMap(container, resolved, data, palette, isDark, onClickItem, exportDims, activeGroupOverride) {
46467
47090
  d3Selection18.select(container).selectAll(":not([data-d3-tooltip])").remove();
46468
47091
  const width = exportDims?.width ?? container.clientWidth;
46469
47092
  const height = exportDims?.height ?? container.clientHeight;
@@ -46474,27 +47097,29 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46474
47097
  { width, height },
46475
47098
  {
46476
47099
  palette,
46477
- isDark
47100
+ isDark,
47101
+ ...activeGroupOverride !== void 0 && {
47102
+ activeGroup: activeGroupOverride
47103
+ }
46478
47104
  }
46479
47105
  );
46480
- const svg = d3Selection18.select(container).append("svg").attr("width", width).attr("height", height).attr("viewBox", `0 0 ${width} ${height}`).attr("preserveAspectRatio", "xMidYMin meet").attr("xmlns", "http://www.w3.org/2000/svg").style("font-family", FONT_FAMILY);
47106
+ const svg = d3Selection18.select(container).append("svg").attr("width", width).attr("height", height).attr("viewBox", `0 0 ${width} ${height}`).attr("preserveAspectRatio", "xMidYMin meet").attr("xmlns", "http://www.w3.org/2000/svg").style("font-family", FONT_FAMILY).style("background", layout.background);
46481
47107
  svg.append("rect").attr("width", width).attr("height", height).attr("fill", layout.background);
46482
- const arrowColor = mix(palette.text, palette.bg, 50);
46483
47108
  const defs = svg.append("defs");
46484
- defs.append("marker").attr("id", "dgmo-map-arrow").attr("viewBox", "0 0 10 10").attr("refX", 9).attr("refY", 5).attr("markerWidth", 7).attr("markerHeight", 7).attr("orient", "auto-start-reverse").append("path").attr("d", "M0,0L10,5L0,10z").attr("fill", arrowColor);
46485
- const haloColor = layout.background;
46486
- if (layout.title) {
46487
- svg.append("text").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).text(layout.title);
46488
- }
46489
- if (layout.subtitle) {
46490
- 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).text(layout.subtitle);
46491
- }
46492
- if (layout.caption) {
46493
- svg.append("text").attr("x", width / 2).attr("y", height - 8).attr("text-anchor", "middle").attr("font-size", LABEL_FONT).attr("fill", palette.textMuted).text(layout.caption);
46494
- }
47109
+ const arrowSize = (w) => Math.min(15, 7 + w * 0.95);
47110
+ const haloColor = palette.bg;
46495
47111
  const gRegions = svg.append("g").attr("class", "dgmo-map-regions");
46496
- for (const r of layout.regions) {
46497
- const p = gRegions.append("path").attr("d", r.d).attr("fill", r.fill).attr("stroke", r.stroke).attr("stroke-width", 0.5);
47112
+ const drawRegion = (g, r, strokeWidth) => {
47113
+ const p = g.append("path").attr("d", r.d).attr("fill", r.fill).attr("stroke", r.stroke).attr("stroke-width", strokeWidth);
47114
+ if (r.layer !== "base") {
47115
+ p.classed("dgmo-map-region", true).attr("data-region", r.id);
47116
+ if (r.score !== void 0) p.attr("data-score", r.score);
47117
+ if (r.tags) {
47118
+ for (const [group, value] of Object.entries(r.tags)) {
47119
+ p.attr(`data-tag-${group.toLowerCase()}`, value.toLowerCase());
47120
+ }
47121
+ }
47122
+ }
46498
47123
  if (r.lineNumber >= 0) {
46499
47124
  p.attr("data-line-number", r.lineNumber);
46500
47125
  if (onClickItem) {
@@ -46504,11 +47129,31 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46504
47129
  );
46505
47130
  }
46506
47131
  }
47132
+ };
47133
+ for (const r of layout.regions) drawRegion(gRegions, r, 0.5);
47134
+ if (layout.rivers.length) {
47135
+ const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none");
47136
+ for (const r of layout.rivers) {
47137
+ gRivers.append("path").attr("d", r.d).attr("stroke", r.color).attr("stroke-width", r.width).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
47138
+ }
47139
+ }
47140
+ if (layout.insets.length) {
47141
+ const insetG = svg.append("g").attr("class", "dgmo-map-insets");
47142
+ for (const box of layout.insets) {
47143
+ const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
47144
+ 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");
47145
+ }
47146
+ for (const r of layout.insetRegions) drawRegion(insetG, r, 0.5);
46507
47147
  }
46508
47148
  const gLegs = svg.append("g").attr("class", "dgmo-map-legs").attr("fill", "none");
46509
- for (const leg of layout.legs) {
47149
+ layout.legs.forEach((leg, i) => {
46510
47150
  const p = gLegs.append("path").attr("d", leg.d).attr("stroke", leg.color).attr("stroke-width", leg.width).attr("stroke-linecap", "round");
46511
- if (leg.arrow) p.attr("marker-end", "url(#dgmo-map-arrow)");
47151
+ if (leg.arrow) {
47152
+ const id = `dgmo-map-arrow-${i}`;
47153
+ const s = arrowSize(leg.width);
47154
+ defs.append("marker").attr("id", id).attr("viewBox", "0 0 10 10").attr("refX", 10).attr("refY", 5).attr("markerUnits", "userSpaceOnUse").attr("markerWidth", s).attr("markerHeight", s).attr("orient", "auto-start-reverse").append("path").attr("d", "M0,0L10,5L0,10z").attr("fill", leg.color);
47155
+ p.attr("marker-end", `url(#${id})`);
47156
+ }
46512
47157
  if (leg.label !== void 0 && leg.labelX !== void 0) {
46513
47158
  emitText(
46514
47159
  gLegs,
@@ -46522,13 +47167,13 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46522
47167
  LABEL_FONT - 1
46523
47168
  );
46524
47169
  }
46525
- }
47170
+ });
46526
47171
  const gPois = svg.append("g").attr("class", "dgmo-map-pois");
46527
47172
  for (const poi of layout.pois) {
46528
47173
  if (poi.isOrigin) {
46529
47174
  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);
46530
47175
  }
46531
- 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);
47176
+ 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);
46532
47177
  if (onClickItem) {
46533
47178
  c.style("cursor", "pointer").on(
46534
47179
  "click",
@@ -46552,48 +47197,66 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46552
47197
  const gLabels = svg.append("g").attr("class", "dgmo-map-labels");
46553
47198
  for (const lab of layout.labels) {
46554
47199
  if (lab.leader) {
46555
- gLabels.append("line").attr("x1", lab.leader.x1).attr("y1", lab.leader.y1).attr("x2", lab.leader.x2).attr("y2", lab.leader.y2).attr("stroke", mix(palette.textMuted, palette.bg, 60)).attr("stroke-width", 0.75);
47200
+ 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(
47201
+ "stroke",
47202
+ lab.leaderColor ?? mix(palette.textMuted, palette.bg, 60)
47203
+ ).attr("stroke-width", lab.leaderColor ? 1 : 0.75);
47204
+ if (lab.poiId !== void 0) line12.attr("data-poi", lab.poiId);
46556
47205
  }
46557
- if (lab.pin !== void 0) {
46558
- gLabels.append("rect").attr("x", lab.x - 1).attr("y", lab.y - LABEL_FONT).attr("width", LABEL_FONT * 1.3).attr("height", LABEL_FONT * 1.3).attr("rx", 2).attr("fill", palette.surface).attr("stroke", palette.border);
46559
- }
46560
- emitText(
47206
+ const t = emitText(
46561
47207
  gLabels,
46562
47208
  lab.x,
46563
47209
  lab.y,
46564
47210
  lab.text,
46565
47211
  lab.anchor,
46566
47212
  lab.color,
46567
- haloColor,
47213
+ lab.haloColor,
46568
47214
  lab.halo,
46569
47215
  LABEL_FONT
46570
47216
  );
46571
- }
46572
- if (layout.pinList.length > 0) {
46573
- const gPins = svg.append("g").attr("class", "dgmo-map-pin-list").attr(
46574
- "transform",
46575
- `translate(12, ${height - layout.pinList.length * 14 - 8})`
46576
- );
46577
- layout.pinList.forEach((entry, i) => {
46578
- gPins.append("text").attr("x", 0).attr("y", i * 14).attr("font-size", LABEL_FONT - 1).attr("fill", palette.textMuted).text(`${entry.pin} \u2014 ${entry.label}`);
46579
- });
47217
+ if (lab.poiId !== void 0) {
47218
+ t.attr("data-poi", lab.poiId).style("cursor", "default");
47219
+ }
46580
47220
  }
46581
47221
  if (layout.legend) {
46582
47222
  const legendY = (layout.title ? TITLE_Y + TITLE_FONT_SIZE : 0) + (layout.subtitle ? TITLE_FONT_SIZE : 0) + 8;
46583
47223
  const legendG = svg.append("g").attr("class", "dgmo-map-legend").attr("transform", `translate(0, ${legendY})`);
46584
- const groups = layout.legend.tagGroups.filter((g) => g.entries.length > 0);
47224
+ const ramp = layout.legend.ramp;
47225
+ const scoreGroup = ramp ? {
47226
+ name: ramp.metric?.trim() || "Score",
47227
+ entries: [],
47228
+ gradient: {
47229
+ min: ramp.min,
47230
+ max: ramp.max,
47231
+ hue: ramp.hue,
47232
+ base: ramp.base
47233
+ }
47234
+ } : null;
47235
+ const tagGroups = layout.legend.tagGroups.filter((g) => g.entries.length > 0).map((g) => ({ name: g.name, entries: [...g.entries] }));
47236
+ const groups = [...scoreGroup ? [scoreGroup] : [], ...tagGroups];
46585
47237
  if (groups.length > 0) {
46586
47238
  const config = {
46587
- groups: groups.map((g) => ({ name: g.name, entries: [...g.entries] })),
47239
+ groups,
46588
47240
  position: { placement: "top-center", titleRelation: "below-title" },
46589
47241
  mode: exportDims ? "export" : "preview",
46590
- showEmptyGroups: false
47242
+ showEmptyGroups: false,
47243
+ // Keep inactive siblings visible as pills so the user can click to flip
47244
+ // the active colouring dimension (preview only — export shows just the
47245
+ // active group).
47246
+ showInactivePills: true
46591
47247
  };
46592
47248
  const state = { activeGroup: layout.legend.activeGroup };
46593
47249
  renderLegendD3(legendG, config, state, palette, isDark, void 0, width);
46594
47250
  }
46595
- const pinGap = layout.pinList.length ? layout.pinList.length * 14 + 14 : 0;
46596
- emitExtraLegend(svg, layout, palette, height, pinGap);
47251
+ }
47252
+ if (layout.title) {
47253
+ svg.append("text").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);
47254
+ }
47255
+ if (layout.subtitle) {
47256
+ 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);
47257
+ }
47258
+ if (layout.caption) {
47259
+ 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);
46597
47260
  }
46598
47261
  }
46599
47262
  function renderMapForExport(container, resolved, data, palette, isDark, exportDims) {
@@ -46604,54 +47267,7 @@ function emitText(g, x, y, text, anchor, color, halo, withHalo, fontSize) {
46604
47267
  if (withHalo) {
46605
47268
  t.attr("paint-order", "stroke fill").attr("stroke", halo).attr("stroke-width", 3).attr("stroke-linejoin", "round").attr("stroke-opacity", 0.7);
46606
47269
  }
46607
- }
46608
- function emitExtraLegend(svg, layout, palette, height, bottomGap) {
46609
- const { legend } = layout;
46610
- if (!legend) return;
46611
- if (!legend.ramp && !legend.size && !legend.weight) return;
46612
- const blocks = [];
46613
- const g = svg.append("g").attr("class", "dgmo-map-legend-keys").attr("transform", `translate(12, ${height - 56 - bottomGap})`);
46614
- let xCursor = 0;
46615
- if (legend.ramp) {
46616
- const ramp = legend.ramp;
46617
- blocks.push(() => {
46618
- const block = g.append("g").attr("transform", `translate(${xCursor},0)`);
46619
- const gradId = "dgmo-map-ramp";
46620
- const grad = block.append("defs").append("linearGradient").attr("id", gradId).attr("x1", "0%").attr("x2", "100%");
46621
- grad.append("stop").attr("offset", "0%").attr("stop-color", mix(ramp.hue, palette.bg, 15));
46622
- grad.append("stop").attr("offset", "100%").attr("stop-color", ramp.hue);
46623
- block.append("rect").attr("width", 80).attr("height", 8).attr("fill", `url(#${gradId})`);
46624
- block.append("text").attr("x", 0).attr("y", 22).attr("font-size", 9).attr("fill", palette.textMuted).text(String(ramp.min));
46625
- block.append("text").attr("x", 80).attr("y", 22).attr("text-anchor", "end").attr("font-size", 9).attr("fill", palette.textMuted).text(String(ramp.max));
46626
- if (ramp.metric) {
46627
- block.append("text").attr("x", 0).attr("y", -4).attr("font-size", 9).attr("fill", palette.textMuted).text(ramp.metric);
46628
- }
46629
- xCursor += 110;
46630
- });
46631
- }
46632
- if (legend.size) {
46633
- const sz = legend.size;
46634
- blocks.push(() => {
46635
- const block = g.append("g").attr("transform", `translate(${xCursor},0)`);
46636
- [3, 6, 10].forEach((r, i) => {
46637
- block.append("circle").attr("cx", i * 26 + r).attr("cy", 8).attr("r", r).attr("fill", "none").attr("stroke", palette.textMuted);
46638
- });
46639
- block.append("text").attr("x", 0).attr("y", -4).attr("font-size", 9).attr("fill", palette.textMuted).text(sz.metric ?? "size");
46640
- xCursor += 110;
46641
- });
46642
- }
46643
- if (legend.weight) {
46644
- const wt = legend.weight;
46645
- blocks.push(() => {
46646
- const block = g.append("g").attr("transform", `translate(${xCursor},0)`);
46647
- [1, 3, 6].forEach((w, i) => {
46648
- block.append("line").attr("x1", i * 26).attr("y1", 8).attr("x2", i * 26 + 20).attr("y2", 8).attr("stroke", palette.textMuted).attr("stroke-width", w);
46649
- });
46650
- block.append("text").attr("x", 0).attr("y", -4).attr("font-size", 9).attr("fill", palette.textMuted).text(wt.metric ?? "weight");
46651
- xCursor += 110;
46652
- });
46653
- }
46654
- for (const draw of blocks) draw();
47270
+ return t;
46655
47271
  }
46656
47272
  var LABEL_FONT;
46657
47273
  var init_renderer16 = __esm({
@@ -53123,18 +53739,18 @@ function getRotateFn(mode) {
53123
53739
  return () => 0;
53124
53740
  }
53125
53741
  function renderWordCloudAsync(container, parsed, palette, _isDark, exportDims) {
53126
- return new Promise((resolve2) => {
53742
+ return new Promise((resolve) => {
53127
53743
  d3Selection23.select(container).selectAll(":not([data-d3-tooltip])").remove();
53128
53744
  const { words, cloudOptions } = parsed;
53129
53745
  const title = parsed.noTitle ? null : parsed.title;
53130
53746
  if (words.length === 0) {
53131
- resolve2();
53747
+ resolve();
53132
53748
  return;
53133
53749
  }
53134
53750
  const width = exportDims?.width ?? container.clientWidth;
53135
53751
  const height = exportDims?.height ?? container.clientHeight;
53136
53752
  if (width <= 0 || height <= 0) {
53137
- resolve2();
53753
+ resolve();
53138
53754
  return;
53139
53755
  }
53140
53756
  const titleHeight = title ? 40 : 0;
@@ -53163,7 +53779,7 @@ function renderWordCloudAsync(container, parsed, palette, _isDark, exportDims) {
53163
53779
  "transform",
53164
53780
  (d) => `translate(${d.x},${d.y}) rotate(${d.rotate})`
53165
53781
  ).text((d) => d.text);
53166
- resolve2();
53782
+ resolve();
53167
53783
  }).start();
53168
53784
  });
53169
53785
  }