@diagrammo/dgmo 0.19.0 → 0.20.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 (48) hide show
  1. package/dist/advanced.cjs +919 -298
  2. package/dist/advanced.d.cts +148 -54
  3. package/dist/advanced.d.ts +148 -54
  4. package/dist/advanced.js +922 -300
  5. package/dist/auto.cjs +904 -297
  6. package/dist/auto.js +117 -117
  7. package/dist/auto.mjs +909 -299
  8. package/dist/cli.cjs +159 -159
  9. package/dist/index.cjs +903 -296
  10. package/dist/index.js +908 -298
  11. package/dist/internal.cjs +919 -298
  12. package/dist/internal.d.cts +148 -54
  13. package/dist/internal.d.ts +148 -54
  14. package/dist/internal.js +922 -300
  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 +6 -2
  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 +29 -2
  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.cjs CHANGED
@@ -53,6 +53,33 @@ function rectCircleOverlap(rect, circle) {
53
53
  const dy = nearestY - circle.cy;
54
54
  return dx * dx + dy * dy < circle.r * circle.r;
55
55
  }
56
+ function segmentRectOverlap(x0, y0, x1, y1, rect) {
57
+ const dx = x1 - x0;
58
+ const dy = y1 - y0;
59
+ let t0 = 0;
60
+ let t1 = 1;
61
+ const edges = [
62
+ [-dx, x0 - rect.x],
63
+ [dx, rect.x + rect.w - x0],
64
+ [-dy, y0 - rect.y],
65
+ [dy, rect.y + rect.h - y0]
66
+ ];
67
+ for (const [p, q] of edges) {
68
+ if (p === 0) {
69
+ if (q < 0) return false;
70
+ } else {
71
+ const t = q / p;
72
+ if (p < 0) {
73
+ if (t > t1) return false;
74
+ if (t > t0) t0 = t;
75
+ } else {
76
+ if (t < t0) return false;
77
+ if (t < t1) t1 = t;
78
+ }
79
+ }
80
+ }
81
+ return true;
82
+ }
56
83
  function computeQuadrantPointLabels(points, chartBounds, obstacles, pointRadius, fontSize) {
57
84
  const labelHeight = fontSize + 4;
58
85
  const stepSize = labelHeight + 2;
@@ -3256,6 +3283,57 @@ var init_legend_constants = __esm({
3256
3283
  });
3257
3284
 
3258
3285
  // src/utils/legend-layout.ts
3286
+ function fmtRamp(n) {
3287
+ return Number.isInteger(n) ? String(n) : String(Math.round(n * 10) / 10);
3288
+ }
3289
+ function gradientCapsuleWidth(name, gradient) {
3290
+ const pw = pillWidth(name);
3291
+ const minW = measureLegendText(fmtRamp(gradient.min), LEGEND_ENTRY_FONT_SIZE);
3292
+ const maxW = measureLegendText(fmtRamp(gradient.max), LEGEND_ENTRY_FONT_SIZE);
3293
+ return LEGEND_CAPSULE_PAD + pw + 4 + minW + RAMP_LABEL_GAP + RAMP_LEGEND_W + RAMP_LABEL_GAP + maxW + LEGEND_CAPSULE_PAD;
3294
+ }
3295
+ function buildGradientCapsuleLayout(group, gradient) {
3296
+ const pw = pillWidth(group.name);
3297
+ const minText = fmtRamp(gradient.min);
3298
+ const maxText = fmtRamp(gradient.max);
3299
+ const minW = measureLegendText(minText, LEGEND_ENTRY_FONT_SIZE);
3300
+ const gx = LEGEND_CAPSULE_PAD + pw + 4;
3301
+ const minX = gx;
3302
+ const rampX = gx + minW + RAMP_LABEL_GAP;
3303
+ const maxX = rampX + RAMP_LEGEND_W + RAMP_LABEL_GAP;
3304
+ const width = gradientCapsuleWidth(group.name, gradient);
3305
+ return {
3306
+ groupName: group.name,
3307
+ x: 0,
3308
+ y: 0,
3309
+ width,
3310
+ height: LEGEND_HEIGHT,
3311
+ pill: {
3312
+ groupName: group.name,
3313
+ x: LEGEND_CAPSULE_PAD,
3314
+ y: LEGEND_CAPSULE_PAD,
3315
+ width: pw,
3316
+ height: LEGEND_HEIGHT - LEGEND_CAPSULE_PAD * 2,
3317
+ isActive: true
3318
+ },
3319
+ entries: [],
3320
+ gradient: {
3321
+ rampX,
3322
+ rampY: (LEGEND_HEIGHT - RAMP_LEGEND_H) / 2,
3323
+ rampW: RAMP_LEGEND_W,
3324
+ rampH: RAMP_LEGEND_H,
3325
+ min: gradient.min,
3326
+ max: gradient.max,
3327
+ minText,
3328
+ minX,
3329
+ maxText,
3330
+ maxX,
3331
+ textY: LEGEND_HEIGHT / 2,
3332
+ hue: gradient.hue,
3333
+ base: gradient.base
3334
+ }
3335
+ };
3336
+ }
3259
3337
  function pillWidth(name) {
3260
3338
  return measureLegendText(name, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD;
3261
3339
  }
@@ -3398,7 +3476,7 @@ function computeLegendLayout(config, state, containerWidth) {
3398
3476
  };
3399
3477
  }
3400
3478
  const controlsGroupLayout = isExport ? void 0 : buildControlsGroupLayout(config, state);
3401
- const visibleGroups = config.showEmptyGroups ? groups : groups.filter((g) => g.entries.length > 0);
3479
+ const visibleGroups = config.showEmptyGroups ? groups : groups.filter((g) => g.entries.length > 0 || !!g.gradient);
3402
3480
  if (visibleGroups.length === 0 && (!configControls || configControls.length === 0) && !controlsGroupLayout) {
3403
3481
  return {
3404
3482
  height: 0,
@@ -3477,7 +3555,7 @@ function computeLegendLayout(config, state, containerWidth) {
3477
3555
  groupAvailW,
3478
3556
  config.capsulePillAddonWidth ?? 0
3479
3557
  );
3480
- } else if (!activeGroupName) {
3558
+ } else if (!activeGroupName || config.showInactivePills) {
3481
3559
  const pw = pillWidth(g.name);
3482
3560
  pills.push({
3483
3561
  groupName: g.name,
@@ -3515,6 +3593,7 @@ function computeLegendLayout(config, state, containerWidth) {
3515
3593
  };
3516
3594
  }
3517
3595
  function buildCapsuleLayout(group, containerWidth, addonWidth = 0) {
3596
+ if (group.gradient) return buildGradientCapsuleLayout(group, group.gradient);
3518
3597
  const pw = pillWidth(group.name);
3519
3598
  const info = capsuleWidth(
3520
3599
  group.name,
@@ -3685,7 +3764,7 @@ function getMaxLegendReservedHeight(config, containerWidth) {
3685
3764
  }
3686
3765
  return max;
3687
3766
  }
3688
- var CONTROL_PILL_PAD, CONTROL_FONT_SIZE, CONTROL_ICON_GAP, CONTROL_GAP;
3767
+ var CONTROL_PILL_PAD, CONTROL_FONT_SIZE, CONTROL_ICON_GAP, CONTROL_GAP, RAMP_LEGEND_W, RAMP_LEGEND_H, RAMP_LABEL_GAP;
3689
3768
  var init_legend_layout = __esm({
3690
3769
  "src/utils/legend-layout.ts"() {
3691
3770
  "use strict";
@@ -3694,6 +3773,9 @@ var init_legend_layout = __esm({
3694
3773
  CONTROL_FONT_SIZE = 11;
3695
3774
  CONTROL_ICON_GAP = 4;
3696
3775
  CONTROL_GAP = 8;
3776
+ RAMP_LEGEND_W = 80;
3777
+ RAMP_LEGEND_H = 8;
3778
+ RAMP_LABEL_GAP = 6;
3697
3779
  }
3698
3780
  });
3699
3781
 
@@ -3781,6 +3863,16 @@ function renderCapsule(parent, capsule, palette, groupBg, pillBorder, _isDark, c
3781
3863
  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);
3782
3864
  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);
3783
3865
  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);
3866
+ if (capsule.gradient) {
3867
+ const gr = capsule.gradient;
3868
+ const gradId = `dgmo-legend-ramp-${capsule.groupName.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
3869
+ const def = g.append("defs").append("linearGradient").attr("id", gradId);
3870
+ def.append("stop").attr("offset", "0%").attr("stop-color", mix(gr.hue, gr.base, 15));
3871
+ def.append("stop").attr("offset", "100%").attr("stop-color", gr.hue);
3872
+ 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);
3873
+ 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})`);
3874
+ 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);
3875
+ }
3784
3876
  for (const entry of capsule.entries) {
3785
3877
  const entryG = g.append("g").attr("data-legend-entry", entry.value.toLowerCase()).attr("data-series-name", entry.value).style("cursor", "pointer");
3786
3878
  entryG.append("circle").attr("cx", entry.dotCx).attr("cy", entry.dotCy).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
@@ -11254,23 +11346,22 @@ function parseC4(content, palette) {
11254
11346
  }
11255
11347
  }
11256
11348
  const parent = findParentElement(indent, stack);
11257
- if (parent) {
11258
- const descResult = tryStripDescriptionKeyword(trimmed);
11349
+ const descResult = tryStripDescriptionKeyword(trimmed);
11350
+ if (parent && descResult.isKeyword) {
11259
11351
  if (descResult.needsColon) {
11260
11352
  pushError(
11261
11353
  lineNumber,
11262
- `Use "description: ${descResult.text}" \u2014 colon is required.`,
11354
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`,
11263
11355
  "warning"
11264
11356
  );
11265
11357
  }
11266
- const descText = descResult.isKeyword ? descResult.text : trimmed;
11267
11358
  let desc = elementDescription.get(parent.element);
11268
11359
  if (!desc) {
11269
11360
  desc = [];
11270
11361
  elementDescription.set(parent.element, desc);
11271
11362
  parent.element.description = desc;
11272
11363
  }
11273
- desc.push(descText);
11364
+ desc.push(descResult.text);
11274
11365
  } else {
11275
11366
  pushError(lineNumber, `Unexpected content: "${trimmed}"`);
11276
11367
  }
@@ -11737,7 +11828,7 @@ function parseSitemap(content, palette) {
11737
11828
  if (descResult.needsColon) {
11738
11829
  pushWarning(
11739
11830
  lineNumber,
11740
- `Use "description: ${descResult.text}" \u2014 colon is required.`
11831
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`
11741
11832
  );
11742
11833
  }
11743
11834
  const parent = findParentNode(indent, indentStack);
@@ -12565,23 +12656,22 @@ function parseInfra(content) {
12565
12656
  }
12566
12657
  }
12567
12658
  const descResult = tryStripDescriptionKeyword(trimmed);
12568
- if (descResult.isKeyword && currentNode.isEdge) {
12569
- continue;
12570
- }
12571
- if (!currentNode.isEdge) {
12659
+ if (descResult.isKeyword) {
12660
+ if (currentNode.isEdge) {
12661
+ continue;
12662
+ }
12572
12663
  if (descResult.needsColon) {
12573
12664
  warn(
12574
12665
  lineNumber,
12575
- `Use "description: ${descResult.text}" \u2014 colon is required.`
12666
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`
12576
12667
  );
12577
12668
  }
12578
- const descText = descResult.isKeyword ? descResult.text : trimmed;
12579
- pushDescription(currentNode, descText);
12669
+ pushDescription(currentNode, descResult.text);
12580
12670
  continue;
12581
12671
  }
12582
12672
  warn(
12583
12673
  lineNumber,
12584
- `Unexpected line inside component '${currentNode.label}'. Expected a property (key: value), connection (-> Target), or description text.`
12674
+ `Unexpected line inside component '${currentNode.label}'. Expected a property (key: value), connection (-> Target), or a description (description: text).`
12585
12675
  );
12586
12676
  continue;
12587
12677
  }
@@ -15718,9 +15808,6 @@ function parseMap(content) {
15718
15808
  const pushWarning = (line12, message) => {
15719
15809
  diagnostics.push(makeDgmoError(line12, message, "warning"));
15720
15810
  };
15721
- const pushInfo = (line12, message) => {
15722
- diagnostics.push(makeDgmoError(line12, message, "warning"));
15723
- };
15724
15811
  const lines = content.split("\n");
15725
15812
  let firstIdx = 0;
15726
15813
  while (firstIdx < lines.length) {
@@ -15850,10 +15937,15 @@ function parseMap(content) {
15850
15937
  break;
15851
15938
  case "projection":
15852
15939
  dup(d.projection);
15853
- if (value && !["natural-earth", "albers-usa", "mercator"].includes(value))
15940
+ if (value && ![
15941
+ "equirectangular",
15942
+ "natural-earth",
15943
+ "albers-usa",
15944
+ "mercator"
15945
+ ].includes(value))
15854
15946
  pushWarning(
15855
15947
  line12,
15856
- `Unknown projection "${value}" (expected natural-earth | albers-usa | mercator).`
15948
+ `Unknown projection "${value}" (expected equirectangular | natural-earth | albers-usa | mercator).`
15857
15949
  );
15858
15950
  d.projection = value;
15859
15951
  break;
@@ -15992,17 +16084,21 @@ function parseMap(content) {
15992
16084
  scoreNum = void 0;
15993
16085
  }
15994
16086
  }
15995
- if (scoreNum !== void 0 && Object.keys(tags).length)
15996
- pushInfo(
15997
- line12,
15998
- "A region has both `score:` and a tag value \u2014 v1 renders only the score (bivariate is a future seam)."
15999
- );
16087
+ let regionName = split.name;
16088
+ let regionScope;
16089
+ const rToks = regionName.split(/\s+/);
16090
+ const rLast = rToks[rToks.length - 1];
16091
+ if (rToks.length > 1 && SCOPE_RE.test(rLast)) {
16092
+ regionName = rToks.slice(0, -1).join(" ");
16093
+ regionScope = rLast;
16094
+ }
16000
16095
  const region = {
16001
- name: split.name,
16096
+ name: regionName,
16002
16097
  tags,
16003
16098
  meta,
16004
16099
  lineNumber: line12
16005
16100
  };
16101
+ if (regionScope !== void 0) region.scope = regionScope;
16006
16102
  if (scoreNum !== void 0) region.score = scoreNum;
16007
16103
  regions.push(region);
16008
16104
  }
@@ -17126,7 +17222,7 @@ function parseMindmap(content, palette) {
17126
17222
  if (descResult.needsColon) {
17127
17223
  pushWarning(
17128
17224
  lineNumber,
17129
- `Use "description: ${descResult.text}" \u2014 colon is required.`
17225
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`
17130
17226
  );
17131
17227
  }
17132
17228
  const parent = findMetadataParent2(indent, indentStack);
@@ -18914,7 +19010,7 @@ function parseJourneyMap(content, palette) {
18914
19010
  if (descResult.needsColon) {
18915
19011
  warn(
18916
19012
  lineNumber,
18917
- `Use "description: ${descResult.text}" \u2014 colon is required.`
19013
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`
18918
19014
  );
18919
19015
  }
18920
19016
  currentStep.description = descResult.text;
@@ -45503,7 +45599,9 @@ function resolveMap(parsed, data) {
45503
45599
  const usScoped = parsed.directives.region === "us-states" || parsed.directives.defaultCountry?.toUpperCase() === "US" || parsed.regions.some((r) => {
45504
45600
  const f = fold(r.name);
45505
45601
  return usStateIndex.has(f) && !countryIndex.has(f);
45506
- }) || parsed.pois.some(
45602
+ }) || parsed.regions.some(
45603
+ (r) => r.scope === "US" || r.scope?.startsWith("US-")
45604
+ ) || parsed.pois.some(
45507
45605
  (p) => p.pos.kind === "name" && p.pos.scope?.startsWith("US-")
45508
45606
  );
45509
45607
  const regions = [];
@@ -45515,7 +45613,30 @@ function resolveMap(parsed, data) {
45515
45613
  const inCountry = countryIndex.get(f) ?? (REGION_ALIASES[f] ? countryIndex.get(REGION_ALIASES[f]) : void 0);
45516
45614
  const inState = usStateIndex.get(f);
45517
45615
  let chosen = null;
45518
- if (inCountry && inState) {
45616
+ const scope = r.scope;
45617
+ if (scope) {
45618
+ const wantsState = scope === "US" || scope.startsWith("US-");
45619
+ if (wantsState && inState) {
45620
+ if (scope.startsWith("US-") && inState.id !== scope) {
45621
+ err(
45622
+ r.lineNumber,
45623
+ `No subdivision "${r.name}" in scope ${scope} (it is ${inState.id}).`,
45624
+ "E_MAP_SCOPE_MISS"
45625
+ );
45626
+ continue;
45627
+ }
45628
+ chosen = { ...inState, layer: "us-state" };
45629
+ } else if (!wantsState && inCountry) {
45630
+ chosen = { ...inCountry, layer: "country" };
45631
+ } else {
45632
+ err(
45633
+ r.lineNumber,
45634
+ `No region "${r.name}" found in scope ${scope}.`,
45635
+ "E_MAP_SCOPE_MISS"
45636
+ );
45637
+ continue;
45638
+ }
45639
+ } else if (inCountry && inState) {
45519
45640
  if (usScoped) {
45520
45641
  chosen = { ...inState, layer: "us-state" };
45521
45642
  } else {
@@ -45523,7 +45644,7 @@ function resolveMap(parsed, data) {
45523
45644
  }
45524
45645
  warn(
45525
45646
  r.lineNumber,
45526
- `"${r.name}" is both a country and a US state \u2014 resolved as ${chosen.layer} (${chosen.id}).`,
45647
+ `"${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}").`,
45527
45648
  "W_MAP_REGION_AMBIGUOUS"
45528
45649
  );
45529
45650
  } else if (inState) {
@@ -45695,9 +45816,17 @@ function resolveMap(parsed, data) {
45695
45816
  };
45696
45817
  registerPoi(id, poi, p.lineNumber);
45697
45818
  }
45819
+ const declaredByName = /* @__PURE__ */ new Map();
45820
+ for (const p of pois) {
45821
+ const fn = p.name ? fold(p.name) : void 0;
45822
+ if (fn && fn !== p.id && !declaredByName.has(fn))
45823
+ declaredByName.set(fn, p.id);
45824
+ }
45698
45825
  const resolveEndpoint2 = (ref, line12) => {
45699
45826
  const f = fold(ref);
45700
45827
  if (registry.has(f)) return f;
45828
+ const aliased = declaredByName.get(f);
45829
+ if (aliased) return aliased;
45701
45830
  const got = lookupName(ref, void 0, line12, inferredCountry, true);
45702
45831
  if (got.kind !== "ok") return null;
45703
45832
  noteCountry(got.iso);
@@ -45777,23 +45906,29 @@ function resolveMap(parsed, data) {
45777
45906
  [-180, -85],
45778
45907
  [180, 85]
45779
45908
  ];
45780
- const extent2 = unioned ? pad(unioned, PAD_FRACTION) : DEFAULT_EXTENT;
45909
+ let extent2 = unioned ? pad(unioned, PAD_FRACTION) : DEFAULT_EXTENT;
45781
45910
  const lonSpan = extent2[1][0] - extent2[0][0];
45782
45911
  const latSpan = extent2[1][1] - extent2[0][1];
45783
45912
  const span = Math.max(lonSpan, latSpan);
45784
45913
  const usDominant = (inferredCountry === "US" || subdivisions.includes("us-states")) && !regions.some((r) => r.layer === "country" && r.iso !== "US") && !anyNonUsPoi;
45785
45914
  let projection;
45786
45915
  const override = parsed.directives.projection;
45787
- if (override === "natural-earth" || override === "albers-usa" || override === "mercator") {
45916
+ if (override === "equirectangular" || override === "natural-earth" || override === "albers-usa" || override === "mercator") {
45788
45917
  projection = override;
45789
45918
  } else if (usDominant) {
45790
45919
  projection = "albers-usa";
45791
45920
  } else if (span > WORLD_SPAN) {
45792
- projection = "natural-earth";
45921
+ projection = "equirectangular";
45793
45922
  } else if (span < MERCATOR_MAX_SPAN) {
45794
45923
  projection = "mercator";
45795
45924
  } else {
45796
- projection = "natural-earth";
45925
+ projection = "equirectangular";
45926
+ }
45927
+ if (lonSpan >= 180) {
45928
+ extent2 = [
45929
+ [-180, Math.min(extent2[0][1], WORLD_LAT_SOUTH)],
45930
+ [180, Math.max(extent2[1][1], WORLD_LAT_NORTH)]
45931
+ ];
45797
45932
  }
45798
45933
  result.regions = regions;
45799
45934
  result.pois = pois;
@@ -45841,7 +45976,7 @@ function firstError(diags) {
45841
45976
  const e = diags.find((d) => d.severity === "error");
45842
45977
  return e ? formatDgmoError(e) : null;
45843
45978
  }
45844
- var WORLD_SPAN, MERCATOR_MAX_SPAN, PAD_FRACTION, REGION_ALIASES;
45979
+ var WORLD_SPAN, MERCATOR_MAX_SPAN, PAD_FRACTION, WORLD_LAT_SOUTH, WORLD_LAT_NORTH, REGION_ALIASES;
45845
45980
  var init_resolver2 = __esm({
45846
45981
  "src/map/resolver.ts"() {
45847
45982
  "use strict";
@@ -45850,6 +45985,8 @@ var init_resolver2 = __esm({
45850
45985
  WORLD_SPAN = 90;
45851
45986
  MERCATOR_MAX_SPAN = 25;
45852
45987
  PAD_FRACTION = 0.05;
45988
+ WORLD_LAT_SOUTH = -58;
45989
+ WORLD_LAT_NORTH = 78;
45853
45990
  REGION_ALIASES = {
45854
45991
  // Common everyday names → the Natural-Earth display name actually shipped.
45855
45992
  "united states": "united states of america",
@@ -45914,13 +46051,36 @@ function moduleBaseDir() {
45914
46051
  function loadMapData() {
45915
46052
  cache ??= (async () => {
45916
46053
  const dir = await firstExistingDir(moduleBaseDir());
45917
- const [worldCoarse, worldDetail, usStates, gazetteer] = await Promise.all([
46054
+ const [
46055
+ worldCoarse,
46056
+ worldDetail,
46057
+ usStates,
46058
+ lakes,
46059
+ rivers,
46060
+ naLand,
46061
+ naLakes,
46062
+ gazetteer
46063
+ ] = await Promise.all([
45918
46064
  readJson(dir, FILES.worldCoarse),
45919
46065
  readJson(dir, FILES.worldDetail),
45920
46066
  readJson(dir, FILES.usStates),
46067
+ // Lakes/rivers/NA assets are optional — older bundles may predate them.
46068
+ readJson(dir, FILES.lakes).catch(() => void 0),
46069
+ readJson(dir, FILES.rivers).catch(() => void 0),
46070
+ readJson(dir, FILES.naLand).catch(() => void 0),
46071
+ readJson(dir, FILES.naLakes).catch(() => void 0),
45921
46072
  readJson(dir, FILES.gazetteer)
45922
46073
  ]);
45923
- return validate({ worldCoarse, worldDetail, usStates, gazetteer });
46074
+ return validate({
46075
+ worldCoarse,
46076
+ worldDetail,
46077
+ usStates,
46078
+ gazetteer,
46079
+ ...lakes && { lakes },
46080
+ ...rivers && { rivers },
46081
+ ...naLand && { naLand },
46082
+ ...naLakes && { naLakes }
46083
+ });
45924
46084
  })().catch((e) => {
45925
46085
  cache = void 0;
45926
46086
  throw e;
@@ -45939,6 +46099,10 @@ var init_load_data = __esm({
45939
46099
  worldCoarse: "world-coarse.json",
45940
46100
  worldDetail: "world-detail.json",
45941
46101
  usStates: "us-states.json",
46102
+ lakes: "lakes.json",
46103
+ rivers: "rivers.json",
46104
+ naLand: "na-land.json",
46105
+ naLakes: "na-lakes.json",
45942
46106
  gazetteer: "gazetteer.json"
45943
46107
  };
45944
46108
  CANDIDATE_DIRS = [
@@ -45966,36 +46130,67 @@ function decodeLayer(topo) {
45966
46130
  function projectionFor(family) {
45967
46131
  switch (family) {
45968
46132
  case "albers-usa":
45969
- return (0, import_d3_geo2.geoAlbersUsa)();
46133
+ return usConusProjection();
45970
46134
  case "mercator":
45971
46135
  return (0, import_d3_geo2.geoMercator)();
45972
46136
  case "natural-earth":
45973
- default:
45974
46137
  return (0, import_d3_geo2.geoNaturalEarth1)();
46138
+ case "equirectangular":
46139
+ default:
46140
+ return (0, import_d3_geo2.geoEquirectangular)();
45975
46141
  }
45976
46142
  }
46143
+ function mapBackgroundColor(palette) {
46144
+ return mix(palette.colors.blue, palette.bg, WATER_TINT);
46145
+ }
45977
46146
  function layoutMap(resolved, data, size, opts) {
45978
46147
  const { palette, isDark } = opts;
45979
46148
  const { width, height } = size;
45980
- const worldTopo = resolved.basemaps.world === "detail" ? data.worldDetail : data.worldCoarse;
46149
+ const wantsUsStates = resolved.basemaps.subdivisions.includes("us-states");
46150
+ const usCrisp = resolved.projection === "albers-usa" && wantsUsStates && !!data.naLand;
46151
+ const worldTopo = usCrisp ? data.naLand : resolved.basemaps.world === "detail" ? data.worldDetail : data.worldCoarse;
45981
46152
  const worldLayer = decodeLayer(worldTopo);
45982
- const usLayer = resolved.basemaps.subdivisions.includes("us-states") ? decodeLayer(data.usStates) : null;
45983
- const neutralFill = mix(palette.border, palette.bg, 32);
45984
- const regionStroke = mix(palette.border, palette.bg, 70);
46153
+ const usLayer = wantsUsStates ? decodeLayer(data.usStates) : null;
46154
+ const landTint = isDark ? LAND_TINT_DARK : LAND_TINT_LIGHT;
46155
+ const neutralFill = mix(palette.colors.green, palette.bg, landTint);
46156
+ const water = mapBackgroundColor(palette);
46157
+ const usContext = usLayer !== null;
46158
+ const foreignFill = mix(
46159
+ palette.colors.gray,
46160
+ palette.bg,
46161
+ isDark ? FOREIGN_TINT_DARK : FOREIGN_TINT_LIGHT
46162
+ );
46163
+ const regionStroke = isDark ? mix(palette.bg, palette.text, 78) : mix(palette.text, palette.bg, 78);
45985
46164
  const scores = resolved.regions.filter((r) => r.score !== void 0).map((r) => r.score);
45986
46165
  const scaleOverride = resolved.directives.scale;
45987
46166
  const rampMin = scaleOverride ? scaleOverride.min : Math.min(...scores);
45988
46167
  const rampMax = scaleOverride ? scaleOverride.max : Math.max(...scores);
45989
- const rampHue = palette.primary;
46168
+ const rampHue = palette.colors.red;
45990
46169
  const hasRamp = scores.length > 0;
45991
- const activeGroup = resolveActiveTagGroup(
45992
- resolved.tagGroups,
45993
- resolved.directives.activeTag
45994
- );
46170
+ const SCORE_NAME = hasRamp ? resolved.directives.metric?.trim() || "Score" : null;
46171
+ const matchColorGroup = (v) => {
46172
+ const lv = v.trim().toLowerCase();
46173
+ if (lv === "none") return null;
46174
+ if (SCORE_NAME && (lv === "score" || lv === SCORE_NAME.toLowerCase()))
46175
+ return SCORE_NAME;
46176
+ const tg = resolved.tagGroups.find((g) => g.name.toLowerCase() === lv);
46177
+ return tg ? tg.name : v;
46178
+ };
46179
+ const override = opts.activeGroup;
46180
+ let activeGroup;
46181
+ if (override !== void 0) {
46182
+ activeGroup = override === null ? null : matchColorGroup(override);
46183
+ } else if (resolved.directives.activeTag !== void 0) {
46184
+ activeGroup = matchColorGroup(resolved.directives.activeTag);
46185
+ } else {
46186
+ activeGroup = SCORE_NAME ?? (resolved.tagGroups.length > 0 ? resolved.tagGroups[0].name : null);
46187
+ }
46188
+ const activeIsScore = SCORE_NAME !== null && activeGroup === SCORE_NAME;
46189
+ const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
45995
46190
  const fillForScore = (s) => {
45996
46191
  const t = rampMax > rampMin ? (s - rampMin) / (rampMax - rampMin) : 1;
45997
46192
  const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
45998
- return mix(rampHue, isDark ? palette.surface : palette.bg, pct);
46193
+ return mix(rampHue, rampBase, pct);
45999
46194
  };
46000
46195
  const tagFill = (tags, groupName) => {
46001
46196
  if (!groupName) return null;
@@ -46009,40 +46204,40 @@ function layoutMap(resolved, data, size, opts) {
46009
46204
  (e) => e.value.toLowerCase() === val.toLowerCase()
46010
46205
  );
46011
46206
  if (!entry?.color) return null;
46012
- return shapeFill(palette, entry.color, isDark);
46207
+ return mix(
46208
+ entry.color,
46209
+ palette.bg,
46210
+ isDark ? TAG_TINT_DARK : TAG_TINT_LIGHT
46211
+ );
46212
+ };
46213
+ const regionFill = (r) => {
46214
+ if (activeIsScore) {
46215
+ return r.score !== void 0 ? fillForScore(r.score) : neutralFill;
46216
+ }
46217
+ return tagFill(r.tags, activeGroup) ?? neutralFill;
46013
46218
  };
46014
46219
  const regionById = new Map(resolved.regions.map((r) => [r.iso, r]));
46015
- const regionFeatures = [];
46016
- for (const r of resolved.regions) {
46017
- const f = r.layer === "us-state" ? usLayer?.get(r.iso) : worldLayer.get(r.iso);
46018
- if (f) regionFeatures.push(f);
46019
- }
46020
- const extentCorners = () => {
46220
+ const extentOutline = () => {
46021
46221
  const [[w, s], [e, n]] = resolved.extent;
46222
+ const N = 16;
46223
+ const coords = [];
46224
+ for (let i = 0; i <= N; i++) {
46225
+ const t = i / N;
46226
+ const lon = w + (e - w) * t;
46227
+ const lat = s + (n - s) * t;
46228
+ coords.push([lon, s], [lon, n], [w, lat], [e, lat]);
46229
+ }
46022
46230
  return {
46023
46231
  type: "Feature",
46024
46232
  properties: {},
46025
- geometry: {
46026
- type: "MultiPoint",
46027
- coordinates: [
46028
- [w, s],
46029
- [e, s],
46030
- [e, n],
46031
- [w, n]
46032
- ]
46033
- }
46233
+ geometry: { type: "MultiPoint", coordinates: coords }
46034
46234
  };
46035
46235
  };
46036
46236
  let fitFeatures;
46037
- if (resolved.projection === "albers-usa") {
46038
- if (regionFeatures.length > 0) fitFeatures = regionFeatures;
46039
- else if (usLayer) fitFeatures = [...usLayer.values()];
46040
- else {
46041
- const us = worldLayer.get("US");
46042
- fitFeatures = us ? [us] : [...worldLayer.values()];
46043
- }
46237
+ if (resolved.projection === "albers-usa" && usLayer) {
46238
+ fitFeatures = [...usLayer.entries()].filter(([iso]) => !US_NON_CONUS.has(iso)).map(([, f]) => f);
46044
46239
  } else {
46045
- fitFeatures = [extentCorners()];
46240
+ fitFeatures = [extentOutline()];
46046
46241
  }
46047
46242
  const fitTarget = { type: "FeatureCollection", features: fitFeatures };
46048
46243
  const projection = projectionFor(resolved.projection);
@@ -46051,32 +46246,311 @@ function layoutMap(resolved, data, size, opts) {
46051
46246
  if (centerLon > 180) centerLon -= 360;
46052
46247
  projection.rotate([-centerLon, 0]);
46053
46248
  }
46054
- projection.fitExtent(
46249
+ const TITLE_GAP = 16;
46250
+ let topPad = FIT_PAD;
46251
+ if (resolved.title && resolved.pois.length > 0) {
46252
+ const bannerBottom = (resolved.subtitle ? TITLE_Y + TITLE_FONT_SIZE : TITLE_Y) + TITLE_FONT_SIZE / 2;
46253
+ topPad = Math.max(FIT_PAD, bannerBottom + TITLE_GAP);
46254
+ }
46255
+ const fitBox = [
46256
+ [FIT_PAD, topPad],
46055
46257
  [
46056
- [FIT_PAD, FIT_PAD],
46057
- [
46058
- Math.max(FIT_PAD + 1, width - FIT_PAD),
46059
- Math.max(FIT_PAD + 1, height - FIT_PAD)
46060
- ]
46061
- ],
46062
- fitTarget
46063
- );
46064
- const path = (0, import_d3_geo2.geoPath)(projection);
46065
- const project = (lon, lat) => projection([lon, lat]) ?? null;
46258
+ Math.max(FIT_PAD + 1, width - FIT_PAD),
46259
+ Math.max(topPad + 1, height - FIT_PAD)
46260
+ ]
46261
+ ];
46262
+ projection.fitExtent(fitBox, fitTarget);
46263
+ const fitGB = (0, import_d3_geo2.geoBounds)(fitTarget);
46264
+ const fitIsGlobal = fitGB[1][0] - fitGB[0][0] >= 270 || fitGB[1][1] - fitGB[0][1] >= 130;
46265
+ let path;
46266
+ let project;
46267
+ if (fitIsGlobal) {
46268
+ const cb = (0, import_d3_geo2.geoPath)(projection).bounds(fitTarget);
46269
+ const bx0 = cb[0][0];
46270
+ const by0 = cb[0][1];
46271
+ const cw = cb[1][0] - bx0;
46272
+ const ch = cb[1][1] - by0;
46273
+ const ox = fitBox[0][0];
46274
+ const oy = fitBox[0][1];
46275
+ const sx = cw > 0 ? (fitBox[1][0] - ox) / cw : 1;
46276
+ const sy = ch > 0 ? (fitBox[1][1] - oy) / ch : 1;
46277
+ const stretch = (x, y) => [
46278
+ ox + (x - bx0) * sx,
46279
+ oy + (y - by0) * sy
46280
+ ];
46281
+ const baseProjection = projection;
46282
+ const tx = (0, import_d3_geo2.geoTransform)({
46283
+ point(x, y) {
46284
+ const [px, py] = stretch(x, y);
46285
+ this.stream.point(px, py);
46286
+ }
46287
+ });
46288
+ path = (0, import_d3_geo2.geoPath)({
46289
+ stream: (s) => baseProjection.stream(
46290
+ tx.stream(s)
46291
+ )
46292
+ });
46293
+ project = (lon, lat) => {
46294
+ const p = baseProjection([lon, lat]);
46295
+ return p ? stretch(p[0], p[1]) : null;
46296
+ };
46297
+ } else {
46298
+ path = (0, import_d3_geo2.geoPath)(projection);
46299
+ project = (lon, lat) => projection([lon, lat]) ?? null;
46300
+ }
46301
+ const insets = [];
46302
+ const insetRegions = [];
46303
+ const insetLabelSeeds = [];
46304
+ if (resolved.projection === "albers-usa" && usLayer) {
46305
+ const PAD = 8;
46306
+ const GAP = 12;
46307
+ const yB = height - FIT_PAD;
46308
+ const BW = 8;
46309
+ const coast = /* @__PURE__ */ new Map();
46310
+ const addPt = (lon, lat) => {
46311
+ const p = projection([lon, lat]);
46312
+ if (!p) return;
46313
+ const bi = Math.floor(p[0] / BW);
46314
+ const cur = coast.get(bi);
46315
+ if (cur === void 0 || p[1] > cur) coast.set(bi, p[1]);
46316
+ };
46317
+ const walk = (co) => {
46318
+ if (Array.isArray(co) && typeof co[0] === "number")
46319
+ addPt(co[0], co[1]);
46320
+ else if (Array.isArray(co)) for (const c of co) walk(c);
46321
+ };
46322
+ for (const [iso, f] of usLayer) {
46323
+ if (US_NON_CONUS.has(iso)) continue;
46324
+ walk(f.geometry.coordinates);
46325
+ }
46326
+ const at = (x) => {
46327
+ const bi = Math.floor(x / BW);
46328
+ let y = -Infinity;
46329
+ for (let k = bi - 1; k <= bi + 1; k++) {
46330
+ const v = coast.get(k);
46331
+ if (v !== void 0 && v > y) y = v;
46332
+ }
46333
+ return y;
46334
+ };
46335
+ const coastTop = (x0, xr) => {
46336
+ const n = 24;
46337
+ const pts = [];
46338
+ let maxY = -Infinity;
46339
+ for (let i = 0; i <= n; i++) {
46340
+ const x = x0 + (xr - x0) * i / n;
46341
+ const y = at(x);
46342
+ if (y > -Infinity) {
46343
+ pts.push([x, y]);
46344
+ if (y > maxY) maxY = y;
46345
+ }
46346
+ }
46347
+ if (pts.length === 0) return () => yB - height * 0.42;
46348
+ let m = 0;
46349
+ if (pts.length >= 2) {
46350
+ let sx = 0, sy = 0, sxx = 0, sxy = 0;
46351
+ for (const [x, y] of pts) {
46352
+ sx += x;
46353
+ sy += y;
46354
+ sxx += x * x;
46355
+ sxy += x * y;
46356
+ }
46357
+ const den = pts.length * sxx - sx * sx;
46358
+ if (den !== 0) m = (pts.length * sxy - sx * sy) / den;
46359
+ }
46360
+ m = Math.max(-0.35, Math.min(0.35, m));
46361
+ let c = -Infinity;
46362
+ for (const [x, y] of pts) {
46363
+ const need = y - m * x + GAP;
46364
+ if (need > c) c = need;
46365
+ }
46366
+ return (x) => m * x + c;
46367
+ };
46368
+ const placeInset = (iso, proj, boxX, iwReq) => {
46369
+ const f = usLayer.get(iso);
46370
+ if (!f) return boxX;
46371
+ const x0 = boxX;
46372
+ const iw = Math.min(iwReq, width - FIT_PAD - x0 - 2 * PAD);
46373
+ if (iw < 24) return boxX;
46374
+ const xr = x0 + iw + 2 * PAD;
46375
+ const top = coastTop(x0, xr);
46376
+ const yL = top(x0);
46377
+ const yR = top(xr);
46378
+ proj.fitWidth(iw, f);
46379
+ const bb = (0, import_d3_geo2.geoPath)(proj).bounds(f);
46380
+ const sh = Number.isFinite(bb[0][0]) ? bb[1][1] - bb[0][1] : iw;
46381
+ const needH = sh + 2 * PAD;
46382
+ let topFit = Math.max(yL, yR);
46383
+ const bottom = Math.min(topFit + needH, yB);
46384
+ if (bottom - topFit < needH) topFit = bottom - needH;
46385
+ const lift = topFit - Math.max(yL, yR);
46386
+ const topL = yL + lift;
46387
+ const topR = yR + lift;
46388
+ proj.fitExtent(
46389
+ [
46390
+ [x0 + PAD, topFit + PAD],
46391
+ [xr - PAD, bottom - PAD]
46392
+ ],
46393
+ f
46394
+ );
46395
+ const d = (0, import_d3_geo2.geoPath)(proj)(f) ?? "";
46396
+ if (!d) return xr;
46397
+ const r = regionById.get(iso);
46398
+ let fill2 = neutralFill;
46399
+ let lineNumber = -1;
46400
+ if (r?.layer === "us-state") {
46401
+ fill2 = regionFill(r);
46402
+ lineNumber = r.lineNumber;
46403
+ }
46404
+ insets.push({
46405
+ x: x0,
46406
+ y: Math.min(topL, topR),
46407
+ w: xr - x0,
46408
+ h: bottom - Math.min(topL, topR),
46409
+ points: [
46410
+ [x0, topL],
46411
+ [xr, topR],
46412
+ [xr, bottom],
46413
+ [x0, bottom]
46414
+ ]
46415
+ });
46416
+ insetRegions.push({
46417
+ id: iso,
46418
+ d,
46419
+ fill: fill2,
46420
+ stroke: regionStroke,
46421
+ lineNumber,
46422
+ layer: "us-state",
46423
+ ...r?.score !== void 0 && { score: r.score },
46424
+ ...r && Object.keys(r.tags).length > 0 && { tags: r.tags }
46425
+ });
46426
+ const ctr = (0, import_d3_geo2.geoPath)(proj).centroid(f);
46427
+ if (Number.isFinite(ctr[0])) {
46428
+ const name = f.properties?.name ?? iso;
46429
+ insetLabelSeeds.push({ x: ctr[0], y: ctr[1], iso, name, lineNumber });
46430
+ }
46431
+ return xr;
46432
+ };
46433
+ const akRight = placeInset(
46434
+ "US-AK",
46435
+ alaskaProjection(),
46436
+ FIT_PAD,
46437
+ width * 0.15
46438
+ );
46439
+ placeInset("US-HI", hawaiiProjection(), akRight + 24, width * 0.1);
46440
+ }
46441
+ const conusFit = resolved.projection === "albers-usa" && !!usLayer;
46442
+ const cullExtent = conusFit ? (0, import_d3_geo2.geoBounds)(fitTarget) : resolved.extent;
46443
+ const [[exW, exS], [exE, exN]] = cullExtent;
46444
+ const lonSpan = exE - exW;
46445
+ const latSpan = exN - exS;
46446
+ const isGlobalView = lonSpan >= 270 || latSpan >= 130;
46447
+ const padLon = Math.max(8, lonSpan * 0.35);
46448
+ const padLat = Math.max(8, latSpan * 0.35);
46449
+ const vW = exW - padLon;
46450
+ const vE = exE + padLon;
46451
+ const vS = exS - padLat;
46452
+ const vN = exN + padLat;
46453
+ const vLonCenter = (exW + exE) / 2;
46454
+ const normLon = (lon) => {
46455
+ let L = lon;
46456
+ while (L < vLonCenter - 180) L += 360;
46457
+ while (L > vLonCenter + 180) L -= 360;
46458
+ return L;
46459
+ };
46460
+ const ringOverlapsView = (ring) => {
46461
+ let anyIn = false;
46462
+ let loMin = Infinity, loMax = -Infinity, laMin = Infinity, laMax = -Infinity, rawMin = Infinity, rawMax = -Infinity;
46463
+ for (const [rawLon, lat] of ring) {
46464
+ const lon = normLon(rawLon);
46465
+ if (lon >= vW && lon <= vE && lat >= vS && lat <= vN) anyIn = true;
46466
+ if (lon < loMin) loMin = lon;
46467
+ if (lon > loMax) loMax = lon;
46468
+ if (rawLon < rawMin) rawMin = rawLon;
46469
+ if (rawLon > rawMax) rawMax = rawLon;
46470
+ if (lat < laMin) laMin = lat;
46471
+ if (lat > laMax) laMax = lat;
46472
+ }
46473
+ if (loMax - loMin > 270) return false;
46474
+ if (rawMax - rawMin > 180 && loMax - loMin < 90) return false;
46475
+ if (anyIn) return true;
46476
+ if (loMax - loMin > 180) return false;
46477
+ return !(loMax < vW || loMin > vE || laMax < vS || laMin > vN);
46478
+ };
46479
+ const cullFeatureToView = (f) => {
46480
+ if (isGlobalView) return f;
46481
+ const g = f.geometry;
46482
+ if (!g) return f;
46483
+ if (g.type === "Polygon") {
46484
+ const ring = g.coordinates[0];
46485
+ return ringOverlapsView(ring) ? f : null;
46486
+ }
46487
+ if (g.type === "MultiPolygon") {
46488
+ const polys = g.coordinates;
46489
+ const keep = polys.filter(
46490
+ (p) => ringOverlapsView(p[0])
46491
+ );
46492
+ if (!keep.length) return null;
46493
+ if (keep.length === polys.length) return f;
46494
+ return { ...f, geometry: { ...g, coordinates: keep } };
46495
+ }
46496
+ return f;
46497
+ };
46498
+ const SEAM_SLIVER_MAX_SPAN = 100;
46499
+ const ringIsFrameFiller = (ring) => {
46500
+ const lons = ring.map(([lon]) => lon).sort((a, b) => a - b);
46501
+ if (lons.length < 2) return false;
46502
+ let maxGap = -1;
46503
+ let gapIdx = 0;
46504
+ for (let i = 1; i < lons.length; i++) {
46505
+ const g = lons[i] - lons[i - 1];
46506
+ if (g > maxGap) {
46507
+ maxGap = g;
46508
+ gapIdx = i;
46509
+ }
46510
+ }
46511
+ const wrapGap = lons[0] + 360 - lons[lons.length - 1];
46512
+ if (wrapGap >= maxGap) return false;
46513
+ const span = 360 - maxGap;
46514
+ const east = lons[gapIdx - 1] + 360;
46515
+ return east > 180 && span < SEAM_SLIVER_MAX_SPAN;
46516
+ };
46517
+ const dropFrameFillers = (f) => {
46518
+ const g = f.geometry;
46519
+ if (!g) return f;
46520
+ if (g.type === "Polygon") {
46521
+ const ring = g.coordinates[0];
46522
+ return ringIsFrameFiller(ring) ? null : f;
46523
+ }
46524
+ if (g.type === "MultiPolygon") {
46525
+ const polys = g.coordinates;
46526
+ const keep = polys.filter(
46527
+ (p) => !ringIsFrameFiller(p[0])
46528
+ );
46529
+ if (!keep.length) return null;
46530
+ if (keep.length === polys.length) return f;
46531
+ return { ...f, geometry: { ...g, coordinates: keep } };
46532
+ }
46533
+ return f;
46534
+ };
46066
46535
  const regions = [];
46067
- const pushRegionLayer = (layerFeatures, layerKind) => {
46536
+ const pushRegionLayer = (layerFeatures, layerKind, shouldCull) => {
46068
46537
  for (const [iso, f] of layerFeatures) {
46069
- const d = path(f) ?? "";
46070
- if (!d) continue;
46538
+ if (layerKind === "us-state" && usContext && INSET_STATES.has(iso))
46539
+ continue;
46540
+ if (layerKind === "country" && usContext && iso === "US") continue;
46071
46541
  const r = regionById.get(iso);
46542
+ const viewF = shouldCull ? cullFeatureToView(f) : dropFrameFillers(f);
46543
+ if (!viewF) continue;
46544
+ const d = path(viewF) ?? "";
46545
+ if (!d) continue;
46072
46546
  const isThisLayer = r?.layer === layerKind;
46073
- let fill2 = neutralFill;
46547
+ const isForeign = layerKind === "country" && usContext && iso !== "US";
46548
+ let fill2 = isForeign ? foreignFill : neutralFill;
46074
46549
  let label;
46075
46550
  let lineNumber = -1;
46076
46551
  let layer = "base";
46077
46552
  if (isThisLayer) {
46078
- if (r.score !== void 0) fill2 = fillForScore(r.score);
46079
- else fill2 = tagFill(r.tags, activeGroup) ?? neutralFill;
46553
+ fill2 = regionFill(r);
46080
46554
  lineNumber = r.lineNumber;
46081
46555
  layer = layerKind;
46082
46556
  label = r.name;
@@ -46088,12 +46562,42 @@ function layoutMap(resolved, data, size, opts) {
46088
46562
  stroke: regionStroke,
46089
46563
  lineNumber,
46090
46564
  layer,
46091
- ...label !== void 0 && { label }
46565
+ ...label !== void 0 && { label },
46566
+ ...isThisLayer && r.score !== void 0 && { score: r.score },
46567
+ ...isThisLayer && Object.keys(r.tags).length > 0 && { tags: r.tags }
46092
46568
  });
46093
46569
  }
46094
46570
  };
46095
- pushRegionLayer(worldLayer, "country");
46096
- if (usLayer) pushRegionLayer(usLayer, "us-state");
46571
+ pushRegionLayer(worldLayer, "country", !isGlobalView);
46572
+ if (usLayer) pushRegionLayer(usLayer, "us-state", !conusFit && !isGlobalView);
46573
+ const lakesTopo = usCrisp && data.naLakes ? data.naLakes : data.lakes;
46574
+ if (lakesTopo) {
46575
+ for (const [, f] of decodeLayer(lakesTopo)) {
46576
+ const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
46577
+ if (!viewF) continue;
46578
+ const d = path(viewF) ?? "";
46579
+ if (!d) continue;
46580
+ regions.push({
46581
+ id: "lake",
46582
+ d,
46583
+ fill: water,
46584
+ stroke: "none",
46585
+ lineNumber: -1,
46586
+ layer: "base"
46587
+ });
46588
+ }
46589
+ }
46590
+ const riverColor = water;
46591
+ const rivers = [];
46592
+ if (data.rivers) {
46593
+ for (const [, f] of decodeLayer(data.rivers)) {
46594
+ const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
46595
+ if (!viewF) continue;
46596
+ const d = path(viewF) ?? "";
46597
+ if (!d) continue;
46598
+ rivers.push({ d, color: riverColor, width: RIVER_WIDTH });
46599
+ }
46600
+ }
46097
46601
  const sizeVals = resolved.pois.map((p) => Number(p.meta["size"])).filter((n) => Number.isFinite(n) && n > 0);
46098
46602
  const sizeMin = sizeVals.length ? Math.min(...sizeVals) : 0;
46099
46603
  const sizeMax = sizeVals.length ? Math.max(...sizeVals) : 0;
@@ -46114,8 +46618,8 @@ function layoutMap(resolved, data, size, opts) {
46114
46618
  if (hex) return { fill: hex, stroke: mix(hex, palette.text, 18) };
46115
46619
  }
46116
46620
  return {
46117
- fill: palette.accent,
46118
- stroke: mix(palette.accent, palette.text, 18)
46621
+ fill: palette.colors.orange,
46622
+ stroke: mix(palette.colors.orange, palette.text, 18)
46119
46623
  };
46120
46624
  };
46121
46625
  const routeNumberById = /* @__PURE__ */ new Map();
@@ -46153,7 +46657,7 @@ function layoutMap(resolved, data, size, opts) {
46153
46657
  cy += Math.sin(ang) * COLO_R;
46154
46658
  }
46155
46659
  const { fill: fill2, stroke: stroke2 } = poiFill(e.p);
46156
- poiScreen.set(e.p.id, { cx, cy });
46660
+ poiScreen.set(e.p.id, { cx, cy, r: radiusFor(e.p) });
46157
46661
  const num = routeNumberById.get(e.p.id);
46158
46662
  pois.push({
46159
46663
  id: e.p.id,
@@ -46170,17 +46674,36 @@ function layoutMap(resolved, data, size, opts) {
46170
46674
  });
46171
46675
  }
46172
46676
  const legs = [];
46677
+ const RIM_GAP = 1.5;
46173
46678
  const legPath = (a, b, curved, offset) => {
46174
- if (!curved && offset === 0) return `M${a.cx},${a.cy}L${b.cx},${b.cy}`;
46175
46679
  const mx = (a.cx + b.cx) / 2;
46176
46680
  const my = (a.cy + b.cy) / 2;
46177
46681
  const dx = b.cx - a.cx;
46178
46682
  const dy = b.cy - a.cy;
46179
46683
  const len = Math.hypot(dx, dy) || 1;
46684
+ const trimA = Math.min(a.r + RIM_GAP, len * 0.45);
46685
+ const trimB = Math.min(b.r + RIM_GAP, len * 0.45);
46686
+ if (!curved && offset === 0) {
46687
+ const ux = dx / len;
46688
+ const uy = dy / len;
46689
+ const ax2 = a.cx + ux * trimA;
46690
+ const ay2 = a.cy + uy * trimA;
46691
+ const bx2 = b.cx - ux * trimB;
46692
+ const by2 = b.cy - uy * trimB;
46693
+ return `M${ax2},${ay2}L${bx2},${by2}`;
46694
+ }
46180
46695
  const nx = -dy / len;
46181
46696
  const ny = dx / len;
46182
46697
  const bow = offset !== 0 ? offset : len * ARC_CURVE_FRAC;
46183
- return `M${a.cx},${a.cy}Q${mx + nx * bow},${my + ny * bow} ${b.cx},${b.cy}`;
46698
+ const px = mx + nx * bow;
46699
+ const py = my + ny * bow;
46700
+ const ta = Math.hypot(px - a.cx, py - a.cy) || 1;
46701
+ const tb = Math.hypot(b.cx - px, b.cy - py) || 1;
46702
+ const ax = a.cx + (px - a.cx) / ta * trimA;
46703
+ const ay = a.cy + (py - a.cy) / ta * trimA;
46704
+ const bx = b.cx - (b.cx - px) / tb * trimB;
46705
+ const by = b.cy - (b.cy - py) / tb * trimB;
46706
+ return `M${ax},${ay}Q${px},${py} ${bx},${by}`;
46184
46707
  };
46185
46708
  for (const rt of resolved.routes) {
46186
46709
  const curved = rt.meta["style"] === "arc";
@@ -46191,7 +46714,7 @@ function layoutMap(resolved, data, size, opts) {
46191
46714
  legs.push({
46192
46715
  d: legPath(a, b, curved, 0),
46193
46716
  width: W_MIN,
46194
- color: mix(palette.text, palette.bg, 55),
46717
+ color: mix(palette.text, palette.bg, 72),
46195
46718
  arrow: true,
46196
46719
  lineNumber: rt.lineNumber
46197
46720
  });
@@ -46227,7 +46750,7 @@ function layoutMap(resolved, data, size, opts) {
46227
46750
  legs.push({
46228
46751
  d: legPath(a, b, curved, offset),
46229
46752
  width: widthFor(e),
46230
- color: mix(palette.text, palette.bg, 45),
46753
+ color: mix(palette.text, palette.bg, 66),
46231
46754
  arrow: e.directed,
46232
46755
  lineNumber: e.lineNumber,
46233
46756
  ...e.label !== void 0 && {
@@ -46239,38 +46762,92 @@ function layoutMap(resolved, data, size, opts) {
46239
46762
  });
46240
46763
  }
46241
46764
  const labels = [];
46242
- const pinList = [];
46243
46765
  const obstacles = [];
46244
46766
  const markers = pois.map((p) => ({
46245
46767
  cx: p.cx,
46246
46768
  cy: p.cy,
46247
46769
  r: p.r
46248
46770
  }));
46249
- const collides = (rect) => markers.some((m) => rectCircleOverlap(rect, m)) || obstacles.some((o) => rectsOverlap(rect, o));
46771
+ const legSegments = [];
46772
+ for (const leg of legs) {
46773
+ const m = /^M(-?[\d.]+),(-?[\d.]+)(?:L(-?[\d.]+),(-?[\d.]+)|Q(-?[\d.]+),(-?[\d.]+) (-?[\d.]+),(-?[\d.]+))$/.exec(
46774
+ leg.d
46775
+ );
46776
+ if (!m) continue;
46777
+ const x0 = +m[1];
46778
+ const y0 = +m[2];
46779
+ if (m[3] !== void 0) {
46780
+ legSegments.push([x0, y0, +m[3], +m[4]]);
46781
+ } else {
46782
+ const cx = +m[5];
46783
+ const cy = +m[6];
46784
+ const ex = +m[7];
46785
+ const ey = +m[8];
46786
+ const N = 8;
46787
+ let px = x0;
46788
+ let py = y0;
46789
+ for (let i = 1; i <= N; i++) {
46790
+ const t = i / N;
46791
+ const u = 1 - t;
46792
+ const qx = u * u * x0 + 2 * u * t * cx + t * t * ex;
46793
+ const qy = u * u * y0 + 2 * u * t * cy + t * t * ey;
46794
+ legSegments.push([px, py, qx, qy]);
46795
+ px = qx;
46796
+ py = qy;
46797
+ }
46798
+ }
46799
+ }
46800
+ 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));
46250
46801
  const regionLabelMode = resolved.directives.regionLabels ?? "off";
46802
+ const LABEL_PADX = 6;
46803
+ const LABEL_PADY = 3;
46804
+ const labelW = (text) => measureLegendText(text, FONT) + 2 * LABEL_PADX;
46805
+ const labelH = FONT + 2 * LABEL_PADY;
46806
+ const pushRegionLabel = (x, y, text, fill2, lineNumber) => {
46807
+ const color = contrastText(
46808
+ fill2,
46809
+ palette.textOnFillLight,
46810
+ palette.textOnFillDark
46811
+ );
46812
+ const haloColor = color === palette.textOnFillLight ? palette.textOnFillDark : palette.textOnFillLight;
46813
+ labels.push({
46814
+ x,
46815
+ y,
46816
+ text,
46817
+ anchor: "middle",
46818
+ color,
46819
+ halo: true,
46820
+ haloColor,
46821
+ lineNumber
46822
+ });
46823
+ };
46824
+ const WORLD_LABEL_ANCHORS = {
46825
+ US: [-98.5, 39.5]
46826
+ // CONUS geographic centre (near Lebanon, Kansas)
46827
+ };
46251
46828
  if (regionLabelMode === "full" || regionLabelMode === "abbrev") {
46252
46829
  for (const r of regions) {
46253
46830
  if (r.layer === "base" || r.label === void 0) continue;
46254
46831
  const f = r.layer === "us-state" ? usLayer?.get(r.id) : worldLayer.get(r.id);
46255
46832
  if (!f) continue;
46256
46833
  const [[x0, y0], [x1, y1]] = path.bounds(f);
46257
- if ((x1 - x0) * (y1 - y0) < TINY_REGION_AREA) continue;
46258
- const c = path.centroid(f);
46259
- if (!Number.isFinite(c[0])) continue;
46260
46834
  const text = regionLabelMode === "abbrev" ? r.id.replace(/^US-/, "") : r.label;
46261
- labels.push({
46262
- x: c[0],
46263
- y: c[1],
46835
+ if (labelW(text) > x1 - x0 || labelH > y1 - y0) continue;
46836
+ const anchor = r.layer !== "us-state" ? WORLD_LABEL_ANCHORS[r.id] : void 0;
46837
+ const c = anchor ? project(anchor[0], anchor[1]) : path.centroid(f);
46838
+ if (!c || !Number.isFinite(c[0])) continue;
46839
+ pushRegionLabel(c[0], c[1], text, r.fill, r.lineNumber);
46840
+ }
46841
+ for (const seed of insetLabelSeeds) {
46842
+ const text = regionLabelMode === "abbrev" ? seed.iso.replace(/^US-/, "") : seed.name;
46843
+ const src = regionById.get(seed.iso);
46844
+ pushRegionLabel(
46845
+ seed.x,
46846
+ seed.y,
46264
46847
  text,
46265
- anchor: "middle",
46266
- color: contrastText(
46267
- r.fill,
46268
- palette.textOnFillLight,
46269
- palette.textOnFillDark
46270
- ),
46271
- halo: true,
46272
- lineNumber: r.lineNumber
46273
- });
46848
+ src ? regionFill(src) : neutralFill,
46849
+ seed.lineNumber
46850
+ );
46274
46851
  }
46275
46852
  }
46276
46853
  const poiLabelMode = resolved.directives.poiLabels ?? "auto";
@@ -46283,68 +46860,106 @@ function layoutMap(resolved, data, size, opts) {
46283
46860
  const src = poiById.get(p.id);
46284
46861
  return src?.label ?? src?.name ?? p.id;
46285
46862
  };
46286
- let pinCounter = 0;
46287
- for (const p of ordered) {
46863
+ const poiLabH = FONT * 1.25;
46864
+ const labelInfo = (p) => {
46288
46865
  const text = labelText(p);
46289
- const w = measureLegendText(text, FONT);
46290
- const h = FONT * 1.25;
46291
- const inline = { x: p.cx + p.r + 3, y: p.cy - h / 2, w, h };
46292
- if (!collides(inline)) {
46293
- obstacles.push(inline);
46866
+ return { text, w: measureLegendText(text, FONT) };
46867
+ };
46868
+ const pushInline = (p, text, w, side) => {
46869
+ const tx = side === "right" ? p.cx + p.r + 3 : p.cx - p.r - 3;
46870
+ obstacles.push({
46871
+ x: side === "right" ? tx : tx - w,
46872
+ y: p.cy - poiLabH / 2,
46873
+ w,
46874
+ h: poiLabH
46875
+ });
46876
+ labels.push({
46877
+ x: tx,
46878
+ y: p.cy + FONT / 3,
46879
+ text,
46880
+ anchor: side === "right" ? "start" : "end",
46881
+ color: palette.text,
46882
+ halo: true,
46883
+ haloColor: palette.bg,
46884
+ poiId: p.id,
46885
+ lineNumber: p.lineNumber
46886
+ });
46887
+ };
46888
+ const inlineFits = (p, w, side) => {
46889
+ const tx = side === "right" ? p.cx + p.r + 3 : p.cx - p.r - 3;
46890
+ const rect = {
46891
+ x: side === "right" ? tx : tx - w,
46892
+ y: p.cy - poiLabH / 2,
46893
+ w,
46894
+ h: poiLabH
46895
+ };
46896
+ return rect.x >= 0 && rect.x + rect.w <= width && !collides(rect);
46897
+ };
46898
+ const GROUP_R = 30;
46899
+ const groups = [];
46900
+ for (const p of ordered) {
46901
+ const near = groups.find(
46902
+ (g) => g.some((q) => Math.hypot(q.cx - p.cx, q.cy - p.cy) < GROUP_R)
46903
+ );
46904
+ if (near) near.push(p);
46905
+ else groups.push([p]);
46906
+ }
46907
+ const ROW_GAP2 = 3;
46908
+ const step = poiLabH + ROW_GAP2;
46909
+ const COL_GAP = 16;
46910
+ const placeColumn = (group) => {
46911
+ const items = group.map((p) => ({ p, ...labelInfo(p) })).sort((a, b) => a.p.cy - b.p.cy || (a.text < b.text ? -1 : 1));
46912
+ const left = Math.min(...items.map((o) => o.p.cx - o.p.r));
46913
+ const right = Math.max(...items.map((o) => o.p.cx + o.p.r));
46914
+ const cyMid = (Math.min(...items.map((o) => o.p.cy)) + Math.max(...items.map((o) => o.p.cy))) / 2;
46915
+ const maxW = Math.max(...items.map((o) => o.w));
46916
+ const side = right + COL_GAP + maxW <= width - 2 ? "right" : "left";
46917
+ const colX = side === "right" ? right + COL_GAP : left - COL_GAP;
46918
+ const totalH = items.length * step;
46919
+ let startY = cyMid - totalH / 2;
46920
+ startY = Math.max(2, Math.min(startY, height - totalH - 2));
46921
+ items.forEach((o, i) => {
46922
+ const rowCy = startY + i * step + step / 2;
46923
+ obstacles.push({
46924
+ x: side === "right" ? colX : colX - o.w,
46925
+ y: rowCy - poiLabH / 2,
46926
+ w: o.w,
46927
+ h: poiLabH
46928
+ });
46294
46929
  labels.push({
46295
- x: inline.x,
46296
- y: p.cy + FONT / 3,
46297
- text,
46298
- anchor: "start",
46930
+ x: colX,
46931
+ y: rowCy + FONT / 3,
46932
+ text: o.text,
46933
+ anchor: side === "right" ? "start" : "end",
46299
46934
  color: palette.text,
46300
46935
  halo: true,
46301
- lineNumber: p.lineNumber
46936
+ haloColor: palette.bg,
46937
+ leader: {
46938
+ x1: o.p.cx,
46939
+ y1: o.p.cy,
46940
+ x2: side === "right" ? colX - 2 : colX + 2,
46941
+ y2: rowCy
46942
+ },
46943
+ leaderColor: o.p.fill,
46944
+ poiId: o.p.id,
46945
+ lineNumber: o.p.lineNumber
46302
46946
  });
46303
- continue;
46304
- }
46305
- let placed = false;
46306
- for (let k = 1; k <= 2 && !placed; k++) {
46307
- for (const [dx, dy] of RING_DIRS) {
46308
- const cx = p.cx + dx * LEADER_STEP * k;
46309
- const cy = p.cy + dy * LEADER_STEP * k;
46310
- const rect = {
46311
- x: dx >= 0 ? cx : cx - w,
46312
- y: cy - h / 2,
46313
- w,
46314
- h
46315
- };
46316
- if (rect.x < 0 || rect.x + rect.w > width || rect.y < 0 || rect.y + rect.h > height) {
46317
- continue;
46318
- }
46319
- if (collides(rect)) continue;
46320
- obstacles.push(rect);
46321
- labels.push({
46322
- x: cx,
46323
- y: cy + FONT / 3,
46324
- text,
46325
- anchor: dx >= 0 ? "start" : "end",
46326
- color: palette.text,
46327
- halo: true,
46328
- leader: { x1: p.cx, y1: p.cy, x2: cx, y2: cy },
46329
- lineNumber: p.lineNumber
46330
- });
46331
- placed = true;
46332
- break;
46947
+ });
46948
+ };
46949
+ for (const g of groups) {
46950
+ if (g.length === 1) {
46951
+ const p = g[0];
46952
+ const { text, w } = labelInfo(p);
46953
+ if (inlineFits(p, w, "right")) {
46954
+ pushInline(p, text, w, "right");
46955
+ continue;
46956
+ }
46957
+ if (inlineFits(p, w, "left")) {
46958
+ pushInline(p, text, w, "left");
46959
+ continue;
46333
46960
  }
46334
46961
  }
46335
- if (placed) continue;
46336
- pinCounter += 1;
46337
- pinList.push({ pin: pinCounter, label: text });
46338
- labels.push({
46339
- x: p.cx + p.r + 2,
46340
- y: p.cy - p.r,
46341
- text: String(pinCounter),
46342
- anchor: "start",
46343
- color: palette.text,
46344
- halo: true,
46345
- pin: pinCounter,
46346
- lineNumber: p.lineNumber
46347
- });
46962
+ placeColumn(g);
46348
46963
  }
46349
46964
  }
46350
46965
  let legend = null;
@@ -46353,8 +46968,7 @@ function layoutMap(resolved, data, size, opts) {
46353
46968
  name: g.name,
46354
46969
  entries: g.entries.map((e) => ({ value: e.value, color: e.color }))
46355
46970
  }));
46356
- const hasAnything = tagGroups.length > 0 || hasRamp || sizeVals.length > 0 || weightVals.length > 0;
46357
- if (hasAnything) {
46971
+ if (tagGroups.length > 0 || hasRamp) {
46358
46972
  legend = {
46359
46973
  tagGroups,
46360
46974
  activeGroup,
@@ -46365,20 +46979,9 @@ function layoutMap(resolved, data, size, opts) {
46365
46979
  },
46366
46980
  min: rampMin,
46367
46981
  max: rampMax,
46368
- hue: rampHue
46982
+ hue: rampHue,
46983
+ base: rampBase
46369
46984
  }
46370
- },
46371
- ...sizeVals.length > 0 && {
46372
- size: {
46373
- ...resolved.directives.sizeMetric !== void 0 && {
46374
- metric: resolved.directives.sizeMetric
46375
- },
46376
- min: sizeMin,
46377
- max: sizeMax
46378
- }
46379
- },
46380
- ...weightVals.length > 0 && {
46381
- weight: { min: wMin, max: wMax }
46382
46985
  }
46383
46986
  };
46384
46987
  }
@@ -46386,28 +46989,30 @@ function layoutMap(resolved, data, size, opts) {
46386
46989
  return {
46387
46990
  width,
46388
46991
  height,
46389
- background: palette.bg,
46992
+ background: water,
46390
46993
  title: resolved.title,
46391
46994
  ...resolved.subtitle !== void 0 && { subtitle: resolved.subtitle },
46392
46995
  ...resolved.caption !== void 0 && { caption: resolved.caption },
46393
46996
  regions,
46997
+ rivers,
46394
46998
  legs,
46395
46999
  pois,
46396
47000
  labels,
46397
- pinList,
46398
- legend
47001
+ legend,
47002
+ insets,
47003
+ insetRegions
46399
47004
  };
46400
47005
  }
46401
- var import_d3_geo2, import_topojson_client2, 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;
47006
+ var import_d3_geo2, import_topojson_client2, 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;
46402
47007
  var init_layout15 = __esm({
46403
47008
  "src/map/layout.ts"() {
46404
47009
  "use strict";
46405
47010
  import_d3_geo2 = require("d3-geo");
46406
47011
  import_topojson_client2 = require("topojson-client");
46407
47012
  init_color_utils();
46408
- init_tag_groups();
46409
47013
  init_label_layout();
46410
47014
  init_legend_constants();
47015
+ init_title_constants();
46411
47016
  FIT_PAD = 24;
46412
47017
  RAMP_FLOOR = 15;
46413
47018
  R_DEFAULT = 6;
@@ -46416,23 +47021,32 @@ var init_layout15 = __esm({
46416
47021
  W_MIN = 1.25;
46417
47022
  W_MAX = 8;
46418
47023
  FONT = 11;
46419
- LEADER_STEP = 14;
46420
47024
  COLO_EPS = 1.5;
47025
+ LAND_TINT_LIGHT = 58;
47026
+ LAND_TINT_DARK = 75;
47027
+ TAG_TINT_LIGHT = 60;
47028
+ TAG_TINT_DARK = 68;
47029
+ WATER_TINT = 55;
47030
+ RIVER_WIDTH = 1.3;
47031
+ FOREIGN_TINT_LIGHT = 30;
47032
+ FOREIGN_TINT_DARK = 62;
46421
47033
  COLO_R = 9;
46422
47034
  GOLDEN_ANGLE = 2.399963229728653;
46423
47035
  FAN_STEP = 16;
46424
- TINY_REGION_AREA = 600;
46425
47036
  ARC_CURVE_FRAC = 0.18;
46426
- RING_DIRS = [
46427
- [1, 0],
46428
- [0, 1],
46429
- [-1, 0],
46430
- [0, -1],
46431
- [1, 1],
46432
- [-1, 1],
46433
- [-1, -1],
46434
- [1, -1]
46435
- ];
47037
+ usConusProjection = () => (0, import_d3_geo2.geoConicEqualArea)().parallels([29.5, 45.5]).rotate([96, 0]);
47038
+ alaskaProjection = () => (0, import_d3_geo2.geoConicEqualArea)().rotate([154, 0]).center([-2, 58.5]).parallels([55, 65]);
47039
+ hawaiiProjection = () => (0, import_d3_geo2.geoMercator)();
47040
+ INSET_STATES = /* @__PURE__ */ new Set(["US-AK", "US-HI"]);
47041
+ US_NON_CONUS = /* @__PURE__ */ new Set([
47042
+ "US-AK",
47043
+ "US-HI",
47044
+ "US-AS",
47045
+ "US-GU",
47046
+ "US-MP",
47047
+ "US-PR",
47048
+ "US-VI"
47049
+ ]);
46436
47050
  }
46437
47051
  });
46438
47052
 
@@ -46442,7 +47056,7 @@ __export(renderer_exports16, {
46442
47056
  renderMap: () => renderMap,
46443
47057
  renderMapForExport: () => renderMapForExport
46444
47058
  });
46445
- function renderMap(container, resolved, data, palette, isDark, onClickItem, exportDims) {
47059
+ function renderMap(container, resolved, data, palette, isDark, onClickItem, exportDims, activeGroupOverride) {
46446
47060
  d3Selection18.select(container).selectAll(":not([data-d3-tooltip])").remove();
46447
47061
  const width = exportDims?.width ?? container.clientWidth;
46448
47062
  const height = exportDims?.height ?? container.clientHeight;
@@ -46453,27 +47067,29 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46453
47067
  { width, height },
46454
47068
  {
46455
47069
  palette,
46456
- isDark
47070
+ isDark,
47071
+ ...activeGroupOverride !== void 0 && {
47072
+ activeGroup: activeGroupOverride
47073
+ }
46457
47074
  }
46458
47075
  );
46459
- 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);
47076
+ 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);
46460
47077
  svg.append("rect").attr("width", width).attr("height", height).attr("fill", layout.background);
46461
- const arrowColor = mix(palette.text, palette.bg, 50);
46462
47078
  const defs = svg.append("defs");
46463
- 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);
46464
- const haloColor = layout.background;
46465
- if (layout.title) {
46466
- 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);
46467
- }
46468
- if (layout.subtitle) {
46469
- 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);
46470
- }
46471
- if (layout.caption) {
46472
- 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);
46473
- }
47079
+ const arrowSize = (w) => Math.min(15, 7 + w * 0.95);
47080
+ const haloColor = palette.bg;
46474
47081
  const gRegions = svg.append("g").attr("class", "dgmo-map-regions");
46475
- for (const r of layout.regions) {
46476
- const p = gRegions.append("path").attr("d", r.d).attr("fill", r.fill).attr("stroke", r.stroke).attr("stroke-width", 0.5);
47082
+ const drawRegion = (g, r, strokeWidth) => {
47083
+ const p = g.append("path").attr("d", r.d).attr("fill", r.fill).attr("stroke", r.stroke).attr("stroke-width", strokeWidth);
47084
+ if (r.layer !== "base") {
47085
+ p.classed("dgmo-map-region", true).attr("data-region", r.id);
47086
+ if (r.score !== void 0) p.attr("data-score", r.score);
47087
+ if (r.tags) {
47088
+ for (const [group, value] of Object.entries(r.tags)) {
47089
+ p.attr(`data-tag-${group.toLowerCase()}`, value.toLowerCase());
47090
+ }
47091
+ }
47092
+ }
46477
47093
  if (r.lineNumber >= 0) {
46478
47094
  p.attr("data-line-number", r.lineNumber);
46479
47095
  if (onClickItem) {
@@ -46483,11 +47099,31 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46483
47099
  );
46484
47100
  }
46485
47101
  }
47102
+ };
47103
+ for (const r of layout.regions) drawRegion(gRegions, r, 0.5);
47104
+ if (layout.rivers.length) {
47105
+ const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none");
47106
+ for (const r of layout.rivers) {
47107
+ gRivers.append("path").attr("d", r.d).attr("stroke", r.color).attr("stroke-width", r.width).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
47108
+ }
47109
+ }
47110
+ if (layout.insets.length) {
47111
+ const insetG = svg.append("g").attr("class", "dgmo-map-insets");
47112
+ for (const box of layout.insets) {
47113
+ const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
47114
+ 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");
47115
+ }
47116
+ for (const r of layout.insetRegions) drawRegion(insetG, r, 0.5);
46486
47117
  }
46487
47118
  const gLegs = svg.append("g").attr("class", "dgmo-map-legs").attr("fill", "none");
46488
- for (const leg of layout.legs) {
47119
+ layout.legs.forEach((leg, i) => {
46489
47120
  const p = gLegs.append("path").attr("d", leg.d).attr("stroke", leg.color).attr("stroke-width", leg.width).attr("stroke-linecap", "round");
46490
- if (leg.arrow) p.attr("marker-end", "url(#dgmo-map-arrow)");
47121
+ if (leg.arrow) {
47122
+ const id = `dgmo-map-arrow-${i}`;
47123
+ const s = arrowSize(leg.width);
47124
+ 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);
47125
+ p.attr("marker-end", `url(#${id})`);
47126
+ }
46491
47127
  if (leg.label !== void 0 && leg.labelX !== void 0) {
46492
47128
  emitText(
46493
47129
  gLegs,
@@ -46501,13 +47137,13 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46501
47137
  LABEL_FONT - 1
46502
47138
  );
46503
47139
  }
46504
- }
47140
+ });
46505
47141
  const gPois = svg.append("g").attr("class", "dgmo-map-pois");
46506
47142
  for (const poi of layout.pois) {
46507
47143
  if (poi.isOrigin) {
46508
47144
  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);
46509
47145
  }
46510
- 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);
47146
+ 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);
46511
47147
  if (onClickItem) {
46512
47148
  c.style("cursor", "pointer").on(
46513
47149
  "click",
@@ -46531,48 +47167,66 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46531
47167
  const gLabels = svg.append("g").attr("class", "dgmo-map-labels");
46532
47168
  for (const lab of layout.labels) {
46533
47169
  if (lab.leader) {
46534
- 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);
46535
- }
46536
- if (lab.pin !== void 0) {
46537
- 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);
47170
+ 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(
47171
+ "stroke",
47172
+ lab.leaderColor ?? mix(palette.textMuted, palette.bg, 60)
47173
+ ).attr("stroke-width", lab.leaderColor ? 1 : 0.75);
47174
+ if (lab.poiId !== void 0) line12.attr("data-poi", lab.poiId);
46538
47175
  }
46539
- emitText(
47176
+ const t = emitText(
46540
47177
  gLabels,
46541
47178
  lab.x,
46542
47179
  lab.y,
46543
47180
  lab.text,
46544
47181
  lab.anchor,
46545
47182
  lab.color,
46546
- haloColor,
47183
+ lab.haloColor,
46547
47184
  lab.halo,
46548
47185
  LABEL_FONT
46549
47186
  );
46550
- }
46551
- if (layout.pinList.length > 0) {
46552
- const gPins = svg.append("g").attr("class", "dgmo-map-pin-list").attr(
46553
- "transform",
46554
- `translate(12, ${height - layout.pinList.length * 14 - 8})`
46555
- );
46556
- layout.pinList.forEach((entry, i) => {
46557
- 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}`);
46558
- });
47187
+ if (lab.poiId !== void 0) {
47188
+ t.attr("data-poi", lab.poiId).style("cursor", "default");
47189
+ }
46559
47190
  }
46560
47191
  if (layout.legend) {
46561
47192
  const legendY = (layout.title ? TITLE_Y + TITLE_FONT_SIZE : 0) + (layout.subtitle ? TITLE_FONT_SIZE : 0) + 8;
46562
47193
  const legendG = svg.append("g").attr("class", "dgmo-map-legend").attr("transform", `translate(0, ${legendY})`);
46563
- const groups = layout.legend.tagGroups.filter((g) => g.entries.length > 0);
47194
+ const ramp = layout.legend.ramp;
47195
+ const scoreGroup = ramp ? {
47196
+ name: ramp.metric?.trim() || "Score",
47197
+ entries: [],
47198
+ gradient: {
47199
+ min: ramp.min,
47200
+ max: ramp.max,
47201
+ hue: ramp.hue,
47202
+ base: ramp.base
47203
+ }
47204
+ } : null;
47205
+ const tagGroups = layout.legend.tagGroups.filter((g) => g.entries.length > 0).map((g) => ({ name: g.name, entries: [...g.entries] }));
47206
+ const groups = [...scoreGroup ? [scoreGroup] : [], ...tagGroups];
46564
47207
  if (groups.length > 0) {
46565
47208
  const config = {
46566
- groups: groups.map((g) => ({ name: g.name, entries: [...g.entries] })),
47209
+ groups,
46567
47210
  position: { placement: "top-center", titleRelation: "below-title" },
46568
47211
  mode: exportDims ? "export" : "preview",
46569
- showEmptyGroups: false
47212
+ showEmptyGroups: false,
47213
+ // Keep inactive siblings visible as pills so the user can click to flip
47214
+ // the active colouring dimension (preview only — export shows just the
47215
+ // active group).
47216
+ showInactivePills: true
46570
47217
  };
46571
47218
  const state = { activeGroup: layout.legend.activeGroup };
46572
47219
  renderLegendD3(legendG, config, state, palette, isDark, void 0, width);
46573
47220
  }
46574
- const pinGap = layout.pinList.length ? layout.pinList.length * 14 + 14 : 0;
46575
- emitExtraLegend(svg, layout, palette, height, pinGap);
47221
+ }
47222
+ if (layout.title) {
47223
+ 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);
47224
+ }
47225
+ if (layout.subtitle) {
47226
+ 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);
47227
+ }
47228
+ if (layout.caption) {
47229
+ 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);
46576
47230
  }
46577
47231
  }
46578
47232
  function renderMapForExport(container, resolved, data, palette, isDark, exportDims) {
@@ -46583,54 +47237,7 @@ function emitText(g, x, y, text, anchor, color, halo, withHalo, fontSize) {
46583
47237
  if (withHalo) {
46584
47238
  t.attr("paint-order", "stroke fill").attr("stroke", halo).attr("stroke-width", 3).attr("stroke-linejoin", "round").attr("stroke-opacity", 0.7);
46585
47239
  }
46586
- }
46587
- function emitExtraLegend(svg, layout, palette, height, bottomGap) {
46588
- const { legend } = layout;
46589
- if (!legend) return;
46590
- if (!legend.ramp && !legend.size && !legend.weight) return;
46591
- const blocks = [];
46592
- const g = svg.append("g").attr("class", "dgmo-map-legend-keys").attr("transform", `translate(12, ${height - 56 - bottomGap})`);
46593
- let xCursor = 0;
46594
- if (legend.ramp) {
46595
- const ramp = legend.ramp;
46596
- blocks.push(() => {
46597
- const block = g.append("g").attr("transform", `translate(${xCursor},0)`);
46598
- const gradId = "dgmo-map-ramp";
46599
- const grad = block.append("defs").append("linearGradient").attr("id", gradId).attr("x1", "0%").attr("x2", "100%");
46600
- grad.append("stop").attr("offset", "0%").attr("stop-color", mix(ramp.hue, palette.bg, 15));
46601
- grad.append("stop").attr("offset", "100%").attr("stop-color", ramp.hue);
46602
- block.append("rect").attr("width", 80).attr("height", 8).attr("fill", `url(#${gradId})`);
46603
- block.append("text").attr("x", 0).attr("y", 22).attr("font-size", 9).attr("fill", palette.textMuted).text(String(ramp.min));
46604
- 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));
46605
- if (ramp.metric) {
46606
- block.append("text").attr("x", 0).attr("y", -4).attr("font-size", 9).attr("fill", palette.textMuted).text(ramp.metric);
46607
- }
46608
- xCursor += 110;
46609
- });
46610
- }
46611
- if (legend.size) {
46612
- const sz = legend.size;
46613
- blocks.push(() => {
46614
- const block = g.append("g").attr("transform", `translate(${xCursor},0)`);
46615
- [3, 6, 10].forEach((r, i) => {
46616
- block.append("circle").attr("cx", i * 26 + r).attr("cy", 8).attr("r", r).attr("fill", "none").attr("stroke", palette.textMuted);
46617
- });
46618
- block.append("text").attr("x", 0).attr("y", -4).attr("font-size", 9).attr("fill", palette.textMuted).text(sz.metric ?? "size");
46619
- xCursor += 110;
46620
- });
46621
- }
46622
- if (legend.weight) {
46623
- const wt = legend.weight;
46624
- blocks.push(() => {
46625
- const block = g.append("g").attr("transform", `translate(${xCursor},0)`);
46626
- [1, 3, 6].forEach((w, i) => {
46627
- 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);
46628
- });
46629
- block.append("text").attr("x", 0).attr("y", -4).attr("font-size", 9).attr("fill", palette.textMuted).text(wt.metric ?? "weight");
46630
- xCursor += 110;
46631
- });
46632
- }
46633
- for (const draw of blocks) draw();
47240
+ return t;
46634
47241
  }
46635
47242
  var d3Selection18, LABEL_FONT;
46636
47243
  var init_renderer16 = __esm({