@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/auto.mjs 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;
@@ -3179,6 +3206,57 @@ var init_legend_constants = __esm({
3179
3206
  });
3180
3207
 
3181
3208
  // src/utils/legend-layout.ts
3209
+ function fmtRamp(n) {
3210
+ return Number.isInteger(n) ? String(n) : String(Math.round(n * 10) / 10);
3211
+ }
3212
+ function gradientCapsuleWidth(name, gradient) {
3213
+ const pw = pillWidth(name);
3214
+ const minW = measureLegendText(fmtRamp(gradient.min), LEGEND_ENTRY_FONT_SIZE);
3215
+ const maxW = measureLegendText(fmtRamp(gradient.max), LEGEND_ENTRY_FONT_SIZE);
3216
+ return LEGEND_CAPSULE_PAD + pw + 4 + minW + RAMP_LABEL_GAP + RAMP_LEGEND_W + RAMP_LABEL_GAP + maxW + LEGEND_CAPSULE_PAD;
3217
+ }
3218
+ function buildGradientCapsuleLayout(group, gradient) {
3219
+ const pw = pillWidth(group.name);
3220
+ const minText = fmtRamp(gradient.min);
3221
+ const maxText = fmtRamp(gradient.max);
3222
+ const minW = measureLegendText(minText, LEGEND_ENTRY_FONT_SIZE);
3223
+ const gx = LEGEND_CAPSULE_PAD + pw + 4;
3224
+ const minX = gx;
3225
+ const rampX = gx + minW + RAMP_LABEL_GAP;
3226
+ const maxX = rampX + RAMP_LEGEND_W + RAMP_LABEL_GAP;
3227
+ const width = gradientCapsuleWidth(group.name, gradient);
3228
+ return {
3229
+ groupName: group.name,
3230
+ x: 0,
3231
+ y: 0,
3232
+ width,
3233
+ height: LEGEND_HEIGHT,
3234
+ pill: {
3235
+ groupName: group.name,
3236
+ x: LEGEND_CAPSULE_PAD,
3237
+ y: LEGEND_CAPSULE_PAD,
3238
+ width: pw,
3239
+ height: LEGEND_HEIGHT - LEGEND_CAPSULE_PAD * 2,
3240
+ isActive: true
3241
+ },
3242
+ entries: [],
3243
+ gradient: {
3244
+ rampX,
3245
+ rampY: (LEGEND_HEIGHT - RAMP_LEGEND_H) / 2,
3246
+ rampW: RAMP_LEGEND_W,
3247
+ rampH: RAMP_LEGEND_H,
3248
+ min: gradient.min,
3249
+ max: gradient.max,
3250
+ minText,
3251
+ minX,
3252
+ maxText,
3253
+ maxX,
3254
+ textY: LEGEND_HEIGHT / 2,
3255
+ hue: gradient.hue,
3256
+ base: gradient.base
3257
+ }
3258
+ };
3259
+ }
3182
3260
  function pillWidth(name) {
3183
3261
  return measureLegendText(name, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD;
3184
3262
  }
@@ -3321,7 +3399,7 @@ function computeLegendLayout(config, state, containerWidth) {
3321
3399
  };
3322
3400
  }
3323
3401
  const controlsGroupLayout = isExport ? void 0 : buildControlsGroupLayout(config, state);
3324
- const visibleGroups = config.showEmptyGroups ? groups : groups.filter((g) => g.entries.length > 0);
3402
+ const visibleGroups = config.showEmptyGroups ? groups : groups.filter((g) => g.entries.length > 0 || !!g.gradient);
3325
3403
  if (visibleGroups.length === 0 && (!configControls || configControls.length === 0) && !controlsGroupLayout) {
3326
3404
  return {
3327
3405
  height: 0,
@@ -3400,7 +3478,7 @@ function computeLegendLayout(config, state, containerWidth) {
3400
3478
  groupAvailW,
3401
3479
  config.capsulePillAddonWidth ?? 0
3402
3480
  );
3403
- } else if (!activeGroupName) {
3481
+ } else if (!activeGroupName || config.showInactivePills) {
3404
3482
  const pw = pillWidth(g.name);
3405
3483
  pills.push({
3406
3484
  groupName: g.name,
@@ -3438,6 +3516,7 @@ function computeLegendLayout(config, state, containerWidth) {
3438
3516
  };
3439
3517
  }
3440
3518
  function buildCapsuleLayout(group, containerWidth, addonWidth = 0) {
3519
+ if (group.gradient) return buildGradientCapsuleLayout(group, group.gradient);
3441
3520
  const pw = pillWidth(group.name);
3442
3521
  const info = capsuleWidth(
3443
3522
  group.name,
@@ -3608,7 +3687,7 @@ function getMaxLegendReservedHeight(config, containerWidth) {
3608
3687
  }
3609
3688
  return max;
3610
3689
  }
3611
- var CONTROL_PILL_PAD, CONTROL_FONT_SIZE, CONTROL_ICON_GAP, CONTROL_GAP;
3690
+ var CONTROL_PILL_PAD, CONTROL_FONT_SIZE, CONTROL_ICON_GAP, CONTROL_GAP, RAMP_LEGEND_W, RAMP_LEGEND_H, RAMP_LABEL_GAP;
3612
3691
  var init_legend_layout = __esm({
3613
3692
  "src/utils/legend-layout.ts"() {
3614
3693
  "use strict";
@@ -3617,6 +3696,9 @@ var init_legend_layout = __esm({
3617
3696
  CONTROL_FONT_SIZE = 11;
3618
3697
  CONTROL_ICON_GAP = 4;
3619
3698
  CONTROL_GAP = 8;
3699
+ RAMP_LEGEND_W = 80;
3700
+ RAMP_LEGEND_H = 8;
3701
+ RAMP_LABEL_GAP = 6;
3620
3702
  }
3621
3703
  });
3622
3704
 
@@ -3704,6 +3786,16 @@ function renderCapsule(parent, capsule, palette, groupBg, pillBorder, _isDark, c
3704
3786
  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);
3705
3787
  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);
3706
3788
  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);
3789
+ if (capsule.gradient) {
3790
+ const gr = capsule.gradient;
3791
+ const gradId = `dgmo-legend-ramp-${capsule.groupName.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
3792
+ const def = g.append("defs").append("linearGradient").attr("id", gradId);
3793
+ def.append("stop").attr("offset", "0%").attr("stop-color", mix(gr.hue, gr.base, 15));
3794
+ def.append("stop").attr("offset", "100%").attr("stop-color", gr.hue);
3795
+ 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);
3796
+ 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})`);
3797
+ 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);
3798
+ }
3707
3799
  for (const entry of capsule.entries) {
3708
3800
  const entryG = g.append("g").attr("data-legend-entry", entry.value.toLowerCase()).attr("data-series-name", entry.value).style("cursor", "pointer");
3709
3801
  entryG.append("circle").attr("cx", entry.dotCx).attr("cy", entry.dotCy).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
@@ -11195,23 +11287,22 @@ function parseC4(content, palette) {
11195
11287
  }
11196
11288
  }
11197
11289
  const parent = findParentElement(indent, stack);
11198
- if (parent) {
11199
- const descResult = tryStripDescriptionKeyword(trimmed);
11290
+ const descResult = tryStripDescriptionKeyword(trimmed);
11291
+ if (parent && descResult.isKeyword) {
11200
11292
  if (descResult.needsColon) {
11201
11293
  pushError(
11202
11294
  lineNumber,
11203
- `Use "description: ${descResult.text}" \u2014 colon is required.`,
11295
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`,
11204
11296
  "warning"
11205
11297
  );
11206
11298
  }
11207
- const descText = descResult.isKeyword ? descResult.text : trimmed;
11208
11299
  let desc = elementDescription.get(parent.element);
11209
11300
  if (!desc) {
11210
11301
  desc = [];
11211
11302
  elementDescription.set(parent.element, desc);
11212
11303
  parent.element.description = desc;
11213
11304
  }
11214
- desc.push(descText);
11305
+ desc.push(descResult.text);
11215
11306
  } else {
11216
11307
  pushError(lineNumber, `Unexpected content: "${trimmed}"`);
11217
11308
  }
@@ -11678,7 +11769,7 @@ function parseSitemap(content, palette) {
11678
11769
  if (descResult.needsColon) {
11679
11770
  pushWarning(
11680
11771
  lineNumber,
11681
- `Use "description: ${descResult.text}" \u2014 colon is required.`
11772
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`
11682
11773
  );
11683
11774
  }
11684
11775
  const parent = findParentNode(indent, indentStack);
@@ -12506,23 +12597,22 @@ function parseInfra(content) {
12506
12597
  }
12507
12598
  }
12508
12599
  const descResult = tryStripDescriptionKeyword(trimmed);
12509
- if (descResult.isKeyword && currentNode.isEdge) {
12510
- continue;
12511
- }
12512
- if (!currentNode.isEdge) {
12600
+ if (descResult.isKeyword) {
12601
+ if (currentNode.isEdge) {
12602
+ continue;
12603
+ }
12513
12604
  if (descResult.needsColon) {
12514
12605
  warn2(
12515
12606
  lineNumber,
12516
- `Use "description: ${descResult.text}" \u2014 colon is required.`
12607
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`
12517
12608
  );
12518
12609
  }
12519
- const descText = descResult.isKeyword ? descResult.text : trimmed;
12520
- pushDescription(currentNode, descText);
12610
+ pushDescription(currentNode, descResult.text);
12521
12611
  continue;
12522
12612
  }
12523
12613
  warn2(
12524
12614
  lineNumber,
12525
- `Unexpected line inside component '${currentNode.label}'. Expected a property (key: value), connection (-> Target), or description text.`
12615
+ `Unexpected line inside component '${currentNode.label}'. Expected a property (key: value), connection (-> Target), or a description (description: text).`
12526
12616
  );
12527
12617
  continue;
12528
12618
  }
@@ -15659,9 +15749,6 @@ function parseMap(content) {
15659
15749
  const pushWarning = (line12, message) => {
15660
15750
  diagnostics.push(makeDgmoError(line12, message, "warning"));
15661
15751
  };
15662
- const pushInfo = (line12, message) => {
15663
- diagnostics.push(makeDgmoError(line12, message, "warning"));
15664
- };
15665
15752
  const lines = content.split("\n");
15666
15753
  let firstIdx = 0;
15667
15754
  while (firstIdx < lines.length) {
@@ -15791,10 +15878,15 @@ function parseMap(content) {
15791
15878
  break;
15792
15879
  case "projection":
15793
15880
  dup(d.projection);
15794
- if (value && !["natural-earth", "albers-usa", "mercator"].includes(value))
15881
+ if (value && ![
15882
+ "equirectangular",
15883
+ "natural-earth",
15884
+ "albers-usa",
15885
+ "mercator"
15886
+ ].includes(value))
15795
15887
  pushWarning(
15796
15888
  line12,
15797
- `Unknown projection "${value}" (expected natural-earth | albers-usa | mercator).`
15889
+ `Unknown projection "${value}" (expected equirectangular | natural-earth | albers-usa | mercator).`
15798
15890
  );
15799
15891
  d.projection = value;
15800
15892
  break;
@@ -15933,17 +16025,21 @@ function parseMap(content) {
15933
16025
  scoreNum = void 0;
15934
16026
  }
15935
16027
  }
15936
- if (scoreNum !== void 0 && Object.keys(tags).length)
15937
- pushInfo(
15938
- line12,
15939
- "A region has both `score:` and a tag value \u2014 v1 renders only the score (bivariate is a future seam)."
15940
- );
16028
+ let regionName = split.name;
16029
+ let regionScope;
16030
+ const rToks = regionName.split(/\s+/);
16031
+ const rLast = rToks[rToks.length - 1];
16032
+ if (rToks.length > 1 && SCOPE_RE.test(rLast)) {
16033
+ regionName = rToks.slice(0, -1).join(" ");
16034
+ regionScope = rLast;
16035
+ }
15941
16036
  const region = {
15942
- name: split.name,
16037
+ name: regionName,
15943
16038
  tags,
15944
16039
  meta,
15945
16040
  lineNumber: line12
15946
16041
  };
16042
+ if (regionScope !== void 0) region.scope = regionScope;
15947
16043
  if (scoreNum !== void 0) region.score = scoreNum;
15948
16044
  regions.push(region);
15949
16045
  }
@@ -17067,7 +17163,7 @@ function parseMindmap(content, palette) {
17067
17163
  if (descResult.needsColon) {
17068
17164
  pushWarning(
17069
17165
  lineNumber,
17070
- `Use "description: ${descResult.text}" \u2014 colon is required.`
17166
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`
17071
17167
  );
17072
17168
  }
17073
17169
  const parent = findMetadataParent2(indent, indentStack);
@@ -18855,7 +18951,7 @@ function parseJourneyMap(content, palette) {
18855
18951
  if (descResult.needsColon) {
18856
18952
  warn2(
18857
18953
  lineNumber,
18858
- `Use "description: ${descResult.text}" \u2014 colon is required.`
18954
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`
18859
18955
  );
18860
18956
  }
18861
18957
  currentStep.description = descResult.text;
@@ -45444,7 +45540,9 @@ function resolveMap(parsed, data) {
45444
45540
  const usScoped = parsed.directives.region === "us-states" || parsed.directives.defaultCountry?.toUpperCase() === "US" || parsed.regions.some((r) => {
45445
45541
  const f = fold(r.name);
45446
45542
  return usStateIndex.has(f) && !countryIndex.has(f);
45447
- }) || parsed.pois.some(
45543
+ }) || parsed.regions.some(
45544
+ (r) => r.scope === "US" || r.scope?.startsWith("US-")
45545
+ ) || parsed.pois.some(
45448
45546
  (p) => p.pos.kind === "name" && p.pos.scope?.startsWith("US-")
45449
45547
  );
45450
45548
  const regions = [];
@@ -45456,7 +45554,30 @@ function resolveMap(parsed, data) {
45456
45554
  const inCountry = countryIndex.get(f) ?? (REGION_ALIASES[f] ? countryIndex.get(REGION_ALIASES[f]) : void 0);
45457
45555
  const inState = usStateIndex.get(f);
45458
45556
  let chosen = null;
45459
- if (inCountry && inState) {
45557
+ const scope = r.scope;
45558
+ if (scope) {
45559
+ const wantsState = scope === "US" || scope.startsWith("US-");
45560
+ if (wantsState && inState) {
45561
+ if (scope.startsWith("US-") && inState.id !== scope) {
45562
+ err(
45563
+ r.lineNumber,
45564
+ `No subdivision "${r.name}" in scope ${scope} (it is ${inState.id}).`,
45565
+ "E_MAP_SCOPE_MISS"
45566
+ );
45567
+ continue;
45568
+ }
45569
+ chosen = { ...inState, layer: "us-state" };
45570
+ } else if (!wantsState && inCountry) {
45571
+ chosen = { ...inCountry, layer: "country" };
45572
+ } else {
45573
+ err(
45574
+ r.lineNumber,
45575
+ `No region "${r.name}" found in scope ${scope}.`,
45576
+ "E_MAP_SCOPE_MISS"
45577
+ );
45578
+ continue;
45579
+ }
45580
+ } else if (inCountry && inState) {
45460
45581
  if (usScoped) {
45461
45582
  chosen = { ...inState, layer: "us-state" };
45462
45583
  } else {
@@ -45464,7 +45585,7 @@ function resolveMap(parsed, data) {
45464
45585
  }
45465
45586
  warn2(
45466
45587
  r.lineNumber,
45467
- `"${r.name}" is both a country and a US state \u2014 resolved as ${chosen.layer} (${chosen.id}).`,
45588
+ `"${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}").`,
45468
45589
  "W_MAP_REGION_AMBIGUOUS"
45469
45590
  );
45470
45591
  } else if (inState) {
@@ -45636,9 +45757,17 @@ function resolveMap(parsed, data) {
45636
45757
  };
45637
45758
  registerPoi(id, poi, p.lineNumber);
45638
45759
  }
45760
+ const declaredByName = /* @__PURE__ */ new Map();
45761
+ for (const p of pois) {
45762
+ const fn = p.name ? fold(p.name) : void 0;
45763
+ if (fn && fn !== p.id && !declaredByName.has(fn))
45764
+ declaredByName.set(fn, p.id);
45765
+ }
45639
45766
  const resolveEndpoint2 = (ref, line12) => {
45640
45767
  const f = fold(ref);
45641
45768
  if (registry.has(f)) return f;
45769
+ const aliased = declaredByName.get(f);
45770
+ if (aliased) return aliased;
45642
45771
  const got = lookupName(ref, void 0, line12, inferredCountry, true);
45643
45772
  if (got.kind !== "ok") return null;
45644
45773
  noteCountry(got.iso);
@@ -45718,23 +45847,29 @@ function resolveMap(parsed, data) {
45718
45847
  [-180, -85],
45719
45848
  [180, 85]
45720
45849
  ];
45721
- const extent2 = unioned ? pad(unioned, PAD_FRACTION) : DEFAULT_EXTENT;
45850
+ let extent2 = unioned ? pad(unioned, PAD_FRACTION) : DEFAULT_EXTENT;
45722
45851
  const lonSpan = extent2[1][0] - extent2[0][0];
45723
45852
  const latSpan = extent2[1][1] - extent2[0][1];
45724
45853
  const span = Math.max(lonSpan, latSpan);
45725
45854
  const usDominant = (inferredCountry === "US" || subdivisions.includes("us-states")) && !regions.some((r) => r.layer === "country" && r.iso !== "US") && !anyNonUsPoi;
45726
45855
  let projection;
45727
45856
  const override = parsed.directives.projection;
45728
- if (override === "natural-earth" || override === "albers-usa" || override === "mercator") {
45857
+ if (override === "equirectangular" || override === "natural-earth" || override === "albers-usa" || override === "mercator") {
45729
45858
  projection = override;
45730
45859
  } else if (usDominant) {
45731
45860
  projection = "albers-usa";
45732
45861
  } else if (span > WORLD_SPAN) {
45733
- projection = "natural-earth";
45862
+ projection = "equirectangular";
45734
45863
  } else if (span < MERCATOR_MAX_SPAN) {
45735
45864
  projection = "mercator";
45736
45865
  } else {
45737
- projection = "natural-earth";
45866
+ projection = "equirectangular";
45867
+ }
45868
+ if (lonSpan >= 180) {
45869
+ extent2 = [
45870
+ [-180, Math.min(extent2[0][1], WORLD_LAT_SOUTH)],
45871
+ [180, Math.max(extent2[1][1], WORLD_LAT_NORTH)]
45872
+ ];
45738
45873
  }
45739
45874
  result.regions = regions;
45740
45875
  result.pois = pois;
@@ -45782,7 +45917,7 @@ function firstError(diags) {
45782
45917
  const e = diags.find((d) => d.severity === "error");
45783
45918
  return e ? formatDgmoError(e) : null;
45784
45919
  }
45785
- var WORLD_SPAN, MERCATOR_MAX_SPAN, PAD_FRACTION, REGION_ALIASES;
45920
+ var WORLD_SPAN, MERCATOR_MAX_SPAN, PAD_FRACTION, WORLD_LAT_SOUTH, WORLD_LAT_NORTH, REGION_ALIASES;
45786
45921
  var init_resolver2 = __esm({
45787
45922
  "src/map/resolver.ts"() {
45788
45923
  "use strict";
@@ -45791,6 +45926,8 @@ var init_resolver2 = __esm({
45791
45926
  WORLD_SPAN = 90;
45792
45927
  MERCATOR_MAX_SPAN = 25;
45793
45928
  PAD_FRACTION = 0.05;
45929
+ WORLD_LAT_SOUTH = -58;
45930
+ WORLD_LAT_NORTH = 78;
45794
45931
  REGION_ALIASES = {
45795
45932
  // Common everyday names → the Natural-Earth display name actually shipped.
45796
45933
  "united states": "united states of america",
@@ -45858,13 +45995,36 @@ function moduleBaseDir() {
45858
45995
  function loadMapData() {
45859
45996
  cache ??= (async () => {
45860
45997
  const dir = await firstExistingDir(moduleBaseDir());
45861
- const [worldCoarse, worldDetail, usStates, gazetteer] = await Promise.all([
45998
+ const [
45999
+ worldCoarse,
46000
+ worldDetail,
46001
+ usStates,
46002
+ lakes,
46003
+ rivers,
46004
+ naLand,
46005
+ naLakes,
46006
+ gazetteer
46007
+ ] = await Promise.all([
45862
46008
  readJson(dir, FILES.worldCoarse),
45863
46009
  readJson(dir, FILES.worldDetail),
45864
46010
  readJson(dir, FILES.usStates),
46011
+ // Lakes/rivers/NA assets are optional — older bundles may predate them.
46012
+ readJson(dir, FILES.lakes).catch(() => void 0),
46013
+ readJson(dir, FILES.rivers).catch(() => void 0),
46014
+ readJson(dir, FILES.naLand).catch(() => void 0),
46015
+ readJson(dir, FILES.naLakes).catch(() => void 0),
45865
46016
  readJson(dir, FILES.gazetteer)
45866
46017
  ]);
45867
- return validate({ worldCoarse, worldDetail, usStates, gazetteer });
46018
+ return validate({
46019
+ worldCoarse,
46020
+ worldDetail,
46021
+ usStates,
46022
+ gazetteer,
46023
+ ...lakes && { lakes },
46024
+ ...rivers && { rivers },
46025
+ ...naLand && { naLand },
46026
+ ...naLakes && { naLakes }
46027
+ });
45868
46028
  })().catch((e) => {
45869
46029
  cache = void 0;
45870
46030
  throw e;
@@ -45879,6 +46039,10 @@ var init_load_data = __esm({
45879
46039
  worldCoarse: "world-coarse.json",
45880
46040
  worldDetail: "world-detail.json",
45881
46041
  usStates: "us-states.json",
46042
+ lakes: "lakes.json",
46043
+ rivers: "rivers.json",
46044
+ naLand: "na-land.json",
46045
+ naLakes: "na-lakes.json",
45882
46046
  gazetteer: "gazetteer.json"
45883
46047
  };
45884
46048
  CANDIDATE_DIRS = [
@@ -45894,8 +46058,11 @@ var init_load_data = __esm({
45894
46058
  import {
45895
46059
  geoPath,
45896
46060
  geoNaturalEarth1,
45897
- geoAlbersUsa,
45898
- geoMercator
46061
+ geoEquirectangular,
46062
+ geoConicEqualArea,
46063
+ geoMercator,
46064
+ geoBounds as geoBounds2,
46065
+ geoTransform
45899
46066
  } from "d3-geo";
45900
46067
  import { feature as feature2 } from "topojson-client";
45901
46068
  function geomObject2(topo) {
@@ -45913,36 +46080,67 @@ function decodeLayer(topo) {
45913
46080
  function projectionFor(family) {
45914
46081
  switch (family) {
45915
46082
  case "albers-usa":
45916
- return geoAlbersUsa();
46083
+ return usConusProjection();
45917
46084
  case "mercator":
45918
46085
  return geoMercator();
45919
46086
  case "natural-earth":
45920
- default:
45921
46087
  return geoNaturalEarth1();
46088
+ case "equirectangular":
46089
+ default:
46090
+ return geoEquirectangular();
45922
46091
  }
45923
46092
  }
46093
+ function mapBackgroundColor(palette) {
46094
+ return mix(palette.colors.blue, palette.bg, WATER_TINT);
46095
+ }
45924
46096
  function layoutMap(resolved, data, size, opts) {
45925
46097
  const { palette, isDark } = opts;
45926
46098
  const { width, height } = size;
45927
- const worldTopo = resolved.basemaps.world === "detail" ? data.worldDetail : data.worldCoarse;
46099
+ const wantsUsStates = resolved.basemaps.subdivisions.includes("us-states");
46100
+ const usCrisp = resolved.projection === "albers-usa" && wantsUsStates && !!data.naLand;
46101
+ const worldTopo = usCrisp ? data.naLand : resolved.basemaps.world === "detail" ? data.worldDetail : data.worldCoarse;
45928
46102
  const worldLayer = decodeLayer(worldTopo);
45929
- const usLayer = resolved.basemaps.subdivisions.includes("us-states") ? decodeLayer(data.usStates) : null;
45930
- const neutralFill = mix(palette.border, palette.bg, 32);
45931
- const regionStroke = mix(palette.border, palette.bg, 70);
46103
+ const usLayer = wantsUsStates ? decodeLayer(data.usStates) : null;
46104
+ const landTint = isDark ? LAND_TINT_DARK : LAND_TINT_LIGHT;
46105
+ const neutralFill = mix(palette.colors.green, palette.bg, landTint);
46106
+ const water = mapBackgroundColor(palette);
46107
+ const usContext = usLayer !== null;
46108
+ const foreignFill = mix(
46109
+ palette.colors.gray,
46110
+ palette.bg,
46111
+ isDark ? FOREIGN_TINT_DARK : FOREIGN_TINT_LIGHT
46112
+ );
46113
+ const regionStroke = isDark ? mix(palette.bg, palette.text, 78) : mix(palette.text, palette.bg, 78);
45932
46114
  const scores = resolved.regions.filter((r) => r.score !== void 0).map((r) => r.score);
45933
46115
  const scaleOverride = resolved.directives.scale;
45934
46116
  const rampMin = scaleOverride ? scaleOverride.min : Math.min(...scores);
45935
46117
  const rampMax = scaleOverride ? scaleOverride.max : Math.max(...scores);
45936
- const rampHue = palette.primary;
46118
+ const rampHue = palette.colors.red;
45937
46119
  const hasRamp = scores.length > 0;
45938
- const activeGroup = resolveActiveTagGroup(
45939
- resolved.tagGroups,
45940
- resolved.directives.activeTag
45941
- );
46120
+ const SCORE_NAME = hasRamp ? resolved.directives.metric?.trim() || "Score" : null;
46121
+ const matchColorGroup = (v) => {
46122
+ const lv = v.trim().toLowerCase();
46123
+ if (lv === "none") return null;
46124
+ if (SCORE_NAME && (lv === "score" || lv === SCORE_NAME.toLowerCase()))
46125
+ return SCORE_NAME;
46126
+ const tg = resolved.tagGroups.find((g) => g.name.toLowerCase() === lv);
46127
+ return tg ? tg.name : v;
46128
+ };
46129
+ const override = opts.activeGroup;
46130
+ let activeGroup;
46131
+ if (override !== void 0) {
46132
+ activeGroup = override === null ? null : matchColorGroup(override);
46133
+ } else if (resolved.directives.activeTag !== void 0) {
46134
+ activeGroup = matchColorGroup(resolved.directives.activeTag);
46135
+ } else {
46136
+ activeGroup = SCORE_NAME ?? (resolved.tagGroups.length > 0 ? resolved.tagGroups[0].name : null);
46137
+ }
46138
+ const activeIsScore = SCORE_NAME !== null && activeGroup === SCORE_NAME;
46139
+ const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
45942
46140
  const fillForScore = (s) => {
45943
46141
  const t = rampMax > rampMin ? (s - rampMin) / (rampMax - rampMin) : 1;
45944
46142
  const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
45945
- return mix(rampHue, isDark ? palette.surface : palette.bg, pct);
46143
+ return mix(rampHue, rampBase, pct);
45946
46144
  };
45947
46145
  const tagFill = (tags, groupName) => {
45948
46146
  if (!groupName) return null;
@@ -45956,40 +46154,40 @@ function layoutMap(resolved, data, size, opts) {
45956
46154
  (e) => e.value.toLowerCase() === val.toLowerCase()
45957
46155
  );
45958
46156
  if (!entry?.color) return null;
45959
- return shapeFill(palette, entry.color, isDark);
46157
+ return mix(
46158
+ entry.color,
46159
+ palette.bg,
46160
+ isDark ? TAG_TINT_DARK : TAG_TINT_LIGHT
46161
+ );
46162
+ };
46163
+ const regionFill = (r) => {
46164
+ if (activeIsScore) {
46165
+ return r.score !== void 0 ? fillForScore(r.score) : neutralFill;
46166
+ }
46167
+ return tagFill(r.tags, activeGroup) ?? neutralFill;
45960
46168
  };
45961
46169
  const regionById = new Map(resolved.regions.map((r) => [r.iso, r]));
45962
- const regionFeatures = [];
45963
- for (const r of resolved.regions) {
45964
- const f = r.layer === "us-state" ? usLayer?.get(r.iso) : worldLayer.get(r.iso);
45965
- if (f) regionFeatures.push(f);
45966
- }
45967
- const extentCorners = () => {
46170
+ const extentOutline = () => {
45968
46171
  const [[w, s], [e, n]] = resolved.extent;
46172
+ const N = 16;
46173
+ const coords = [];
46174
+ for (let i = 0; i <= N; i++) {
46175
+ const t = i / N;
46176
+ const lon = w + (e - w) * t;
46177
+ const lat = s + (n - s) * t;
46178
+ coords.push([lon, s], [lon, n], [w, lat], [e, lat]);
46179
+ }
45969
46180
  return {
45970
46181
  type: "Feature",
45971
46182
  properties: {},
45972
- geometry: {
45973
- type: "MultiPoint",
45974
- coordinates: [
45975
- [w, s],
45976
- [e, s],
45977
- [e, n],
45978
- [w, n]
45979
- ]
45980
- }
46183
+ geometry: { type: "MultiPoint", coordinates: coords }
45981
46184
  };
45982
46185
  };
45983
46186
  let fitFeatures;
45984
- if (resolved.projection === "albers-usa") {
45985
- if (regionFeatures.length > 0) fitFeatures = regionFeatures;
45986
- else if (usLayer) fitFeatures = [...usLayer.values()];
45987
- else {
45988
- const us = worldLayer.get("US");
45989
- fitFeatures = us ? [us] : [...worldLayer.values()];
45990
- }
46187
+ if (resolved.projection === "albers-usa" && usLayer) {
46188
+ fitFeatures = [...usLayer.entries()].filter(([iso]) => !US_NON_CONUS.has(iso)).map(([, f]) => f);
45991
46189
  } else {
45992
- fitFeatures = [extentCorners()];
46190
+ fitFeatures = [extentOutline()];
45993
46191
  }
45994
46192
  const fitTarget = { type: "FeatureCollection", features: fitFeatures };
45995
46193
  const projection = projectionFor(resolved.projection);
@@ -45998,32 +46196,311 @@ function layoutMap(resolved, data, size, opts) {
45998
46196
  if (centerLon > 180) centerLon -= 360;
45999
46197
  projection.rotate([-centerLon, 0]);
46000
46198
  }
46001
- projection.fitExtent(
46199
+ const TITLE_GAP = 16;
46200
+ let topPad = FIT_PAD;
46201
+ if (resolved.title && resolved.pois.length > 0) {
46202
+ const bannerBottom = (resolved.subtitle ? TITLE_Y + TITLE_FONT_SIZE : TITLE_Y) + TITLE_FONT_SIZE / 2;
46203
+ topPad = Math.max(FIT_PAD, bannerBottom + TITLE_GAP);
46204
+ }
46205
+ const fitBox = [
46206
+ [FIT_PAD, topPad],
46002
46207
  [
46003
- [FIT_PAD, FIT_PAD],
46004
- [
46005
- Math.max(FIT_PAD + 1, width - FIT_PAD),
46006
- Math.max(FIT_PAD + 1, height - FIT_PAD)
46007
- ]
46008
- ],
46009
- fitTarget
46010
- );
46011
- const path = geoPath(projection);
46012
- const project = (lon, lat) => projection([lon, lat]) ?? null;
46208
+ Math.max(FIT_PAD + 1, width - FIT_PAD),
46209
+ Math.max(topPad + 1, height - FIT_PAD)
46210
+ ]
46211
+ ];
46212
+ projection.fitExtent(fitBox, fitTarget);
46213
+ const fitGB = geoBounds2(fitTarget);
46214
+ const fitIsGlobal = fitGB[1][0] - fitGB[0][0] >= 270 || fitGB[1][1] - fitGB[0][1] >= 130;
46215
+ let path;
46216
+ let project;
46217
+ if (fitIsGlobal) {
46218
+ const cb = geoPath(projection).bounds(fitTarget);
46219
+ const bx0 = cb[0][0];
46220
+ const by0 = cb[0][1];
46221
+ const cw = cb[1][0] - bx0;
46222
+ const ch = cb[1][1] - by0;
46223
+ const ox = fitBox[0][0];
46224
+ const oy = fitBox[0][1];
46225
+ const sx = cw > 0 ? (fitBox[1][0] - ox) / cw : 1;
46226
+ const sy = ch > 0 ? (fitBox[1][1] - oy) / ch : 1;
46227
+ const stretch = (x, y) => [
46228
+ ox + (x - bx0) * sx,
46229
+ oy + (y - by0) * sy
46230
+ ];
46231
+ const baseProjection = projection;
46232
+ const tx = geoTransform({
46233
+ point(x, y) {
46234
+ const [px, py] = stretch(x, y);
46235
+ this.stream.point(px, py);
46236
+ }
46237
+ });
46238
+ path = geoPath({
46239
+ stream: (s) => baseProjection.stream(
46240
+ tx.stream(s)
46241
+ )
46242
+ });
46243
+ project = (lon, lat) => {
46244
+ const p = baseProjection([lon, lat]);
46245
+ return p ? stretch(p[0], p[1]) : null;
46246
+ };
46247
+ } else {
46248
+ path = geoPath(projection);
46249
+ project = (lon, lat) => projection([lon, lat]) ?? null;
46250
+ }
46251
+ const insets = [];
46252
+ const insetRegions = [];
46253
+ const insetLabelSeeds = [];
46254
+ if (resolved.projection === "albers-usa" && usLayer) {
46255
+ const PAD = 8;
46256
+ const GAP = 12;
46257
+ const yB = height - FIT_PAD;
46258
+ const BW = 8;
46259
+ const coast = /* @__PURE__ */ new Map();
46260
+ const addPt = (lon, lat) => {
46261
+ const p = projection([lon, lat]);
46262
+ if (!p) return;
46263
+ const bi = Math.floor(p[0] / BW);
46264
+ const cur = coast.get(bi);
46265
+ if (cur === void 0 || p[1] > cur) coast.set(bi, p[1]);
46266
+ };
46267
+ const walk = (co) => {
46268
+ if (Array.isArray(co) && typeof co[0] === "number")
46269
+ addPt(co[0], co[1]);
46270
+ else if (Array.isArray(co)) for (const c of co) walk(c);
46271
+ };
46272
+ for (const [iso, f] of usLayer) {
46273
+ if (US_NON_CONUS.has(iso)) continue;
46274
+ walk(f.geometry.coordinates);
46275
+ }
46276
+ const at = (x) => {
46277
+ const bi = Math.floor(x / BW);
46278
+ let y = -Infinity;
46279
+ for (let k = bi - 1; k <= bi + 1; k++) {
46280
+ const v = coast.get(k);
46281
+ if (v !== void 0 && v > y) y = v;
46282
+ }
46283
+ return y;
46284
+ };
46285
+ const coastTop = (x0, xr) => {
46286
+ const n = 24;
46287
+ const pts = [];
46288
+ let maxY = -Infinity;
46289
+ for (let i = 0; i <= n; i++) {
46290
+ const x = x0 + (xr - x0) * i / n;
46291
+ const y = at(x);
46292
+ if (y > -Infinity) {
46293
+ pts.push([x, y]);
46294
+ if (y > maxY) maxY = y;
46295
+ }
46296
+ }
46297
+ if (pts.length === 0) return () => yB - height * 0.42;
46298
+ let m = 0;
46299
+ if (pts.length >= 2) {
46300
+ let sx = 0, sy = 0, sxx = 0, sxy = 0;
46301
+ for (const [x, y] of pts) {
46302
+ sx += x;
46303
+ sy += y;
46304
+ sxx += x * x;
46305
+ sxy += x * y;
46306
+ }
46307
+ const den = pts.length * sxx - sx * sx;
46308
+ if (den !== 0) m = (pts.length * sxy - sx * sy) / den;
46309
+ }
46310
+ m = Math.max(-0.35, Math.min(0.35, m));
46311
+ let c = -Infinity;
46312
+ for (const [x, y] of pts) {
46313
+ const need = y - m * x + GAP;
46314
+ if (need > c) c = need;
46315
+ }
46316
+ return (x) => m * x + c;
46317
+ };
46318
+ const placeInset = (iso, proj, boxX, iwReq) => {
46319
+ const f = usLayer.get(iso);
46320
+ if (!f) return boxX;
46321
+ const x0 = boxX;
46322
+ const iw = Math.min(iwReq, width - FIT_PAD - x0 - 2 * PAD);
46323
+ if (iw < 24) return boxX;
46324
+ const xr = x0 + iw + 2 * PAD;
46325
+ const top = coastTop(x0, xr);
46326
+ const yL = top(x0);
46327
+ const yR = top(xr);
46328
+ proj.fitWidth(iw, f);
46329
+ const bb = geoPath(proj).bounds(f);
46330
+ const sh = Number.isFinite(bb[0][0]) ? bb[1][1] - bb[0][1] : iw;
46331
+ const needH = sh + 2 * PAD;
46332
+ let topFit = Math.max(yL, yR);
46333
+ const bottom = Math.min(topFit + needH, yB);
46334
+ if (bottom - topFit < needH) topFit = bottom - needH;
46335
+ const lift = topFit - Math.max(yL, yR);
46336
+ const topL = yL + lift;
46337
+ const topR = yR + lift;
46338
+ proj.fitExtent(
46339
+ [
46340
+ [x0 + PAD, topFit + PAD],
46341
+ [xr - PAD, bottom - PAD]
46342
+ ],
46343
+ f
46344
+ );
46345
+ const d = geoPath(proj)(f) ?? "";
46346
+ if (!d) return xr;
46347
+ const r = regionById.get(iso);
46348
+ let fill2 = neutralFill;
46349
+ let lineNumber = -1;
46350
+ if (r?.layer === "us-state") {
46351
+ fill2 = regionFill(r);
46352
+ lineNumber = r.lineNumber;
46353
+ }
46354
+ insets.push({
46355
+ x: x0,
46356
+ y: Math.min(topL, topR),
46357
+ w: xr - x0,
46358
+ h: bottom - Math.min(topL, topR),
46359
+ points: [
46360
+ [x0, topL],
46361
+ [xr, topR],
46362
+ [xr, bottom],
46363
+ [x0, bottom]
46364
+ ]
46365
+ });
46366
+ insetRegions.push({
46367
+ id: iso,
46368
+ d,
46369
+ fill: fill2,
46370
+ stroke: regionStroke,
46371
+ lineNumber,
46372
+ layer: "us-state",
46373
+ ...r?.score !== void 0 && { score: r.score },
46374
+ ...r && Object.keys(r.tags).length > 0 && { tags: r.tags }
46375
+ });
46376
+ const ctr = geoPath(proj).centroid(f);
46377
+ if (Number.isFinite(ctr[0])) {
46378
+ const name = f.properties?.name ?? iso;
46379
+ insetLabelSeeds.push({ x: ctr[0], y: ctr[1], iso, name, lineNumber });
46380
+ }
46381
+ return xr;
46382
+ };
46383
+ const akRight = placeInset(
46384
+ "US-AK",
46385
+ alaskaProjection(),
46386
+ FIT_PAD,
46387
+ width * 0.15
46388
+ );
46389
+ placeInset("US-HI", hawaiiProjection(), akRight + 24, width * 0.1);
46390
+ }
46391
+ const conusFit = resolved.projection === "albers-usa" && !!usLayer;
46392
+ const cullExtent = conusFit ? geoBounds2(fitTarget) : resolved.extent;
46393
+ const [[exW, exS], [exE, exN]] = cullExtent;
46394
+ const lonSpan = exE - exW;
46395
+ const latSpan = exN - exS;
46396
+ const isGlobalView = lonSpan >= 270 || latSpan >= 130;
46397
+ const padLon = Math.max(8, lonSpan * 0.35);
46398
+ const padLat = Math.max(8, latSpan * 0.35);
46399
+ const vW = exW - padLon;
46400
+ const vE = exE + padLon;
46401
+ const vS = exS - padLat;
46402
+ const vN = exN + padLat;
46403
+ const vLonCenter = (exW + exE) / 2;
46404
+ const normLon = (lon) => {
46405
+ let L = lon;
46406
+ while (L < vLonCenter - 180) L += 360;
46407
+ while (L > vLonCenter + 180) L -= 360;
46408
+ return L;
46409
+ };
46410
+ const ringOverlapsView = (ring) => {
46411
+ let anyIn = false;
46412
+ let loMin = Infinity, loMax = -Infinity, laMin = Infinity, laMax = -Infinity, rawMin = Infinity, rawMax = -Infinity;
46413
+ for (const [rawLon, lat] of ring) {
46414
+ const lon = normLon(rawLon);
46415
+ if (lon >= vW && lon <= vE && lat >= vS && lat <= vN) anyIn = true;
46416
+ if (lon < loMin) loMin = lon;
46417
+ if (lon > loMax) loMax = lon;
46418
+ if (rawLon < rawMin) rawMin = rawLon;
46419
+ if (rawLon > rawMax) rawMax = rawLon;
46420
+ if (lat < laMin) laMin = lat;
46421
+ if (lat > laMax) laMax = lat;
46422
+ }
46423
+ if (loMax - loMin > 270) return false;
46424
+ if (rawMax - rawMin > 180 && loMax - loMin < 90) return false;
46425
+ if (anyIn) return true;
46426
+ if (loMax - loMin > 180) return false;
46427
+ return !(loMax < vW || loMin > vE || laMax < vS || laMin > vN);
46428
+ };
46429
+ const cullFeatureToView = (f) => {
46430
+ if (isGlobalView) return f;
46431
+ const g = f.geometry;
46432
+ if (!g) return f;
46433
+ if (g.type === "Polygon") {
46434
+ const ring = g.coordinates[0];
46435
+ return ringOverlapsView(ring) ? f : null;
46436
+ }
46437
+ if (g.type === "MultiPolygon") {
46438
+ const polys = g.coordinates;
46439
+ const keep = polys.filter(
46440
+ (p) => ringOverlapsView(p[0])
46441
+ );
46442
+ if (!keep.length) return null;
46443
+ if (keep.length === polys.length) return f;
46444
+ return { ...f, geometry: { ...g, coordinates: keep } };
46445
+ }
46446
+ return f;
46447
+ };
46448
+ const SEAM_SLIVER_MAX_SPAN = 100;
46449
+ const ringIsFrameFiller = (ring) => {
46450
+ const lons = ring.map(([lon]) => lon).sort((a, b) => a - b);
46451
+ if (lons.length < 2) return false;
46452
+ let maxGap = -1;
46453
+ let gapIdx = 0;
46454
+ for (let i = 1; i < lons.length; i++) {
46455
+ const g = lons[i] - lons[i - 1];
46456
+ if (g > maxGap) {
46457
+ maxGap = g;
46458
+ gapIdx = i;
46459
+ }
46460
+ }
46461
+ const wrapGap = lons[0] + 360 - lons[lons.length - 1];
46462
+ if (wrapGap >= maxGap) return false;
46463
+ const span = 360 - maxGap;
46464
+ const east = lons[gapIdx - 1] + 360;
46465
+ return east > 180 && span < SEAM_SLIVER_MAX_SPAN;
46466
+ };
46467
+ const dropFrameFillers = (f) => {
46468
+ const g = f.geometry;
46469
+ if (!g) return f;
46470
+ if (g.type === "Polygon") {
46471
+ const ring = g.coordinates[0];
46472
+ return ringIsFrameFiller(ring) ? null : f;
46473
+ }
46474
+ if (g.type === "MultiPolygon") {
46475
+ const polys = g.coordinates;
46476
+ const keep = polys.filter(
46477
+ (p) => !ringIsFrameFiller(p[0])
46478
+ );
46479
+ if (!keep.length) return null;
46480
+ if (keep.length === polys.length) return f;
46481
+ return { ...f, geometry: { ...g, coordinates: keep } };
46482
+ }
46483
+ return f;
46484
+ };
46013
46485
  const regions = [];
46014
- const pushRegionLayer = (layerFeatures, layerKind) => {
46486
+ const pushRegionLayer = (layerFeatures, layerKind, shouldCull) => {
46015
46487
  for (const [iso, f] of layerFeatures) {
46016
- const d = path(f) ?? "";
46017
- if (!d) continue;
46488
+ if (layerKind === "us-state" && usContext && INSET_STATES.has(iso))
46489
+ continue;
46490
+ if (layerKind === "country" && usContext && iso === "US") continue;
46018
46491
  const r = regionById.get(iso);
46492
+ const viewF = shouldCull ? cullFeatureToView(f) : dropFrameFillers(f);
46493
+ if (!viewF) continue;
46494
+ const d = path(viewF) ?? "";
46495
+ if (!d) continue;
46019
46496
  const isThisLayer = r?.layer === layerKind;
46020
- let fill2 = neutralFill;
46497
+ const isForeign = layerKind === "country" && usContext && iso !== "US";
46498
+ let fill2 = isForeign ? foreignFill : neutralFill;
46021
46499
  let label;
46022
46500
  let lineNumber = -1;
46023
46501
  let layer = "base";
46024
46502
  if (isThisLayer) {
46025
- if (r.score !== void 0) fill2 = fillForScore(r.score);
46026
- else fill2 = tagFill(r.tags, activeGroup) ?? neutralFill;
46503
+ fill2 = regionFill(r);
46027
46504
  lineNumber = r.lineNumber;
46028
46505
  layer = layerKind;
46029
46506
  label = r.name;
@@ -46035,12 +46512,42 @@ function layoutMap(resolved, data, size, opts) {
46035
46512
  stroke: regionStroke,
46036
46513
  lineNumber,
46037
46514
  layer,
46038
- ...label !== void 0 && { label }
46515
+ ...label !== void 0 && { label },
46516
+ ...isThisLayer && r.score !== void 0 && { score: r.score },
46517
+ ...isThisLayer && Object.keys(r.tags).length > 0 && { tags: r.tags }
46039
46518
  });
46040
46519
  }
46041
46520
  };
46042
- pushRegionLayer(worldLayer, "country");
46043
- if (usLayer) pushRegionLayer(usLayer, "us-state");
46521
+ pushRegionLayer(worldLayer, "country", !isGlobalView);
46522
+ if (usLayer) pushRegionLayer(usLayer, "us-state", !conusFit && !isGlobalView);
46523
+ const lakesTopo = usCrisp && data.naLakes ? data.naLakes : data.lakes;
46524
+ if (lakesTopo) {
46525
+ for (const [, f] of decodeLayer(lakesTopo)) {
46526
+ const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
46527
+ if (!viewF) continue;
46528
+ const d = path(viewF) ?? "";
46529
+ if (!d) continue;
46530
+ regions.push({
46531
+ id: "lake",
46532
+ d,
46533
+ fill: water,
46534
+ stroke: "none",
46535
+ lineNumber: -1,
46536
+ layer: "base"
46537
+ });
46538
+ }
46539
+ }
46540
+ const riverColor = water;
46541
+ const rivers = [];
46542
+ if (data.rivers) {
46543
+ for (const [, f] of decodeLayer(data.rivers)) {
46544
+ const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
46545
+ if (!viewF) continue;
46546
+ const d = path(viewF) ?? "";
46547
+ if (!d) continue;
46548
+ rivers.push({ d, color: riverColor, width: RIVER_WIDTH });
46549
+ }
46550
+ }
46044
46551
  const sizeVals = resolved.pois.map((p) => Number(p.meta["size"])).filter((n) => Number.isFinite(n) && n > 0);
46045
46552
  const sizeMin = sizeVals.length ? Math.min(...sizeVals) : 0;
46046
46553
  const sizeMax = sizeVals.length ? Math.max(...sizeVals) : 0;
@@ -46061,8 +46568,8 @@ function layoutMap(resolved, data, size, opts) {
46061
46568
  if (hex) return { fill: hex, stroke: mix(hex, palette.text, 18) };
46062
46569
  }
46063
46570
  return {
46064
- fill: palette.accent,
46065
- stroke: mix(palette.accent, palette.text, 18)
46571
+ fill: palette.colors.orange,
46572
+ stroke: mix(palette.colors.orange, palette.text, 18)
46066
46573
  };
46067
46574
  };
46068
46575
  const routeNumberById = /* @__PURE__ */ new Map();
@@ -46100,7 +46607,7 @@ function layoutMap(resolved, data, size, opts) {
46100
46607
  cy += Math.sin(ang) * COLO_R;
46101
46608
  }
46102
46609
  const { fill: fill2, stroke: stroke2 } = poiFill(e.p);
46103
- poiScreen.set(e.p.id, { cx, cy });
46610
+ poiScreen.set(e.p.id, { cx, cy, r: radiusFor(e.p) });
46104
46611
  const num = routeNumberById.get(e.p.id);
46105
46612
  pois.push({
46106
46613
  id: e.p.id,
@@ -46117,17 +46624,36 @@ function layoutMap(resolved, data, size, opts) {
46117
46624
  });
46118
46625
  }
46119
46626
  const legs = [];
46627
+ const RIM_GAP = 1.5;
46120
46628
  const legPath = (a, b, curved, offset) => {
46121
- if (!curved && offset === 0) return `M${a.cx},${a.cy}L${b.cx},${b.cy}`;
46122
46629
  const mx = (a.cx + b.cx) / 2;
46123
46630
  const my = (a.cy + b.cy) / 2;
46124
46631
  const dx = b.cx - a.cx;
46125
46632
  const dy = b.cy - a.cy;
46126
46633
  const len = Math.hypot(dx, dy) || 1;
46634
+ const trimA = Math.min(a.r + RIM_GAP, len * 0.45);
46635
+ const trimB = Math.min(b.r + RIM_GAP, len * 0.45);
46636
+ if (!curved && offset === 0) {
46637
+ const ux = dx / len;
46638
+ const uy = dy / len;
46639
+ const ax2 = a.cx + ux * trimA;
46640
+ const ay2 = a.cy + uy * trimA;
46641
+ const bx2 = b.cx - ux * trimB;
46642
+ const by2 = b.cy - uy * trimB;
46643
+ return `M${ax2},${ay2}L${bx2},${by2}`;
46644
+ }
46127
46645
  const nx = -dy / len;
46128
46646
  const ny = dx / len;
46129
46647
  const bow = offset !== 0 ? offset : len * ARC_CURVE_FRAC;
46130
- return `M${a.cx},${a.cy}Q${mx + nx * bow},${my + ny * bow} ${b.cx},${b.cy}`;
46648
+ const px = mx + nx * bow;
46649
+ const py = my + ny * bow;
46650
+ const ta = Math.hypot(px - a.cx, py - a.cy) || 1;
46651
+ const tb = Math.hypot(b.cx - px, b.cy - py) || 1;
46652
+ const ax = a.cx + (px - a.cx) / ta * trimA;
46653
+ const ay = a.cy + (py - a.cy) / ta * trimA;
46654
+ const bx = b.cx - (b.cx - px) / tb * trimB;
46655
+ const by = b.cy - (b.cy - py) / tb * trimB;
46656
+ return `M${ax},${ay}Q${px},${py} ${bx},${by}`;
46131
46657
  };
46132
46658
  for (const rt of resolved.routes) {
46133
46659
  const curved = rt.meta["style"] === "arc";
@@ -46138,7 +46664,7 @@ function layoutMap(resolved, data, size, opts) {
46138
46664
  legs.push({
46139
46665
  d: legPath(a, b, curved, 0),
46140
46666
  width: W_MIN,
46141
- color: mix(palette.text, palette.bg, 55),
46667
+ color: mix(palette.text, palette.bg, 72),
46142
46668
  arrow: true,
46143
46669
  lineNumber: rt.lineNumber
46144
46670
  });
@@ -46174,7 +46700,7 @@ function layoutMap(resolved, data, size, opts) {
46174
46700
  legs.push({
46175
46701
  d: legPath(a, b, curved, offset),
46176
46702
  width: widthFor(e),
46177
- color: mix(palette.text, palette.bg, 45),
46703
+ color: mix(palette.text, palette.bg, 66),
46178
46704
  arrow: e.directed,
46179
46705
  lineNumber: e.lineNumber,
46180
46706
  ...e.label !== void 0 && {
@@ -46186,38 +46712,92 @@ function layoutMap(resolved, data, size, opts) {
46186
46712
  });
46187
46713
  }
46188
46714
  const labels = [];
46189
- const pinList = [];
46190
46715
  const obstacles = [];
46191
46716
  const markers = pois.map((p) => ({
46192
46717
  cx: p.cx,
46193
46718
  cy: p.cy,
46194
46719
  r: p.r
46195
46720
  }));
46196
- const collides = (rect) => markers.some((m) => rectCircleOverlap(rect, m)) || obstacles.some((o) => rectsOverlap(rect, o));
46721
+ const legSegments = [];
46722
+ for (const leg of legs) {
46723
+ const m = /^M(-?[\d.]+),(-?[\d.]+)(?:L(-?[\d.]+),(-?[\d.]+)|Q(-?[\d.]+),(-?[\d.]+) (-?[\d.]+),(-?[\d.]+))$/.exec(
46724
+ leg.d
46725
+ );
46726
+ if (!m) continue;
46727
+ const x0 = +m[1];
46728
+ const y0 = +m[2];
46729
+ if (m[3] !== void 0) {
46730
+ legSegments.push([x0, y0, +m[3], +m[4]]);
46731
+ } else {
46732
+ const cx = +m[5];
46733
+ const cy = +m[6];
46734
+ const ex = +m[7];
46735
+ const ey = +m[8];
46736
+ const N = 8;
46737
+ let px = x0;
46738
+ let py = y0;
46739
+ for (let i = 1; i <= N; i++) {
46740
+ const t = i / N;
46741
+ const u = 1 - t;
46742
+ const qx = u * u * x0 + 2 * u * t * cx + t * t * ex;
46743
+ const qy = u * u * y0 + 2 * u * t * cy + t * t * ey;
46744
+ legSegments.push([px, py, qx, qy]);
46745
+ px = qx;
46746
+ py = qy;
46747
+ }
46748
+ }
46749
+ }
46750
+ 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));
46197
46751
  const regionLabelMode = resolved.directives.regionLabels ?? "off";
46752
+ const LABEL_PADX = 6;
46753
+ const LABEL_PADY = 3;
46754
+ const labelW = (text) => measureLegendText(text, FONT) + 2 * LABEL_PADX;
46755
+ const labelH = FONT + 2 * LABEL_PADY;
46756
+ const pushRegionLabel = (x, y, text, fill2, lineNumber) => {
46757
+ const color = contrastText(
46758
+ fill2,
46759
+ palette.textOnFillLight,
46760
+ palette.textOnFillDark
46761
+ );
46762
+ const haloColor = color === palette.textOnFillLight ? palette.textOnFillDark : palette.textOnFillLight;
46763
+ labels.push({
46764
+ x,
46765
+ y,
46766
+ text,
46767
+ anchor: "middle",
46768
+ color,
46769
+ halo: true,
46770
+ haloColor,
46771
+ lineNumber
46772
+ });
46773
+ };
46774
+ const WORLD_LABEL_ANCHORS = {
46775
+ US: [-98.5, 39.5]
46776
+ // CONUS geographic centre (near Lebanon, Kansas)
46777
+ };
46198
46778
  if (regionLabelMode === "full" || regionLabelMode === "abbrev") {
46199
46779
  for (const r of regions) {
46200
46780
  if (r.layer === "base" || r.label === void 0) continue;
46201
46781
  const f = r.layer === "us-state" ? usLayer?.get(r.id) : worldLayer.get(r.id);
46202
46782
  if (!f) continue;
46203
46783
  const [[x0, y0], [x1, y1]] = path.bounds(f);
46204
- if ((x1 - x0) * (y1 - y0) < TINY_REGION_AREA) continue;
46205
- const c = path.centroid(f);
46206
- if (!Number.isFinite(c[0])) continue;
46207
46784
  const text = regionLabelMode === "abbrev" ? r.id.replace(/^US-/, "") : r.label;
46208
- labels.push({
46209
- x: c[0],
46210
- y: c[1],
46785
+ if (labelW(text) > x1 - x0 || labelH > y1 - y0) continue;
46786
+ const anchor = r.layer !== "us-state" ? WORLD_LABEL_ANCHORS[r.id] : void 0;
46787
+ const c = anchor ? project(anchor[0], anchor[1]) : path.centroid(f);
46788
+ if (!c || !Number.isFinite(c[0])) continue;
46789
+ pushRegionLabel(c[0], c[1], text, r.fill, r.lineNumber);
46790
+ }
46791
+ for (const seed of insetLabelSeeds) {
46792
+ const text = regionLabelMode === "abbrev" ? seed.iso.replace(/^US-/, "") : seed.name;
46793
+ const src = regionById.get(seed.iso);
46794
+ pushRegionLabel(
46795
+ seed.x,
46796
+ seed.y,
46211
46797
  text,
46212
- anchor: "middle",
46213
- color: contrastText(
46214
- r.fill,
46215
- palette.textOnFillLight,
46216
- palette.textOnFillDark
46217
- ),
46218
- halo: true,
46219
- lineNumber: r.lineNumber
46220
- });
46798
+ src ? regionFill(src) : neutralFill,
46799
+ seed.lineNumber
46800
+ );
46221
46801
  }
46222
46802
  }
46223
46803
  const poiLabelMode = resolved.directives.poiLabels ?? "auto";
@@ -46230,68 +46810,106 @@ function layoutMap(resolved, data, size, opts) {
46230
46810
  const src = poiById.get(p.id);
46231
46811
  return src?.label ?? src?.name ?? p.id;
46232
46812
  };
46233
- let pinCounter = 0;
46234
- for (const p of ordered) {
46813
+ const poiLabH = FONT * 1.25;
46814
+ const labelInfo = (p) => {
46235
46815
  const text = labelText(p);
46236
- const w = measureLegendText(text, FONT);
46237
- const h = FONT * 1.25;
46238
- const inline = { x: p.cx + p.r + 3, y: p.cy - h / 2, w, h };
46239
- if (!collides(inline)) {
46240
- obstacles.push(inline);
46816
+ return { text, w: measureLegendText(text, FONT) };
46817
+ };
46818
+ const pushInline = (p, text, w, side) => {
46819
+ const tx = side === "right" ? p.cx + p.r + 3 : p.cx - p.r - 3;
46820
+ obstacles.push({
46821
+ x: side === "right" ? tx : tx - w,
46822
+ y: p.cy - poiLabH / 2,
46823
+ w,
46824
+ h: poiLabH
46825
+ });
46826
+ labels.push({
46827
+ x: tx,
46828
+ y: p.cy + FONT / 3,
46829
+ text,
46830
+ anchor: side === "right" ? "start" : "end",
46831
+ color: palette.text,
46832
+ halo: true,
46833
+ haloColor: palette.bg,
46834
+ poiId: p.id,
46835
+ lineNumber: p.lineNumber
46836
+ });
46837
+ };
46838
+ const inlineFits = (p, w, side) => {
46839
+ const tx = side === "right" ? p.cx + p.r + 3 : p.cx - p.r - 3;
46840
+ const rect = {
46841
+ x: side === "right" ? tx : tx - w,
46842
+ y: p.cy - poiLabH / 2,
46843
+ w,
46844
+ h: poiLabH
46845
+ };
46846
+ return rect.x >= 0 && rect.x + rect.w <= width && !collides(rect);
46847
+ };
46848
+ const GROUP_R = 30;
46849
+ const groups = [];
46850
+ for (const p of ordered) {
46851
+ const near = groups.find(
46852
+ (g) => g.some((q) => Math.hypot(q.cx - p.cx, q.cy - p.cy) < GROUP_R)
46853
+ );
46854
+ if (near) near.push(p);
46855
+ else groups.push([p]);
46856
+ }
46857
+ const ROW_GAP2 = 3;
46858
+ const step = poiLabH + ROW_GAP2;
46859
+ const COL_GAP = 16;
46860
+ const placeColumn = (group) => {
46861
+ const items = group.map((p) => ({ p, ...labelInfo(p) })).sort((a, b) => a.p.cy - b.p.cy || (a.text < b.text ? -1 : 1));
46862
+ const left = Math.min(...items.map((o) => o.p.cx - o.p.r));
46863
+ const right = Math.max(...items.map((o) => o.p.cx + o.p.r));
46864
+ const cyMid = (Math.min(...items.map((o) => o.p.cy)) + Math.max(...items.map((o) => o.p.cy))) / 2;
46865
+ const maxW = Math.max(...items.map((o) => o.w));
46866
+ const side = right + COL_GAP + maxW <= width - 2 ? "right" : "left";
46867
+ const colX = side === "right" ? right + COL_GAP : left - COL_GAP;
46868
+ const totalH = items.length * step;
46869
+ let startY = cyMid - totalH / 2;
46870
+ startY = Math.max(2, Math.min(startY, height - totalH - 2));
46871
+ items.forEach((o, i) => {
46872
+ const rowCy = startY + i * step + step / 2;
46873
+ obstacles.push({
46874
+ x: side === "right" ? colX : colX - o.w,
46875
+ y: rowCy - poiLabH / 2,
46876
+ w: o.w,
46877
+ h: poiLabH
46878
+ });
46241
46879
  labels.push({
46242
- x: inline.x,
46243
- y: p.cy + FONT / 3,
46244
- text,
46245
- anchor: "start",
46880
+ x: colX,
46881
+ y: rowCy + FONT / 3,
46882
+ text: o.text,
46883
+ anchor: side === "right" ? "start" : "end",
46246
46884
  color: palette.text,
46247
46885
  halo: true,
46248
- lineNumber: p.lineNumber
46886
+ haloColor: palette.bg,
46887
+ leader: {
46888
+ x1: o.p.cx,
46889
+ y1: o.p.cy,
46890
+ x2: side === "right" ? colX - 2 : colX + 2,
46891
+ y2: rowCy
46892
+ },
46893
+ leaderColor: o.p.fill,
46894
+ poiId: o.p.id,
46895
+ lineNumber: o.p.lineNumber
46249
46896
  });
46250
- continue;
46251
- }
46252
- let placed = false;
46253
- for (let k = 1; k <= 2 && !placed; k++) {
46254
- for (const [dx, dy] of RING_DIRS) {
46255
- const cx = p.cx + dx * LEADER_STEP * k;
46256
- const cy = p.cy + dy * LEADER_STEP * k;
46257
- const rect = {
46258
- x: dx >= 0 ? cx : cx - w,
46259
- y: cy - h / 2,
46260
- w,
46261
- h
46262
- };
46263
- if (rect.x < 0 || rect.x + rect.w > width || rect.y < 0 || rect.y + rect.h > height) {
46264
- continue;
46265
- }
46266
- if (collides(rect)) continue;
46267
- obstacles.push(rect);
46268
- labels.push({
46269
- x: cx,
46270
- y: cy + FONT / 3,
46271
- text,
46272
- anchor: dx >= 0 ? "start" : "end",
46273
- color: palette.text,
46274
- halo: true,
46275
- leader: { x1: p.cx, y1: p.cy, x2: cx, y2: cy },
46276
- lineNumber: p.lineNumber
46277
- });
46278
- placed = true;
46279
- break;
46897
+ });
46898
+ };
46899
+ for (const g of groups) {
46900
+ if (g.length === 1) {
46901
+ const p = g[0];
46902
+ const { text, w } = labelInfo(p);
46903
+ if (inlineFits(p, w, "right")) {
46904
+ pushInline(p, text, w, "right");
46905
+ continue;
46906
+ }
46907
+ if (inlineFits(p, w, "left")) {
46908
+ pushInline(p, text, w, "left");
46909
+ continue;
46280
46910
  }
46281
46911
  }
46282
- if (placed) continue;
46283
- pinCounter += 1;
46284
- pinList.push({ pin: pinCounter, label: text });
46285
- labels.push({
46286
- x: p.cx + p.r + 2,
46287
- y: p.cy - p.r,
46288
- text: String(pinCounter),
46289
- anchor: "start",
46290
- color: palette.text,
46291
- halo: true,
46292
- pin: pinCounter,
46293
- lineNumber: p.lineNumber
46294
- });
46912
+ placeColumn(g);
46295
46913
  }
46296
46914
  }
46297
46915
  let legend = null;
@@ -46300,8 +46918,7 @@ function layoutMap(resolved, data, size, opts) {
46300
46918
  name: g.name,
46301
46919
  entries: g.entries.map((e) => ({ value: e.value, color: e.color }))
46302
46920
  }));
46303
- const hasAnything = tagGroups.length > 0 || hasRamp || sizeVals.length > 0 || weightVals.length > 0;
46304
- if (hasAnything) {
46921
+ if (tagGroups.length > 0 || hasRamp) {
46305
46922
  legend = {
46306
46923
  tagGroups,
46307
46924
  activeGroup,
@@ -46312,20 +46929,9 @@ function layoutMap(resolved, data, size, opts) {
46312
46929
  },
46313
46930
  min: rampMin,
46314
46931
  max: rampMax,
46315
- hue: rampHue
46932
+ hue: rampHue,
46933
+ base: rampBase
46316
46934
  }
46317
- },
46318
- ...sizeVals.length > 0 && {
46319
- size: {
46320
- ...resolved.directives.sizeMetric !== void 0 && {
46321
- metric: resolved.directives.sizeMetric
46322
- },
46323
- min: sizeMin,
46324
- max: sizeMax
46325
- }
46326
- },
46327
- ...weightVals.length > 0 && {
46328
- weight: { min: wMin, max: wMax }
46329
46935
  }
46330
46936
  };
46331
46937
  }
@@ -46333,26 +46939,28 @@ function layoutMap(resolved, data, size, opts) {
46333
46939
  return {
46334
46940
  width,
46335
46941
  height,
46336
- background: palette.bg,
46942
+ background: water,
46337
46943
  title: resolved.title,
46338
46944
  ...resolved.subtitle !== void 0 && { subtitle: resolved.subtitle },
46339
46945
  ...resolved.caption !== void 0 && { caption: resolved.caption },
46340
46946
  regions,
46947
+ rivers,
46341
46948
  legs,
46342
46949
  pois,
46343
46950
  labels,
46344
- pinList,
46345
- legend
46951
+ legend,
46952
+ insets,
46953
+ insetRegions
46346
46954
  };
46347
46955
  }
46348
- 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;
46956
+ 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;
46349
46957
  var init_layout15 = __esm({
46350
46958
  "src/map/layout.ts"() {
46351
46959
  "use strict";
46352
46960
  init_color_utils();
46353
- init_tag_groups();
46354
46961
  init_label_layout();
46355
46962
  init_legend_constants();
46963
+ init_title_constants();
46356
46964
  FIT_PAD = 24;
46357
46965
  RAMP_FLOOR = 15;
46358
46966
  R_DEFAULT = 6;
@@ -46361,23 +46969,32 @@ var init_layout15 = __esm({
46361
46969
  W_MIN = 1.25;
46362
46970
  W_MAX = 8;
46363
46971
  FONT = 11;
46364
- LEADER_STEP = 14;
46365
46972
  COLO_EPS = 1.5;
46973
+ LAND_TINT_LIGHT = 58;
46974
+ LAND_TINT_DARK = 75;
46975
+ TAG_TINT_LIGHT = 60;
46976
+ TAG_TINT_DARK = 68;
46977
+ WATER_TINT = 55;
46978
+ RIVER_WIDTH = 1.3;
46979
+ FOREIGN_TINT_LIGHT = 30;
46980
+ FOREIGN_TINT_DARK = 62;
46366
46981
  COLO_R = 9;
46367
46982
  GOLDEN_ANGLE = 2.399963229728653;
46368
46983
  FAN_STEP = 16;
46369
- TINY_REGION_AREA = 600;
46370
46984
  ARC_CURVE_FRAC = 0.18;
46371
- RING_DIRS = [
46372
- [1, 0],
46373
- [0, 1],
46374
- [-1, 0],
46375
- [0, -1],
46376
- [1, 1],
46377
- [-1, 1],
46378
- [-1, -1],
46379
- [1, -1]
46380
- ];
46985
+ usConusProjection = () => geoConicEqualArea().parallels([29.5, 45.5]).rotate([96, 0]);
46986
+ alaskaProjection = () => geoConicEqualArea().rotate([154, 0]).center([-2, 58.5]).parallels([55, 65]);
46987
+ hawaiiProjection = () => geoMercator();
46988
+ INSET_STATES = /* @__PURE__ */ new Set(["US-AK", "US-HI"]);
46989
+ US_NON_CONUS = /* @__PURE__ */ new Set([
46990
+ "US-AK",
46991
+ "US-HI",
46992
+ "US-AS",
46993
+ "US-GU",
46994
+ "US-MP",
46995
+ "US-PR",
46996
+ "US-VI"
46997
+ ]);
46381
46998
  }
46382
46999
  });
46383
47000
 
@@ -46388,7 +47005,7 @@ __export(renderer_exports16, {
46388
47005
  renderMapForExport: () => renderMapForExport
46389
47006
  });
46390
47007
  import * as d3Selection18 from "d3-selection";
46391
- function renderMap(container, resolved, data, palette, isDark, onClickItem, exportDims) {
47008
+ function renderMap(container, resolved, data, palette, isDark, onClickItem, exportDims, activeGroupOverride) {
46392
47009
  d3Selection18.select(container).selectAll(":not([data-d3-tooltip])").remove();
46393
47010
  const width = exportDims?.width ?? container.clientWidth;
46394
47011
  const height = exportDims?.height ?? container.clientHeight;
@@ -46399,27 +47016,29 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46399
47016
  { width, height },
46400
47017
  {
46401
47018
  palette,
46402
- isDark
47019
+ isDark,
47020
+ ...activeGroupOverride !== void 0 && {
47021
+ activeGroup: activeGroupOverride
47022
+ }
46403
47023
  }
46404
47024
  );
46405
- 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);
47025
+ 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);
46406
47026
  svg.append("rect").attr("width", width).attr("height", height).attr("fill", layout.background);
46407
- const arrowColor = mix(palette.text, palette.bg, 50);
46408
47027
  const defs = svg.append("defs");
46409
- 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);
46410
- const haloColor = layout.background;
46411
- if (layout.title) {
46412
- 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);
46413
- }
46414
- if (layout.subtitle) {
46415
- 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);
46416
- }
46417
- if (layout.caption) {
46418
- 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);
46419
- }
47028
+ const arrowSize = (w) => Math.min(15, 7 + w * 0.95);
47029
+ const haloColor = palette.bg;
46420
47030
  const gRegions = svg.append("g").attr("class", "dgmo-map-regions");
46421
- for (const r of layout.regions) {
46422
- const p = gRegions.append("path").attr("d", r.d).attr("fill", r.fill).attr("stroke", r.stroke).attr("stroke-width", 0.5);
47031
+ const drawRegion = (g, r, strokeWidth) => {
47032
+ const p = g.append("path").attr("d", r.d).attr("fill", r.fill).attr("stroke", r.stroke).attr("stroke-width", strokeWidth);
47033
+ if (r.layer !== "base") {
47034
+ p.classed("dgmo-map-region", true).attr("data-region", r.id);
47035
+ if (r.score !== void 0) p.attr("data-score", r.score);
47036
+ if (r.tags) {
47037
+ for (const [group, value] of Object.entries(r.tags)) {
47038
+ p.attr(`data-tag-${group.toLowerCase()}`, value.toLowerCase());
47039
+ }
47040
+ }
47041
+ }
46423
47042
  if (r.lineNumber >= 0) {
46424
47043
  p.attr("data-line-number", r.lineNumber);
46425
47044
  if (onClickItem) {
@@ -46429,11 +47048,31 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46429
47048
  );
46430
47049
  }
46431
47050
  }
47051
+ };
47052
+ for (const r of layout.regions) drawRegion(gRegions, r, 0.5);
47053
+ if (layout.rivers.length) {
47054
+ const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none");
47055
+ for (const r of layout.rivers) {
47056
+ gRivers.append("path").attr("d", r.d).attr("stroke", r.color).attr("stroke-width", r.width).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
47057
+ }
47058
+ }
47059
+ if (layout.insets.length) {
47060
+ const insetG = svg.append("g").attr("class", "dgmo-map-insets");
47061
+ for (const box of layout.insets) {
47062
+ const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
47063
+ 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");
47064
+ }
47065
+ for (const r of layout.insetRegions) drawRegion(insetG, r, 0.5);
46432
47066
  }
46433
47067
  const gLegs = svg.append("g").attr("class", "dgmo-map-legs").attr("fill", "none");
46434
- for (const leg of layout.legs) {
47068
+ layout.legs.forEach((leg, i) => {
46435
47069
  const p = gLegs.append("path").attr("d", leg.d).attr("stroke", leg.color).attr("stroke-width", leg.width).attr("stroke-linecap", "round");
46436
- if (leg.arrow) p.attr("marker-end", "url(#dgmo-map-arrow)");
47070
+ if (leg.arrow) {
47071
+ const id = `dgmo-map-arrow-${i}`;
47072
+ const s = arrowSize(leg.width);
47073
+ 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);
47074
+ p.attr("marker-end", `url(#${id})`);
47075
+ }
46437
47076
  if (leg.label !== void 0 && leg.labelX !== void 0) {
46438
47077
  emitText(
46439
47078
  gLegs,
@@ -46447,13 +47086,13 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46447
47086
  LABEL_FONT - 1
46448
47087
  );
46449
47088
  }
46450
- }
47089
+ });
46451
47090
  const gPois = svg.append("g").attr("class", "dgmo-map-pois");
46452
47091
  for (const poi of layout.pois) {
46453
47092
  if (poi.isOrigin) {
46454
47093
  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);
46455
47094
  }
46456
- 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);
47095
+ 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);
46457
47096
  if (onClickItem) {
46458
47097
  c.style("cursor", "pointer").on(
46459
47098
  "click",
@@ -46477,48 +47116,66 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46477
47116
  const gLabels = svg.append("g").attr("class", "dgmo-map-labels");
46478
47117
  for (const lab of layout.labels) {
46479
47118
  if (lab.leader) {
46480
- 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);
46481
- }
46482
- if (lab.pin !== void 0) {
46483
- 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);
47119
+ 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(
47120
+ "stroke",
47121
+ lab.leaderColor ?? mix(palette.textMuted, palette.bg, 60)
47122
+ ).attr("stroke-width", lab.leaderColor ? 1 : 0.75);
47123
+ if (lab.poiId !== void 0) line12.attr("data-poi", lab.poiId);
46484
47124
  }
46485
- emitText(
47125
+ const t = emitText(
46486
47126
  gLabels,
46487
47127
  lab.x,
46488
47128
  lab.y,
46489
47129
  lab.text,
46490
47130
  lab.anchor,
46491
47131
  lab.color,
46492
- haloColor,
47132
+ lab.haloColor,
46493
47133
  lab.halo,
46494
47134
  LABEL_FONT
46495
47135
  );
46496
- }
46497
- if (layout.pinList.length > 0) {
46498
- const gPins = svg.append("g").attr("class", "dgmo-map-pin-list").attr(
46499
- "transform",
46500
- `translate(12, ${height - layout.pinList.length * 14 - 8})`
46501
- );
46502
- layout.pinList.forEach((entry, i) => {
46503
- 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}`);
46504
- });
47136
+ if (lab.poiId !== void 0) {
47137
+ t.attr("data-poi", lab.poiId).style("cursor", "default");
47138
+ }
46505
47139
  }
46506
47140
  if (layout.legend) {
46507
47141
  const legendY = (layout.title ? TITLE_Y + TITLE_FONT_SIZE : 0) + (layout.subtitle ? TITLE_FONT_SIZE : 0) + 8;
46508
47142
  const legendG = svg.append("g").attr("class", "dgmo-map-legend").attr("transform", `translate(0, ${legendY})`);
46509
- const groups = layout.legend.tagGroups.filter((g) => g.entries.length > 0);
47143
+ const ramp = layout.legend.ramp;
47144
+ const scoreGroup = ramp ? {
47145
+ name: ramp.metric?.trim() || "Score",
47146
+ entries: [],
47147
+ gradient: {
47148
+ min: ramp.min,
47149
+ max: ramp.max,
47150
+ hue: ramp.hue,
47151
+ base: ramp.base
47152
+ }
47153
+ } : null;
47154
+ const tagGroups = layout.legend.tagGroups.filter((g) => g.entries.length > 0).map((g) => ({ name: g.name, entries: [...g.entries] }));
47155
+ const groups = [...scoreGroup ? [scoreGroup] : [], ...tagGroups];
46510
47156
  if (groups.length > 0) {
46511
47157
  const config = {
46512
- groups: groups.map((g) => ({ name: g.name, entries: [...g.entries] })),
47158
+ groups,
46513
47159
  position: { placement: "top-center", titleRelation: "below-title" },
46514
47160
  mode: exportDims ? "export" : "preview",
46515
- showEmptyGroups: false
47161
+ showEmptyGroups: false,
47162
+ // Keep inactive siblings visible as pills so the user can click to flip
47163
+ // the active colouring dimension (preview only — export shows just the
47164
+ // active group).
47165
+ showInactivePills: true
46516
47166
  };
46517
47167
  const state = { activeGroup: layout.legend.activeGroup };
46518
47168
  renderLegendD3(legendG, config, state, palette, isDark, void 0, width);
46519
47169
  }
46520
- const pinGap = layout.pinList.length ? layout.pinList.length * 14 + 14 : 0;
46521
- emitExtraLegend(svg, layout, palette, height, pinGap);
47170
+ }
47171
+ if (layout.title) {
47172
+ 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);
47173
+ }
47174
+ if (layout.subtitle) {
47175
+ 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);
47176
+ }
47177
+ if (layout.caption) {
47178
+ 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);
46522
47179
  }
46523
47180
  }
46524
47181
  function renderMapForExport(container, resolved, data, palette, isDark, exportDims) {
@@ -46529,54 +47186,7 @@ function emitText(g, x, y, text, anchor, color, halo, withHalo, fontSize) {
46529
47186
  if (withHalo) {
46530
47187
  t.attr("paint-order", "stroke fill").attr("stroke", halo).attr("stroke-width", 3).attr("stroke-linejoin", "round").attr("stroke-opacity", 0.7);
46531
47188
  }
46532
- }
46533
- function emitExtraLegend(svg, layout, palette, height, bottomGap) {
46534
- const { legend } = layout;
46535
- if (!legend) return;
46536
- if (!legend.ramp && !legend.size && !legend.weight) return;
46537
- const blocks = [];
46538
- const g = svg.append("g").attr("class", "dgmo-map-legend-keys").attr("transform", `translate(12, ${height - 56 - bottomGap})`);
46539
- let xCursor = 0;
46540
- if (legend.ramp) {
46541
- const ramp = legend.ramp;
46542
- blocks.push(() => {
46543
- const block = g.append("g").attr("transform", `translate(${xCursor},0)`);
46544
- const gradId = "dgmo-map-ramp";
46545
- const grad = block.append("defs").append("linearGradient").attr("id", gradId).attr("x1", "0%").attr("x2", "100%");
46546
- grad.append("stop").attr("offset", "0%").attr("stop-color", mix(ramp.hue, palette.bg, 15));
46547
- grad.append("stop").attr("offset", "100%").attr("stop-color", ramp.hue);
46548
- block.append("rect").attr("width", 80).attr("height", 8).attr("fill", `url(#${gradId})`);
46549
- block.append("text").attr("x", 0).attr("y", 22).attr("font-size", 9).attr("fill", palette.textMuted).text(String(ramp.min));
46550
- 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));
46551
- if (ramp.metric) {
46552
- block.append("text").attr("x", 0).attr("y", -4).attr("font-size", 9).attr("fill", palette.textMuted).text(ramp.metric);
46553
- }
46554
- xCursor += 110;
46555
- });
46556
- }
46557
- if (legend.size) {
46558
- const sz = legend.size;
46559
- blocks.push(() => {
46560
- const block = g.append("g").attr("transform", `translate(${xCursor},0)`);
46561
- [3, 6, 10].forEach((r, i) => {
46562
- block.append("circle").attr("cx", i * 26 + r).attr("cy", 8).attr("r", r).attr("fill", "none").attr("stroke", palette.textMuted);
46563
- });
46564
- block.append("text").attr("x", 0).attr("y", -4).attr("font-size", 9).attr("fill", palette.textMuted).text(sz.metric ?? "size");
46565
- xCursor += 110;
46566
- });
46567
- }
46568
- if (legend.weight) {
46569
- const wt = legend.weight;
46570
- blocks.push(() => {
46571
- const block = g.append("g").attr("transform", `translate(${xCursor},0)`);
46572
- [1, 3, 6].forEach((w, i) => {
46573
- 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);
46574
- });
46575
- block.append("text").attr("x", 0).attr("y", -4).attr("font-size", 9).attr("fill", palette.textMuted).text(wt.metric ?? "weight");
46576
- xCursor += 110;
46577
- });
46578
- }
46579
- for (const draw of blocks) draw();
47189
+ return t;
46580
47190
  }
46581
47191
  var LABEL_FONT;
46582
47192
  var init_renderer16 = __esm({
@@ -56435,7 +57045,7 @@ pre.dgmo, code.language-dgmo, pre > code.language-dgmo,
56435
57045
 
56436
57046
  // src/auto/index.ts
56437
57047
  init_safe_href();
56438
- var VERSION = "0.19.0";
57048
+ var VERSION = "0.20.0";
56439
57049
  var DEFAULTS = {
56440
57050
  theme: "auto",
56441
57051
  palette: "nord",