@diagrammo/dgmo 0.19.0 → 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/advanced.cjs +948 -321
  2. package/dist/advanced.d.cts +148 -54
  3. package/dist/advanced.d.ts +148 -54
  4. package/dist/advanced.js +949 -321
  5. package/dist/auto.cjs +930 -317
  6. package/dist/auto.js +117 -117
  7. package/dist/auto.mjs +934 -318
  8. package/dist/cli.cjs +160 -160
  9. package/dist/index.cjs +929 -316
  10. package/dist/index.js +933 -317
  11. package/dist/internal.cjs +948 -321
  12. package/dist/internal.d.cts +148 -54
  13. package/dist/internal.d.ts +148 -54
  14. package/dist/internal.js +949 -321
  15. package/dist/map-data/PROVENANCE.json +1 -1
  16. package/dist/map-data/lakes.json +1 -0
  17. package/dist/map-data/na-lakes.json +1 -0
  18. package/dist/map-data/na-land.json +1 -0
  19. package/dist/map-data/rivers.json +1 -0
  20. package/docs/language-reference.md +12 -7
  21. package/gallery/fixtures/map-region-scope.dgmo +15 -0
  22. package/package.json +4 -4
  23. package/src/advanced.ts +7 -6
  24. package/src/c4/parser.ts +6 -6
  25. package/src/completion.ts +6 -2
  26. package/src/echarts.ts +1 -1
  27. package/src/infra/parser.ts +10 -10
  28. package/src/journey-map/parser.ts +1 -1
  29. package/src/label-layout.ts +36 -0
  30. package/src/map/data/PROVENANCE.json +1 -1
  31. package/src/map/data/README.md +2 -0
  32. package/src/map/data/lakes.json +1 -0
  33. package/src/map/data/na-lakes.json +1 -0
  34. package/src/map/data/na-land.json +1 -0
  35. package/src/map/data/rivers.json +1 -0
  36. package/src/map/layout.ts +1022 -205
  37. package/src/map/load-data.ts +73 -17
  38. package/src/map/parser.ts +22 -13
  39. package/src/map/renderer.ts +200 -219
  40. package/src/map/resolved-types.ts +18 -1
  41. package/src/map/resolver.ts +79 -7
  42. package/src/map/types.ts +4 -0
  43. package/src/mindmap/parser.ts +1 -1
  44. package/src/sitemap/parser.ts +1 -1
  45. package/src/utils/legend-d3.ts +42 -0
  46. package/src/utils/legend-layout.ts +83 -3
  47. package/src/utils/legend-svg.ts +1 -8
  48. package/src/utils/legend-types.ts +44 -1
package/dist/internal.js CHANGED
@@ -331,6 +331,33 @@ function rectCircleOverlap(rect, circle) {
331
331
  const dy = nearestY - circle.cy;
332
332
  return dx * dx + dy * dy < circle.r * circle.r;
333
333
  }
334
+ function segmentRectOverlap(x0, y0, x1, y1, rect) {
335
+ const dx = x1 - x0;
336
+ const dy = y1 - y0;
337
+ let t0 = 0;
338
+ let t1 = 1;
339
+ const edges = [
340
+ [-dx, x0 - rect.x],
341
+ [dx, rect.x + rect.w - x0],
342
+ [-dy, y0 - rect.y],
343
+ [dy, rect.y + rect.h - y0]
344
+ ];
345
+ for (const [p, q] of edges) {
346
+ if (p === 0) {
347
+ if (q < 0) return false;
348
+ } else {
349
+ const t = q / p;
350
+ if (p < 0) {
351
+ if (t > t1) return false;
352
+ if (t > t0) t0 = t;
353
+ } else {
354
+ if (t < t0) return false;
355
+ if (t < t1) t1 = t;
356
+ }
357
+ }
358
+ }
359
+ return true;
360
+ }
334
361
  function computeQuadrantPointLabels(points, chartBounds, obstacles, pointRadius, fontSize) {
335
362
  const labelHeight = fontSize + 4;
336
363
  const stepSize = labelHeight + 2;
@@ -3276,6 +3303,57 @@ var init_legend_constants = __esm({
3276
3303
  });
3277
3304
 
3278
3305
  // src/utils/legend-layout.ts
3306
+ function fmtRamp(n) {
3307
+ return Number.isInteger(n) ? String(n) : String(Math.round(n * 10) / 10);
3308
+ }
3309
+ function gradientCapsuleWidth(name, gradient) {
3310
+ const pw = pillWidth(name);
3311
+ const minW = measureLegendText(fmtRamp(gradient.min), LEGEND_ENTRY_FONT_SIZE);
3312
+ const maxW = measureLegendText(fmtRamp(gradient.max), LEGEND_ENTRY_FONT_SIZE);
3313
+ return LEGEND_CAPSULE_PAD + pw + 4 + minW + RAMP_LABEL_GAP + RAMP_LEGEND_W + RAMP_LABEL_GAP + maxW + LEGEND_CAPSULE_PAD;
3314
+ }
3315
+ function buildGradientCapsuleLayout(group, gradient) {
3316
+ const pw = pillWidth(group.name);
3317
+ const minText = fmtRamp(gradient.min);
3318
+ const maxText = fmtRamp(gradient.max);
3319
+ const minW = measureLegendText(minText, LEGEND_ENTRY_FONT_SIZE);
3320
+ const gx = LEGEND_CAPSULE_PAD + pw + 4;
3321
+ const minX = gx;
3322
+ const rampX = gx + minW + RAMP_LABEL_GAP;
3323
+ const maxX = rampX + RAMP_LEGEND_W + RAMP_LABEL_GAP;
3324
+ const width = gradientCapsuleWidth(group.name, gradient);
3325
+ return {
3326
+ groupName: group.name,
3327
+ x: 0,
3328
+ y: 0,
3329
+ width,
3330
+ height: LEGEND_HEIGHT,
3331
+ pill: {
3332
+ groupName: group.name,
3333
+ x: LEGEND_CAPSULE_PAD,
3334
+ y: LEGEND_CAPSULE_PAD,
3335
+ width: pw,
3336
+ height: LEGEND_HEIGHT - LEGEND_CAPSULE_PAD * 2,
3337
+ isActive: true
3338
+ },
3339
+ entries: [],
3340
+ gradient: {
3341
+ rampX,
3342
+ rampY: (LEGEND_HEIGHT - RAMP_LEGEND_H) / 2,
3343
+ rampW: RAMP_LEGEND_W,
3344
+ rampH: RAMP_LEGEND_H,
3345
+ min: gradient.min,
3346
+ max: gradient.max,
3347
+ minText,
3348
+ minX,
3349
+ maxText,
3350
+ maxX,
3351
+ textY: LEGEND_HEIGHT / 2,
3352
+ hue: gradient.hue,
3353
+ base: gradient.base
3354
+ }
3355
+ };
3356
+ }
3279
3357
  function pillWidth(name) {
3280
3358
  return measureLegendText(name, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD;
3281
3359
  }
@@ -3418,7 +3496,7 @@ function computeLegendLayout(config, state, containerWidth) {
3418
3496
  };
3419
3497
  }
3420
3498
  const controlsGroupLayout = isExport ? void 0 : buildControlsGroupLayout(config, state);
3421
- const visibleGroups = config.showEmptyGroups ? groups : groups.filter((g) => g.entries.length > 0);
3499
+ const visibleGroups = config.showEmptyGroups ? groups : groups.filter((g) => g.entries.length > 0 || !!g.gradient);
3422
3500
  if (visibleGroups.length === 0 && (!configControls || configControls.length === 0) && !controlsGroupLayout) {
3423
3501
  return {
3424
3502
  height: 0,
@@ -3497,7 +3575,7 @@ function computeLegendLayout(config, state, containerWidth) {
3497
3575
  groupAvailW,
3498
3576
  config.capsulePillAddonWidth ?? 0
3499
3577
  );
3500
- } else if (!activeGroupName) {
3578
+ } else if (!activeGroupName || config.showInactivePills) {
3501
3579
  const pw = pillWidth(g.name);
3502
3580
  pills.push({
3503
3581
  groupName: g.name,
@@ -3535,6 +3613,7 @@ function computeLegendLayout(config, state, containerWidth) {
3535
3613
  };
3536
3614
  }
3537
3615
  function buildCapsuleLayout(group, containerWidth, addonWidth = 0) {
3616
+ if (group.gradient) return buildGradientCapsuleLayout(group, group.gradient);
3538
3617
  const pw = pillWidth(group.name);
3539
3618
  const info = capsuleWidth(
3540
3619
  group.name,
@@ -3709,7 +3788,7 @@ function getMaxLegendReservedHeight(config, containerWidth) {
3709
3788
  }
3710
3789
  return max;
3711
3790
  }
3712
- var CONTROL_PILL_PAD, CONTROL_FONT_SIZE, CONTROL_ICON_GAP, CONTROL_GAP;
3791
+ var CONTROL_PILL_PAD, CONTROL_FONT_SIZE, CONTROL_ICON_GAP, CONTROL_GAP, RAMP_LEGEND_W, RAMP_LEGEND_H, RAMP_LABEL_GAP;
3713
3792
  var init_legend_layout = __esm({
3714
3793
  "src/utils/legend-layout.ts"() {
3715
3794
  "use strict";
@@ -3718,6 +3797,9 @@ var init_legend_layout = __esm({
3718
3797
  CONTROL_FONT_SIZE = 11;
3719
3798
  CONTROL_ICON_GAP = 4;
3720
3799
  CONTROL_GAP = 8;
3800
+ RAMP_LEGEND_W = 80;
3801
+ RAMP_LEGEND_H = 8;
3802
+ RAMP_LABEL_GAP = 6;
3721
3803
  }
3722
3804
  });
3723
3805
 
@@ -3805,6 +3887,16 @@ function renderCapsule(parent, capsule, palette, groupBg, pillBorder, _isDark, c
3805
3887
  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);
3806
3888
  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);
3807
3889
  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);
3890
+ if (capsule.gradient) {
3891
+ const gr = capsule.gradient;
3892
+ const gradId = `dgmo-legend-ramp-${capsule.groupName.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
3893
+ const def = g.append("defs").append("linearGradient").attr("id", gradId);
3894
+ def.append("stop").attr("offset", "0%").attr("stop-color", mix(gr.hue, gr.base, 15));
3895
+ def.append("stop").attr("offset", "100%").attr("stop-color", gr.hue);
3896
+ 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);
3897
+ 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})`);
3898
+ 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);
3899
+ }
3808
3900
  for (const entry of capsule.entries) {
3809
3901
  const entryG = g.append("g").attr("data-legend-entry", entry.value.toLowerCase()).attr("data-series-name", entry.value).style("cursor", "pointer");
3810
3902
  entryG.append("circle").attr("cx", entry.dotCx).attr("cy", entry.dotCy).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
@@ -11244,23 +11336,22 @@ function parseC4(content, palette) {
11244
11336
  }
11245
11337
  }
11246
11338
  const parent = findParentElement(indent, stack);
11247
- if (parent) {
11248
- const descResult = tryStripDescriptionKeyword(trimmed);
11339
+ const descResult = tryStripDescriptionKeyword(trimmed);
11340
+ if (parent && descResult.isKeyword) {
11249
11341
  if (descResult.needsColon) {
11250
11342
  pushError(
11251
11343
  lineNumber,
11252
- `Use "description: ${descResult.text}" \u2014 colon is required.`,
11344
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`,
11253
11345
  "warning"
11254
11346
  );
11255
11347
  }
11256
- const descText = descResult.isKeyword ? descResult.text : trimmed;
11257
11348
  let desc = elementDescription.get(parent.element);
11258
11349
  if (!desc) {
11259
11350
  desc = [];
11260
11351
  elementDescription.set(parent.element, desc);
11261
11352
  parent.element.description = desc;
11262
11353
  }
11263
- desc.push(descText);
11354
+ desc.push(descResult.text);
11264
11355
  } else {
11265
11356
  pushError(lineNumber, `Unexpected content: "${trimmed}"`);
11266
11357
  }
@@ -11727,7 +11818,7 @@ function parseSitemap(content, palette) {
11727
11818
  if (descResult.needsColon) {
11728
11819
  pushWarning(
11729
11820
  lineNumber,
11730
- `Use "description: ${descResult.text}" \u2014 colon is required.`
11821
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`
11731
11822
  );
11732
11823
  }
11733
11824
  const parent = findParentNode(indent, indentStack);
@@ -12555,23 +12646,22 @@ function parseInfra(content) {
12555
12646
  }
12556
12647
  }
12557
12648
  const descResult = tryStripDescriptionKeyword(trimmed);
12558
- if (descResult.isKeyword && currentNode.isEdge) {
12559
- continue;
12560
- }
12561
- if (!currentNode.isEdge) {
12649
+ if (descResult.isKeyword) {
12650
+ if (currentNode.isEdge) {
12651
+ continue;
12652
+ }
12562
12653
  if (descResult.needsColon) {
12563
12654
  warn(
12564
12655
  lineNumber,
12565
- `Use "description: ${descResult.text}" \u2014 colon is required.`
12656
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`
12566
12657
  );
12567
12658
  }
12568
- const descText = descResult.isKeyword ? descResult.text : trimmed;
12569
- pushDescription(currentNode, descText);
12659
+ pushDescription(currentNode, descResult.text);
12570
12660
  continue;
12571
12661
  }
12572
12662
  warn(
12573
12663
  lineNumber,
12574
- `Unexpected line inside component '${currentNode.label}'. Expected a property (key: value), connection (-> Target), or description text.`
12664
+ `Unexpected line inside component '${currentNode.label}'. Expected a property (key: value), connection (-> Target), or a description (description: text).`
12575
12665
  );
12576
12666
  continue;
12577
12667
  }
@@ -15708,9 +15798,6 @@ function parseMap(content) {
15708
15798
  const pushWarning = (line12, message) => {
15709
15799
  diagnostics.push(makeDgmoError(line12, message, "warning"));
15710
15800
  };
15711
- const pushInfo = (line12, message) => {
15712
- diagnostics.push(makeDgmoError(line12, message, "warning"));
15713
- };
15714
15801
  const lines = content.split("\n");
15715
15802
  let firstIdx = 0;
15716
15803
  while (firstIdx < lines.length) {
@@ -15840,10 +15927,15 @@ function parseMap(content) {
15840
15927
  break;
15841
15928
  case "projection":
15842
15929
  dup(d.projection);
15843
- if (value && !["natural-earth", "albers-usa", "mercator"].includes(value))
15930
+ if (value && ![
15931
+ "equirectangular",
15932
+ "natural-earth",
15933
+ "albers-usa",
15934
+ "mercator"
15935
+ ].includes(value))
15844
15936
  pushWarning(
15845
15937
  line12,
15846
- `Unknown projection "${value}" (expected natural-earth | albers-usa | mercator).`
15938
+ `Unknown projection "${value}" (expected equirectangular | natural-earth | albers-usa | mercator).`
15847
15939
  );
15848
15940
  d.projection = value;
15849
15941
  break;
@@ -15982,17 +16074,21 @@ function parseMap(content) {
15982
16074
  scoreNum = void 0;
15983
16075
  }
15984
16076
  }
15985
- if (scoreNum !== void 0 && Object.keys(tags).length)
15986
- pushInfo(
15987
- line12,
15988
- "A region has both `score:` and a tag value \u2014 v1 renders only the score (bivariate is a future seam)."
15989
- );
16077
+ let regionName = split.name;
16078
+ let regionScope;
16079
+ const rToks = regionName.split(/\s+/);
16080
+ const rLast = rToks[rToks.length - 1];
16081
+ if (rToks.length > 1 && SCOPE_RE.test(rLast)) {
16082
+ regionName = rToks.slice(0, -1).join(" ");
16083
+ regionScope = rLast;
16084
+ }
15990
16085
  const region = {
15991
- name: split.name,
16086
+ name: regionName,
15992
16087
  tags,
15993
16088
  meta,
15994
16089
  lineNumber: line12
15995
16090
  };
16091
+ if (regionScope !== void 0) region.scope = regionScope;
15996
16092
  if (scoreNum !== void 0) region.score = scoreNum;
15997
16093
  regions.push(region);
15998
16094
  }
@@ -17116,7 +17212,7 @@ function parseMindmap(content, palette) {
17116
17212
  if (descResult.needsColon) {
17117
17213
  pushWarning(
17118
17214
  lineNumber,
17119
- `Use "description: ${descResult.text}" \u2014 colon is required.`
17215
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`
17120
17216
  );
17121
17217
  }
17122
17218
  const parent = findMetadataParent2(indent, indentStack);
@@ -18904,7 +19000,7 @@ function parseJourneyMap(content, palette) {
18904
19000
  if (descResult.needsColon) {
18905
19001
  warn(
18906
19002
  lineNumber,
18907
- `Use "description: ${descResult.text}" \u2014 colon is required.`
19003
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`
18908
19004
  );
18909
19005
  }
18910
19006
  currentStep.description = descResult.text;
@@ -45806,7 +45902,9 @@ function resolveMap(parsed, data) {
45806
45902
  const usScoped = parsed.directives.region === "us-states" || parsed.directives.defaultCountry?.toUpperCase() === "US" || parsed.regions.some((r) => {
45807
45903
  const f = fold(r.name);
45808
45904
  return usStateIndex.has(f) && !countryIndex.has(f);
45809
- }) || parsed.pois.some(
45905
+ }) || parsed.regions.some(
45906
+ (r) => r.scope === "US" || r.scope?.startsWith("US-")
45907
+ ) || parsed.pois.some(
45810
45908
  (p) => p.pos.kind === "name" && p.pos.scope?.startsWith("US-")
45811
45909
  );
45812
45910
  const regions = [];
@@ -45818,7 +45916,30 @@ function resolveMap(parsed, data) {
45818
45916
  const inCountry = countryIndex.get(f) ?? (REGION_ALIASES[f] ? countryIndex.get(REGION_ALIASES[f]) : void 0);
45819
45917
  const inState = usStateIndex.get(f);
45820
45918
  let chosen = null;
45821
- if (inCountry && inState) {
45919
+ const scope = r.scope;
45920
+ if (scope) {
45921
+ const wantsState = scope === "US" || scope.startsWith("US-");
45922
+ if (wantsState && inState) {
45923
+ if (scope.startsWith("US-") && inState.id !== scope) {
45924
+ err(
45925
+ r.lineNumber,
45926
+ `No subdivision "${r.name}" in scope ${scope} (it is ${inState.id}).`,
45927
+ "E_MAP_SCOPE_MISS"
45928
+ );
45929
+ continue;
45930
+ }
45931
+ chosen = { ...inState, layer: "us-state" };
45932
+ } else if (!wantsState && inCountry) {
45933
+ chosen = { ...inCountry, layer: "country" };
45934
+ } else {
45935
+ err(
45936
+ r.lineNumber,
45937
+ `No region "${r.name}" found in scope ${scope}.`,
45938
+ "E_MAP_SCOPE_MISS"
45939
+ );
45940
+ continue;
45941
+ }
45942
+ } else if (inCountry && inState) {
45822
45943
  if (usScoped) {
45823
45944
  chosen = { ...inState, layer: "us-state" };
45824
45945
  } else {
@@ -45826,7 +45947,7 @@ function resolveMap(parsed, data) {
45826
45947
  }
45827
45948
  warn(
45828
45949
  r.lineNumber,
45829
- `"${r.name}" is both a country and a US state \u2014 resolved as ${chosen.layer} (${chosen.id}).`,
45950
+ `"${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}").`,
45830
45951
  "W_MAP_REGION_AMBIGUOUS"
45831
45952
  );
45832
45953
  } else if (inState) {
@@ -45998,9 +46119,17 @@ function resolveMap(parsed, data) {
45998
46119
  };
45999
46120
  registerPoi(id, poi, p.lineNumber);
46000
46121
  }
46122
+ const declaredByName = /* @__PURE__ */ new Map();
46123
+ for (const p of pois) {
46124
+ const fn = p.name ? fold(p.name) : void 0;
46125
+ if (fn && fn !== p.id && !declaredByName.has(fn))
46126
+ declaredByName.set(fn, p.id);
46127
+ }
46001
46128
  const resolveEndpoint2 = (ref, line12) => {
46002
46129
  const f = fold(ref);
46003
46130
  if (registry.has(f)) return f;
46131
+ const aliased = declaredByName.get(f);
46132
+ if (aliased) return aliased;
46004
46133
  const got = lookupName(ref, void 0, line12, inferredCountry, true);
46005
46134
  if (got.kind !== "ok") return null;
46006
46135
  noteCountry(got.iso);
@@ -46080,23 +46209,29 @@ function resolveMap(parsed, data) {
46080
46209
  [-180, -85],
46081
46210
  [180, 85]
46082
46211
  ];
46083
- const extent2 = unioned ? pad(unioned, PAD_FRACTION) : DEFAULT_EXTENT;
46212
+ let extent2 = unioned ? pad(unioned, PAD_FRACTION) : DEFAULT_EXTENT;
46084
46213
  const lonSpan = extent2[1][0] - extent2[0][0];
46085
46214
  const latSpan = extent2[1][1] - extent2[0][1];
46086
46215
  const span = Math.max(lonSpan, latSpan);
46087
46216
  const usDominant = (inferredCountry === "US" || subdivisions.includes("us-states")) && !regions.some((r) => r.layer === "country" && r.iso !== "US") && !anyNonUsPoi;
46088
46217
  let projection;
46089
46218
  const override = parsed.directives.projection;
46090
- if (override === "natural-earth" || override === "albers-usa" || override === "mercator") {
46219
+ if (override === "equirectangular" || override === "natural-earth" || override === "albers-usa" || override === "mercator") {
46091
46220
  projection = override;
46092
46221
  } else if (usDominant) {
46093
46222
  projection = "albers-usa";
46094
46223
  } else if (span > WORLD_SPAN) {
46095
- projection = "natural-earth";
46224
+ projection = "equirectangular";
46096
46225
  } else if (span < MERCATOR_MAX_SPAN) {
46097
46226
  projection = "mercator";
46098
46227
  } else {
46099
- projection = "natural-earth";
46228
+ projection = "equirectangular";
46229
+ }
46230
+ if (lonSpan >= 180) {
46231
+ extent2 = [
46232
+ [-180, Math.min(extent2[0][1], WORLD_LAT_SOUTH)],
46233
+ [180, Math.max(extent2[1][1], WORLD_LAT_NORTH)]
46234
+ ];
46100
46235
  }
46101
46236
  result.regions = regions;
46102
46237
  result.pois = pois;
@@ -46144,7 +46279,7 @@ function firstError(diags) {
46144
46279
  const e = diags.find((d) => d.severity === "error");
46145
46280
  return e ? formatDgmoError(e) : null;
46146
46281
  }
46147
- var WORLD_SPAN, MERCATOR_MAX_SPAN, PAD_FRACTION, REGION_ALIASES;
46282
+ var WORLD_SPAN, MERCATOR_MAX_SPAN, PAD_FRACTION, WORLD_LAT_SOUTH, WORLD_LAT_NORTH, REGION_ALIASES;
46148
46283
  var init_resolver2 = __esm({
46149
46284
  "src/map/resolver.ts"() {
46150
46285
  "use strict";
@@ -46153,6 +46288,8 @@ var init_resolver2 = __esm({
46153
46288
  WORLD_SPAN = 90;
46154
46289
  MERCATOR_MAX_SPAN = 25;
46155
46290
  PAD_FRACTION = 0.05;
46291
+ WORLD_LAT_SOUTH = -58;
46292
+ WORLD_LAT_NORTH = 78;
46156
46293
  REGION_ALIASES = {
46157
46294
  // Common everyday names → the Natural-Earth display name actually shipped.
46158
46295
  "united states": "united states of america",
@@ -46182,17 +46319,22 @@ var load_data_exports = {};
46182
46319
  __export(load_data_exports, {
46183
46320
  loadMapData: () => loadMapData
46184
46321
  });
46185
- import { readFile } from "fs/promises";
46186
- import { fileURLToPath } from "url";
46187
- import { dirname, resolve } from "path";
46188
- async function readJson(dir, name) {
46189
- return JSON.parse(await readFile(resolve(dir, name), "utf8"));
46322
+ async function loadNodeBuiltins() {
46323
+ const [{ readFile }, { fileURLToPath }, { dirname: dirname2, resolve }] = await Promise.all([
46324
+ import("fs/promises"),
46325
+ import("url"),
46326
+ import("path")
46327
+ ]);
46328
+ return { readFile, fileURLToPath, dirname: dirname2, resolve };
46190
46329
  }
46191
- async function firstExistingDir(baseDir) {
46330
+ async function readJson(nb, dir, name) {
46331
+ return JSON.parse(await nb.readFile(nb.resolve(dir, name), "utf8"));
46332
+ }
46333
+ async function firstExistingDir(nb, baseDir) {
46192
46334
  for (const rel of CANDIDATE_DIRS) {
46193
- const dir = resolve(baseDir, rel);
46335
+ const dir = nb.resolve(baseDir, rel);
46194
46336
  try {
46195
- await readFile(resolve(dir, FILES.gazetteer), "utf8");
46337
+ await nb.readFile(nb.resolve(dir, FILES.gazetteer), "utf8");
46196
46338
  return dir;
46197
46339
  } catch {
46198
46340
  }
@@ -46208,10 +46350,10 @@ function validate(data) {
46208
46350
  }
46209
46351
  return data;
46210
46352
  }
46211
- function moduleBaseDir() {
46353
+ function moduleBaseDir(nb) {
46212
46354
  try {
46213
46355
  const url = import.meta.url;
46214
- if (url) return dirname(fileURLToPath(url));
46356
+ if (url) return nb.dirname(nb.fileURLToPath(url));
46215
46357
  } catch {
46216
46358
  }
46217
46359
  if (typeof __dirname !== "undefined") return __dirname;
@@ -46219,14 +46361,38 @@ function moduleBaseDir() {
46219
46361
  }
46220
46362
  function loadMapData() {
46221
46363
  cache ??= (async () => {
46222
- const dir = await firstExistingDir(moduleBaseDir());
46223
- const [worldCoarse, worldDetail, usStates, gazetteer] = await Promise.all([
46224
- readJson(dir, FILES.worldCoarse),
46225
- readJson(dir, FILES.worldDetail),
46226
- readJson(dir, FILES.usStates),
46227
- readJson(dir, FILES.gazetteer)
46364
+ const nb = await loadNodeBuiltins();
46365
+ const dir = await firstExistingDir(nb, moduleBaseDir(nb));
46366
+ const [
46367
+ worldCoarse,
46368
+ worldDetail,
46369
+ usStates,
46370
+ lakes,
46371
+ rivers,
46372
+ naLand,
46373
+ naLakes,
46374
+ gazetteer
46375
+ ] = await Promise.all([
46376
+ readJson(nb, dir, FILES.worldCoarse),
46377
+ readJson(nb, dir, FILES.worldDetail),
46378
+ readJson(nb, dir, FILES.usStates),
46379
+ // Lakes/rivers/NA assets are optional — older bundles may predate them.
46380
+ readJson(nb, dir, FILES.lakes).catch(() => void 0),
46381
+ readJson(nb, dir, FILES.rivers).catch(() => void 0),
46382
+ readJson(nb, dir, FILES.naLand).catch(() => void 0),
46383
+ readJson(nb, dir, FILES.naLakes).catch(() => void 0),
46384
+ readJson(nb, dir, FILES.gazetteer)
46228
46385
  ]);
46229
- return validate({ worldCoarse, worldDetail, usStates, gazetteer });
46386
+ return validate({
46387
+ worldCoarse,
46388
+ worldDetail,
46389
+ usStates,
46390
+ gazetteer,
46391
+ ...lakes && { lakes },
46392
+ ...rivers && { rivers },
46393
+ ...naLand && { naLand },
46394
+ ...naLakes && { naLakes }
46395
+ });
46230
46396
  })().catch((e) => {
46231
46397
  cache = void 0;
46232
46398
  throw e;
@@ -46241,6 +46407,10 @@ var init_load_data = __esm({
46241
46407
  worldCoarse: "world-coarse.json",
46242
46408
  worldDetail: "world-detail.json",
46243
46409
  usStates: "us-states.json",
46410
+ lakes: "lakes.json",
46411
+ rivers: "rivers.json",
46412
+ naLand: "na-land.json",
46413
+ naLakes: "na-lakes.json",
46244
46414
  gazetteer: "gazetteer.json"
46245
46415
  };
46246
46416
  CANDIDATE_DIRS = [
@@ -46256,8 +46426,11 @@ var init_load_data = __esm({
46256
46426
  import {
46257
46427
  geoPath,
46258
46428
  geoNaturalEarth1,
46259
- geoAlbersUsa,
46260
- geoMercator
46429
+ geoEquirectangular,
46430
+ geoConicEqualArea,
46431
+ geoMercator,
46432
+ geoBounds as geoBounds2,
46433
+ geoTransform
46261
46434
  } from "d3-geo";
46262
46435
  import { feature as feature2 } from "topojson-client";
46263
46436
  function geomObject2(topo) {
@@ -46275,36 +46448,74 @@ function decodeLayer(topo) {
46275
46448
  function projectionFor(family) {
46276
46449
  switch (family) {
46277
46450
  case "albers-usa":
46278
- return geoAlbersUsa();
46451
+ return usConusProjection();
46279
46452
  case "mercator":
46280
46453
  return geoMercator();
46281
46454
  case "natural-earth":
46282
- default:
46283
46455
  return geoNaturalEarth1();
46456
+ case "equirectangular":
46457
+ default:
46458
+ return geoEquirectangular();
46284
46459
  }
46285
46460
  }
46461
+ function mapBackgroundColor(palette) {
46462
+ return mix(palette.colors.blue, palette.bg, WATER_TINT);
46463
+ }
46464
+ function mapNeutralLandColor(palette, isDark) {
46465
+ return mix(
46466
+ palette.colors.green,
46467
+ palette.bg,
46468
+ isDark ? LAND_TINT_DARK : LAND_TINT_LIGHT
46469
+ );
46470
+ }
46286
46471
  function layoutMap(resolved, data, size, opts) {
46287
46472
  const { palette, isDark } = opts;
46288
46473
  const { width, height } = size;
46289
- const worldTopo = resolved.basemaps.world === "detail" ? data.worldDetail : data.worldCoarse;
46474
+ const wantsUsStates = resolved.basemaps.subdivisions.includes("us-states");
46475
+ const usCrisp = resolved.projection === "albers-usa" && wantsUsStates && !!data.naLand;
46476
+ const worldTopo = usCrisp ? data.naLand : resolved.basemaps.world === "detail" ? data.worldDetail : data.worldCoarse;
46290
46477
  const worldLayer = decodeLayer(worldTopo);
46291
- const usLayer = resolved.basemaps.subdivisions.includes("us-states") ? decodeLayer(data.usStates) : null;
46292
- const neutralFill = mix(palette.border, palette.bg, 32);
46293
- const regionStroke = mix(palette.border, palette.bg, 70);
46478
+ const usLayer = wantsUsStates ? decodeLayer(data.usStates) : null;
46479
+ const landTint = isDark ? LAND_TINT_DARK : LAND_TINT_LIGHT;
46480
+ const neutralFill = mix(palette.colors.green, palette.bg, landTint);
46481
+ const water = mapBackgroundColor(palette);
46482
+ const usContext = usLayer !== null;
46483
+ const foreignFill = mix(
46484
+ palette.colors.gray,
46485
+ palette.bg,
46486
+ isDark ? FOREIGN_TINT_DARK : FOREIGN_TINT_LIGHT
46487
+ );
46488
+ const regionStroke = isDark ? mix(palette.bg, palette.text, 78) : mix(palette.text, palette.bg, 78);
46294
46489
  const scores = resolved.regions.filter((r) => r.score !== void 0).map((r) => r.score);
46295
46490
  const scaleOverride = resolved.directives.scale;
46296
46491
  const rampMin = scaleOverride ? scaleOverride.min : Math.min(...scores);
46297
46492
  const rampMax = scaleOverride ? scaleOverride.max : Math.max(...scores);
46298
- const rampHue = palette.primary;
46493
+ const rampHue = palette.colors.red;
46299
46494
  const hasRamp = scores.length > 0;
46300
- const activeGroup = resolveActiveTagGroup(
46301
- resolved.tagGroups,
46302
- resolved.directives.activeTag
46303
- );
46495
+ const SCORE_NAME = hasRamp ? resolved.directives.metric?.trim() || "Score" : null;
46496
+ const matchColorGroup = (v) => {
46497
+ const lv = v.trim().toLowerCase();
46498
+ if (lv === "none") return null;
46499
+ if (SCORE_NAME && (lv === "score" || lv === SCORE_NAME.toLowerCase()))
46500
+ return SCORE_NAME;
46501
+ const tg = resolved.tagGroups.find((g) => g.name.toLowerCase() === lv);
46502
+ return tg ? tg.name : v;
46503
+ };
46504
+ const override = opts.activeGroup;
46505
+ let activeGroup;
46506
+ if (override !== void 0) {
46507
+ activeGroup = override === null ? null : matchColorGroup(override);
46508
+ } else if (resolved.directives.activeTag !== void 0) {
46509
+ activeGroup = matchColorGroup(resolved.directives.activeTag);
46510
+ } else {
46511
+ activeGroup = SCORE_NAME ?? (resolved.tagGroups.length > 0 ? resolved.tagGroups[0].name : null);
46512
+ }
46513
+ const activeIsScore = SCORE_NAME !== null && activeGroup === SCORE_NAME;
46514
+ const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
46304
46515
  const fillForScore = (s) => {
46305
46516
  const t = rampMax > rampMin ? (s - rampMin) / (rampMax - rampMin) : 1;
46306
46517
  const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
46307
- return mix(rampHue, isDark ? palette.surface : palette.bg, pct);
46518
+ return mix(rampHue, rampBase, pct);
46308
46519
  };
46309
46520
  const tagFill = (tags, groupName) => {
46310
46521
  if (!groupName) return null;
@@ -46318,40 +46529,40 @@ function layoutMap(resolved, data, size, opts) {
46318
46529
  (e) => e.value.toLowerCase() === val.toLowerCase()
46319
46530
  );
46320
46531
  if (!entry?.color) return null;
46321
- return shapeFill(palette, entry.color, isDark);
46532
+ return mix(
46533
+ entry.color,
46534
+ palette.bg,
46535
+ isDark ? TAG_TINT_DARK : TAG_TINT_LIGHT
46536
+ );
46537
+ };
46538
+ const regionFill = (r) => {
46539
+ if (activeIsScore) {
46540
+ return r.score !== void 0 ? fillForScore(r.score) : neutralFill;
46541
+ }
46542
+ return tagFill(r.tags, activeGroup) ?? neutralFill;
46322
46543
  };
46323
46544
  const regionById = new Map(resolved.regions.map((r) => [r.iso, r]));
46324
- const regionFeatures = [];
46325
- for (const r of resolved.regions) {
46326
- const f = r.layer === "us-state" ? usLayer?.get(r.iso) : worldLayer.get(r.iso);
46327
- if (f) regionFeatures.push(f);
46328
- }
46329
- const extentCorners = () => {
46545
+ const extentOutline = () => {
46330
46546
  const [[w, s], [e, n]] = resolved.extent;
46547
+ const N = 16;
46548
+ const coords = [];
46549
+ for (let i = 0; i <= N; i++) {
46550
+ const t = i / N;
46551
+ const lon = w + (e - w) * t;
46552
+ const lat = s + (n - s) * t;
46553
+ coords.push([lon, s], [lon, n], [w, lat], [e, lat]);
46554
+ }
46331
46555
  return {
46332
46556
  type: "Feature",
46333
46557
  properties: {},
46334
- geometry: {
46335
- type: "MultiPoint",
46336
- coordinates: [
46337
- [w, s],
46338
- [e, s],
46339
- [e, n],
46340
- [w, n]
46341
- ]
46342
- }
46558
+ geometry: { type: "MultiPoint", coordinates: coords }
46343
46559
  };
46344
46560
  };
46345
46561
  let fitFeatures;
46346
- if (resolved.projection === "albers-usa") {
46347
- if (regionFeatures.length > 0) fitFeatures = regionFeatures;
46348
- else if (usLayer) fitFeatures = [...usLayer.values()];
46349
- else {
46350
- const us = worldLayer.get("US");
46351
- fitFeatures = us ? [us] : [...worldLayer.values()];
46352
- }
46562
+ if (resolved.projection === "albers-usa" && usLayer) {
46563
+ fitFeatures = [...usLayer.entries()].filter(([iso]) => !US_NON_CONUS.has(iso)).map(([, f]) => f);
46353
46564
  } else {
46354
- fitFeatures = [extentCorners()];
46565
+ fitFeatures = [extentOutline()];
46355
46566
  }
46356
46567
  const fitTarget = { type: "FeatureCollection", features: fitFeatures };
46357
46568
  const projection = projectionFor(resolved.projection);
@@ -46360,32 +46571,311 @@ function layoutMap(resolved, data, size, opts) {
46360
46571
  if (centerLon > 180) centerLon -= 360;
46361
46572
  projection.rotate([-centerLon, 0]);
46362
46573
  }
46363
- projection.fitExtent(
46574
+ const TITLE_GAP = 16;
46575
+ let topPad = FIT_PAD;
46576
+ if (resolved.title && resolved.pois.length > 0) {
46577
+ const bannerBottom = (resolved.subtitle ? TITLE_Y + TITLE_FONT_SIZE : TITLE_Y) + TITLE_FONT_SIZE / 2;
46578
+ topPad = Math.max(FIT_PAD, bannerBottom + TITLE_GAP);
46579
+ }
46580
+ const fitBox = [
46581
+ [FIT_PAD, topPad],
46364
46582
  [
46365
- [FIT_PAD, FIT_PAD],
46366
- [
46367
- Math.max(FIT_PAD + 1, width - FIT_PAD),
46368
- Math.max(FIT_PAD + 1, height - FIT_PAD)
46369
- ]
46370
- ],
46371
- fitTarget
46372
- );
46373
- const path = geoPath(projection);
46374
- const project = (lon, lat) => projection([lon, lat]) ?? null;
46583
+ Math.max(FIT_PAD + 1, width - FIT_PAD),
46584
+ Math.max(topPad + 1, height - FIT_PAD)
46585
+ ]
46586
+ ];
46587
+ projection.fitExtent(fitBox, fitTarget);
46588
+ const fitGB = geoBounds2(fitTarget);
46589
+ const fitIsGlobal = fitGB[1][0] - fitGB[0][0] >= 270 || fitGB[1][1] - fitGB[0][1] >= 130;
46590
+ let path;
46591
+ let project;
46592
+ if (fitIsGlobal) {
46593
+ const cb = geoPath(projection).bounds(fitTarget);
46594
+ const bx0 = cb[0][0];
46595
+ const by0 = cb[0][1];
46596
+ const cw = cb[1][0] - bx0;
46597
+ const ch = cb[1][1] - by0;
46598
+ const ox = fitBox[0][0];
46599
+ const oy = fitBox[0][1];
46600
+ const sx = cw > 0 ? (fitBox[1][0] - ox) / cw : 1;
46601
+ const sy = ch > 0 ? (fitBox[1][1] - oy) / ch : 1;
46602
+ const stretch = (x, y) => [
46603
+ ox + (x - bx0) * sx,
46604
+ oy + (y - by0) * sy
46605
+ ];
46606
+ const baseProjection = projection;
46607
+ const tx = geoTransform({
46608
+ point(x, y) {
46609
+ const [px, py] = stretch(x, y);
46610
+ this.stream.point(px, py);
46611
+ }
46612
+ });
46613
+ path = geoPath({
46614
+ stream: (s) => baseProjection.stream(
46615
+ tx.stream(s)
46616
+ )
46617
+ });
46618
+ project = (lon, lat) => {
46619
+ const p = baseProjection([lon, lat]);
46620
+ return p ? stretch(p[0], p[1]) : null;
46621
+ };
46622
+ } else {
46623
+ path = geoPath(projection);
46624
+ project = (lon, lat) => projection([lon, lat]) ?? null;
46625
+ }
46626
+ const insets = [];
46627
+ const insetRegions = [];
46628
+ const insetLabelSeeds = [];
46629
+ if (resolved.projection === "albers-usa" && usLayer) {
46630
+ const PAD = 8;
46631
+ const GAP = 12;
46632
+ const yB = height - FIT_PAD;
46633
+ const BW = 8;
46634
+ const coast = /* @__PURE__ */ new Map();
46635
+ const addPt = (lon, lat) => {
46636
+ const p = projection([lon, lat]);
46637
+ if (!p) return;
46638
+ const bi = Math.floor(p[0] / BW);
46639
+ const cur = coast.get(bi);
46640
+ if (cur === void 0 || p[1] > cur) coast.set(bi, p[1]);
46641
+ };
46642
+ const walk = (co) => {
46643
+ if (Array.isArray(co) && typeof co[0] === "number")
46644
+ addPt(co[0], co[1]);
46645
+ else if (Array.isArray(co)) for (const c of co) walk(c);
46646
+ };
46647
+ for (const [iso, f] of usLayer) {
46648
+ if (US_NON_CONUS.has(iso)) continue;
46649
+ walk(f.geometry.coordinates);
46650
+ }
46651
+ const at = (x) => {
46652
+ const bi = Math.floor(x / BW);
46653
+ let y = -Infinity;
46654
+ for (let k = bi - 1; k <= bi + 1; k++) {
46655
+ const v = coast.get(k);
46656
+ if (v !== void 0 && v > y) y = v;
46657
+ }
46658
+ return y;
46659
+ };
46660
+ const coastTop = (x0, xr) => {
46661
+ const n = 24;
46662
+ const pts = [];
46663
+ let maxY = -Infinity;
46664
+ for (let i = 0; i <= n; i++) {
46665
+ const x = x0 + (xr - x0) * i / n;
46666
+ const y = at(x);
46667
+ if (y > -Infinity) {
46668
+ pts.push([x, y]);
46669
+ if (y > maxY) maxY = y;
46670
+ }
46671
+ }
46672
+ if (pts.length === 0) return () => yB - height * 0.42;
46673
+ let m = 0;
46674
+ if (pts.length >= 2) {
46675
+ let sx = 0, sy = 0, sxx = 0, sxy = 0;
46676
+ for (const [x, y] of pts) {
46677
+ sx += x;
46678
+ sy += y;
46679
+ sxx += x * x;
46680
+ sxy += x * y;
46681
+ }
46682
+ const den = pts.length * sxx - sx * sx;
46683
+ if (den !== 0) m = (pts.length * sxy - sx * sy) / den;
46684
+ }
46685
+ m = Math.max(-0.35, Math.min(0.35, m));
46686
+ let c = -Infinity;
46687
+ for (const [x, y] of pts) {
46688
+ const need = y - m * x + GAP;
46689
+ if (need > c) c = need;
46690
+ }
46691
+ return (x) => m * x + c;
46692
+ };
46693
+ const placeInset = (iso, proj, boxX, iwReq) => {
46694
+ const f = usLayer.get(iso);
46695
+ if (!f) return boxX;
46696
+ const x0 = boxX;
46697
+ const iw = Math.min(iwReq, width - FIT_PAD - x0 - 2 * PAD);
46698
+ if (iw < 24) return boxX;
46699
+ const xr = x0 + iw + 2 * PAD;
46700
+ const top = coastTop(x0, xr);
46701
+ const yL = top(x0);
46702
+ const yR = top(xr);
46703
+ proj.fitWidth(iw, f);
46704
+ const bb = geoPath(proj).bounds(f);
46705
+ const sh = Number.isFinite(bb[0][0]) ? bb[1][1] - bb[0][1] : iw;
46706
+ const needH = sh + 2 * PAD;
46707
+ let topFit = Math.max(yL, yR);
46708
+ const bottom = Math.min(topFit + needH, yB);
46709
+ if (bottom - topFit < needH) topFit = bottom - needH;
46710
+ const lift = topFit - Math.max(yL, yR);
46711
+ const topL = yL + lift;
46712
+ const topR = yR + lift;
46713
+ proj.fitExtent(
46714
+ [
46715
+ [x0 + PAD, topFit + PAD],
46716
+ [xr - PAD, bottom - PAD]
46717
+ ],
46718
+ f
46719
+ );
46720
+ const d = geoPath(proj)(f) ?? "";
46721
+ if (!d) return xr;
46722
+ const r = regionById.get(iso);
46723
+ let fill2 = neutralFill;
46724
+ let lineNumber = -1;
46725
+ if (r?.layer === "us-state") {
46726
+ fill2 = regionFill(r);
46727
+ lineNumber = r.lineNumber;
46728
+ }
46729
+ insets.push({
46730
+ x: x0,
46731
+ y: Math.min(topL, topR),
46732
+ w: xr - x0,
46733
+ h: bottom - Math.min(topL, topR),
46734
+ points: [
46735
+ [x0, topL],
46736
+ [xr, topR],
46737
+ [xr, bottom],
46738
+ [x0, bottom]
46739
+ ]
46740
+ });
46741
+ insetRegions.push({
46742
+ id: iso,
46743
+ d,
46744
+ fill: fill2,
46745
+ stroke: regionStroke,
46746
+ lineNumber,
46747
+ layer: "us-state",
46748
+ ...r?.score !== void 0 && { score: r.score },
46749
+ ...r && Object.keys(r.tags).length > 0 && { tags: r.tags }
46750
+ });
46751
+ const ctr = geoPath(proj).centroid(f);
46752
+ if (Number.isFinite(ctr[0])) {
46753
+ const name = f.properties?.name ?? iso;
46754
+ insetLabelSeeds.push({ x: ctr[0], y: ctr[1], iso, name, lineNumber });
46755
+ }
46756
+ return xr;
46757
+ };
46758
+ const akRight = placeInset(
46759
+ "US-AK",
46760
+ alaskaProjection(),
46761
+ FIT_PAD,
46762
+ width * 0.15
46763
+ );
46764
+ placeInset("US-HI", hawaiiProjection(), akRight + 24, width * 0.1);
46765
+ }
46766
+ const conusFit = resolved.projection === "albers-usa" && !!usLayer;
46767
+ const cullExtent = conusFit ? geoBounds2(fitTarget) : resolved.extent;
46768
+ const [[exW, exS], [exE, exN]] = cullExtent;
46769
+ const lonSpan = exE - exW;
46770
+ const latSpan = exN - exS;
46771
+ const isGlobalView = lonSpan >= 270 || latSpan >= 130;
46772
+ const padLon = Math.max(8, lonSpan * 0.35);
46773
+ const padLat = Math.max(8, latSpan * 0.35);
46774
+ const vW = exW - padLon;
46775
+ const vE = exE + padLon;
46776
+ const vS = exS - padLat;
46777
+ const vN = exN + padLat;
46778
+ const vLonCenter = (exW + exE) / 2;
46779
+ const normLon = (lon) => {
46780
+ let L = lon;
46781
+ while (L < vLonCenter - 180) L += 360;
46782
+ while (L > vLonCenter + 180) L -= 360;
46783
+ return L;
46784
+ };
46785
+ const ringOverlapsView = (ring) => {
46786
+ let anyIn = false;
46787
+ let loMin = Infinity, loMax = -Infinity, laMin = Infinity, laMax = -Infinity, rawMin = Infinity, rawMax = -Infinity;
46788
+ for (const [rawLon, lat] of ring) {
46789
+ const lon = normLon(rawLon);
46790
+ if (lon >= vW && lon <= vE && lat >= vS && lat <= vN) anyIn = true;
46791
+ if (lon < loMin) loMin = lon;
46792
+ if (lon > loMax) loMax = lon;
46793
+ if (rawLon < rawMin) rawMin = rawLon;
46794
+ if (rawLon > rawMax) rawMax = rawLon;
46795
+ if (lat < laMin) laMin = lat;
46796
+ if (lat > laMax) laMax = lat;
46797
+ }
46798
+ if (loMax - loMin > 270) return false;
46799
+ if (rawMax - rawMin > 180 && loMax - loMin < 90) return false;
46800
+ if (anyIn) return true;
46801
+ if (loMax - loMin > 180) return false;
46802
+ return !(loMax < vW || loMin > vE || laMax < vS || laMin > vN);
46803
+ };
46804
+ const cullFeatureToView = (f) => {
46805
+ if (isGlobalView) return f;
46806
+ const g = f.geometry;
46807
+ if (!g) return f;
46808
+ if (g.type === "Polygon") {
46809
+ const ring = g.coordinates[0];
46810
+ return ringOverlapsView(ring) ? f : null;
46811
+ }
46812
+ if (g.type === "MultiPolygon") {
46813
+ const polys = g.coordinates;
46814
+ const keep = polys.filter(
46815
+ (p) => ringOverlapsView(p[0])
46816
+ );
46817
+ if (!keep.length) return null;
46818
+ if (keep.length === polys.length) return f;
46819
+ return { ...f, geometry: { ...g, coordinates: keep } };
46820
+ }
46821
+ return f;
46822
+ };
46823
+ const SEAM_SLIVER_MAX_SPAN = 100;
46824
+ const ringIsFrameFiller = (ring) => {
46825
+ const lons = ring.map(([lon]) => lon).sort((a, b) => a - b);
46826
+ if (lons.length < 2) return false;
46827
+ let maxGap = -1;
46828
+ let gapIdx = 0;
46829
+ for (let i = 1; i < lons.length; i++) {
46830
+ const g = lons[i] - lons[i - 1];
46831
+ if (g > maxGap) {
46832
+ maxGap = g;
46833
+ gapIdx = i;
46834
+ }
46835
+ }
46836
+ const wrapGap = lons[0] + 360 - lons[lons.length - 1];
46837
+ if (wrapGap >= maxGap) return false;
46838
+ const span = 360 - maxGap;
46839
+ const east = lons[gapIdx - 1] + 360;
46840
+ return east > 180 && span < SEAM_SLIVER_MAX_SPAN;
46841
+ };
46842
+ const dropFrameFillers = (f) => {
46843
+ const g = f.geometry;
46844
+ if (!g) return f;
46845
+ if (g.type === "Polygon") {
46846
+ const ring = g.coordinates[0];
46847
+ return ringIsFrameFiller(ring) ? null : f;
46848
+ }
46849
+ if (g.type === "MultiPolygon") {
46850
+ const polys = g.coordinates;
46851
+ const keep = polys.filter(
46852
+ (p) => !ringIsFrameFiller(p[0])
46853
+ );
46854
+ if (!keep.length) return null;
46855
+ if (keep.length === polys.length) return f;
46856
+ return { ...f, geometry: { ...g, coordinates: keep } };
46857
+ }
46858
+ return f;
46859
+ };
46375
46860
  const regions = [];
46376
- const pushRegionLayer = (layerFeatures, layerKind) => {
46861
+ const pushRegionLayer = (layerFeatures, layerKind, shouldCull) => {
46377
46862
  for (const [iso, f] of layerFeatures) {
46378
- const d = path(f) ?? "";
46379
- if (!d) continue;
46863
+ if (layerKind === "us-state" && usContext && INSET_STATES.has(iso))
46864
+ continue;
46865
+ if (layerKind === "country" && usContext && iso === "US") continue;
46380
46866
  const r = regionById.get(iso);
46867
+ const viewF = shouldCull ? cullFeatureToView(f) : dropFrameFillers(f);
46868
+ if (!viewF) continue;
46869
+ const d = path(viewF) ?? "";
46870
+ if (!d) continue;
46381
46871
  const isThisLayer = r?.layer === layerKind;
46382
- let fill2 = neutralFill;
46872
+ const isForeign = layerKind === "country" && usContext && iso !== "US";
46873
+ let fill2 = isForeign ? foreignFill : neutralFill;
46383
46874
  let label;
46384
46875
  let lineNumber = -1;
46385
46876
  let layer = "base";
46386
46877
  if (isThisLayer) {
46387
- if (r.score !== void 0) fill2 = fillForScore(r.score);
46388
- else fill2 = tagFill(r.tags, activeGroup) ?? neutralFill;
46878
+ fill2 = regionFill(r);
46389
46879
  lineNumber = r.lineNumber;
46390
46880
  layer = layerKind;
46391
46881
  label = r.name;
@@ -46397,12 +46887,42 @@ function layoutMap(resolved, data, size, opts) {
46397
46887
  stroke: regionStroke,
46398
46888
  lineNumber,
46399
46889
  layer,
46400
- ...label !== void 0 && { label }
46890
+ ...label !== void 0 && { label },
46891
+ ...isThisLayer && r.score !== void 0 && { score: r.score },
46892
+ ...isThisLayer && Object.keys(r.tags).length > 0 && { tags: r.tags }
46401
46893
  });
46402
46894
  }
46403
46895
  };
46404
- pushRegionLayer(worldLayer, "country");
46405
- if (usLayer) pushRegionLayer(usLayer, "us-state");
46896
+ pushRegionLayer(worldLayer, "country", !isGlobalView);
46897
+ if (usLayer) pushRegionLayer(usLayer, "us-state", !conusFit && !isGlobalView);
46898
+ const lakesTopo = usCrisp && data.naLakes ? data.naLakes : data.lakes;
46899
+ if (lakesTopo) {
46900
+ for (const [, f] of decodeLayer(lakesTopo)) {
46901
+ const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
46902
+ if (!viewF) continue;
46903
+ const d = path(viewF) ?? "";
46904
+ if (!d) continue;
46905
+ regions.push({
46906
+ id: "lake",
46907
+ d,
46908
+ fill: water,
46909
+ stroke: "none",
46910
+ lineNumber: -1,
46911
+ layer: "base"
46912
+ });
46913
+ }
46914
+ }
46915
+ const riverColor = water;
46916
+ const rivers = [];
46917
+ if (data.rivers) {
46918
+ for (const [, f] of decodeLayer(data.rivers)) {
46919
+ const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
46920
+ if (!viewF) continue;
46921
+ const d = path(viewF) ?? "";
46922
+ if (!d) continue;
46923
+ rivers.push({ d, color: riverColor, width: RIVER_WIDTH });
46924
+ }
46925
+ }
46406
46926
  const sizeVals = resolved.pois.map((p) => Number(p.meta["size"])).filter((n) => Number.isFinite(n) && n > 0);
46407
46927
  const sizeMin = sizeVals.length ? Math.min(...sizeVals) : 0;
46408
46928
  const sizeMax = sizeVals.length ? Math.max(...sizeVals) : 0;
@@ -46423,8 +46943,8 @@ function layoutMap(resolved, data, size, opts) {
46423
46943
  if (hex) return { fill: hex, stroke: mix(hex, palette.text, 18) };
46424
46944
  }
46425
46945
  return {
46426
- fill: palette.accent,
46427
- stroke: mix(palette.accent, palette.text, 18)
46946
+ fill: palette.colors.orange,
46947
+ stroke: mix(palette.colors.orange, palette.text, 18)
46428
46948
  };
46429
46949
  };
46430
46950
  const routeNumberById = /* @__PURE__ */ new Map();
@@ -46462,7 +46982,7 @@ function layoutMap(resolved, data, size, opts) {
46462
46982
  cy += Math.sin(ang) * COLO_R;
46463
46983
  }
46464
46984
  const { fill: fill2, stroke: stroke2 } = poiFill(e.p);
46465
- poiScreen.set(e.p.id, { cx, cy });
46985
+ poiScreen.set(e.p.id, { cx, cy, r: radiusFor(e.p) });
46466
46986
  const num = routeNumberById.get(e.p.id);
46467
46987
  pois.push({
46468
46988
  id: e.p.id,
@@ -46479,17 +46999,36 @@ function layoutMap(resolved, data, size, opts) {
46479
46999
  });
46480
47000
  }
46481
47001
  const legs = [];
47002
+ const RIM_GAP = 1.5;
46482
47003
  const legPath = (a, b, curved, offset) => {
46483
- if (!curved && offset === 0) return `M${a.cx},${a.cy}L${b.cx},${b.cy}`;
46484
47004
  const mx = (a.cx + b.cx) / 2;
46485
47005
  const my = (a.cy + b.cy) / 2;
46486
47006
  const dx = b.cx - a.cx;
46487
47007
  const dy = b.cy - a.cy;
46488
47008
  const len = Math.hypot(dx, dy) || 1;
47009
+ const trimA = Math.min(a.r + RIM_GAP, len * 0.45);
47010
+ const trimB = Math.min(b.r + RIM_GAP, len * 0.45);
47011
+ if (!curved && offset === 0) {
47012
+ const ux = dx / len;
47013
+ const uy = dy / len;
47014
+ const ax2 = a.cx + ux * trimA;
47015
+ const ay2 = a.cy + uy * trimA;
47016
+ const bx2 = b.cx - ux * trimB;
47017
+ const by2 = b.cy - uy * trimB;
47018
+ return `M${ax2},${ay2}L${bx2},${by2}`;
47019
+ }
46489
47020
  const nx = -dy / len;
46490
47021
  const ny = dx / len;
46491
47022
  const bow = offset !== 0 ? offset : len * ARC_CURVE_FRAC;
46492
- return `M${a.cx},${a.cy}Q${mx + nx * bow},${my + ny * bow} ${b.cx},${b.cy}`;
47023
+ const px = mx + nx * bow;
47024
+ const py = my + ny * bow;
47025
+ const ta = Math.hypot(px - a.cx, py - a.cy) || 1;
47026
+ const tb = Math.hypot(b.cx - px, b.cy - py) || 1;
47027
+ const ax = a.cx + (px - a.cx) / ta * trimA;
47028
+ const ay = a.cy + (py - a.cy) / ta * trimA;
47029
+ const bx = b.cx - (b.cx - px) / tb * trimB;
47030
+ const by = b.cy - (b.cy - py) / tb * trimB;
47031
+ return `M${ax},${ay}Q${px},${py} ${bx},${by}`;
46493
47032
  };
46494
47033
  for (const rt of resolved.routes) {
46495
47034
  const curved = rt.meta["style"] === "arc";
@@ -46500,7 +47039,7 @@ function layoutMap(resolved, data, size, opts) {
46500
47039
  legs.push({
46501
47040
  d: legPath(a, b, curved, 0),
46502
47041
  width: W_MIN,
46503
- color: mix(palette.text, palette.bg, 55),
47042
+ color: mix(palette.text, palette.bg, 72),
46504
47043
  arrow: true,
46505
47044
  lineNumber: rt.lineNumber
46506
47045
  });
@@ -46536,7 +47075,7 @@ function layoutMap(resolved, data, size, opts) {
46536
47075
  legs.push({
46537
47076
  d: legPath(a, b, curved, offset),
46538
47077
  width: widthFor(e),
46539
- color: mix(palette.text, palette.bg, 45),
47078
+ color: mix(palette.text, palette.bg, 66),
46540
47079
  arrow: e.directed,
46541
47080
  lineNumber: e.lineNumber,
46542
47081
  ...e.label !== void 0 && {
@@ -46548,38 +47087,92 @@ function layoutMap(resolved, data, size, opts) {
46548
47087
  });
46549
47088
  }
46550
47089
  const labels = [];
46551
- const pinList = [];
46552
47090
  const obstacles = [];
46553
47091
  const markers = pois.map((p) => ({
46554
47092
  cx: p.cx,
46555
47093
  cy: p.cy,
46556
47094
  r: p.r
46557
47095
  }));
46558
- const collides = (rect) => markers.some((m) => rectCircleOverlap(rect, m)) || obstacles.some((o) => rectsOverlap(rect, o));
47096
+ const legSegments = [];
47097
+ for (const leg of legs) {
47098
+ const m = /^M(-?[\d.]+),(-?[\d.]+)(?:L(-?[\d.]+),(-?[\d.]+)|Q(-?[\d.]+),(-?[\d.]+) (-?[\d.]+),(-?[\d.]+))$/.exec(
47099
+ leg.d
47100
+ );
47101
+ if (!m) continue;
47102
+ const x0 = +m[1];
47103
+ const y0 = +m[2];
47104
+ if (m[3] !== void 0) {
47105
+ legSegments.push([x0, y0, +m[3], +m[4]]);
47106
+ } else {
47107
+ const cx = +m[5];
47108
+ const cy = +m[6];
47109
+ const ex = +m[7];
47110
+ const ey = +m[8];
47111
+ const N = 8;
47112
+ let px = x0;
47113
+ let py = y0;
47114
+ for (let i = 1; i <= N; i++) {
47115
+ const t = i / N;
47116
+ const u = 1 - t;
47117
+ const qx = u * u * x0 + 2 * u * t * cx + t * t * ex;
47118
+ const qy = u * u * y0 + 2 * u * t * cy + t * t * ey;
47119
+ legSegments.push([px, py, qx, qy]);
47120
+ px = qx;
47121
+ py = qy;
47122
+ }
47123
+ }
47124
+ }
47125
+ 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));
46559
47126
  const regionLabelMode = resolved.directives.regionLabels ?? "off";
47127
+ const LABEL_PADX = 6;
47128
+ const LABEL_PADY = 3;
47129
+ const labelW = (text) => measureLegendText(text, FONT) + 2 * LABEL_PADX;
47130
+ const labelH = FONT + 2 * LABEL_PADY;
47131
+ const pushRegionLabel = (x, y, text, fill2, lineNumber) => {
47132
+ const color = contrastText(
47133
+ fill2,
47134
+ palette.textOnFillLight,
47135
+ palette.textOnFillDark
47136
+ );
47137
+ const haloColor = color === palette.textOnFillLight ? palette.textOnFillDark : palette.textOnFillLight;
47138
+ labels.push({
47139
+ x,
47140
+ y,
47141
+ text,
47142
+ anchor: "middle",
47143
+ color,
47144
+ halo: true,
47145
+ haloColor,
47146
+ lineNumber
47147
+ });
47148
+ };
47149
+ const WORLD_LABEL_ANCHORS = {
47150
+ US: [-98.5, 39.5]
47151
+ // CONUS geographic centre (near Lebanon, Kansas)
47152
+ };
46560
47153
  if (regionLabelMode === "full" || regionLabelMode === "abbrev") {
46561
47154
  for (const r of regions) {
46562
47155
  if (r.layer === "base" || r.label === void 0) continue;
46563
47156
  const f = r.layer === "us-state" ? usLayer?.get(r.id) : worldLayer.get(r.id);
46564
47157
  if (!f) continue;
46565
47158
  const [[x0, y0], [x1, y1]] = path.bounds(f);
46566
- if ((x1 - x0) * (y1 - y0) < TINY_REGION_AREA) continue;
46567
- const c = path.centroid(f);
46568
- if (!Number.isFinite(c[0])) continue;
46569
47159
  const text = regionLabelMode === "abbrev" ? r.id.replace(/^US-/, "") : r.label;
46570
- labels.push({
46571
- x: c[0],
46572
- y: c[1],
47160
+ if (labelW(text) > x1 - x0 || labelH > y1 - y0) continue;
47161
+ const anchor = r.layer !== "us-state" ? WORLD_LABEL_ANCHORS[r.id] : void 0;
47162
+ const c = anchor ? project(anchor[0], anchor[1]) : path.centroid(f);
47163
+ if (!c || !Number.isFinite(c[0])) continue;
47164
+ pushRegionLabel(c[0], c[1], text, r.fill, r.lineNumber);
47165
+ }
47166
+ for (const seed of insetLabelSeeds) {
47167
+ const text = regionLabelMode === "abbrev" ? seed.iso.replace(/^US-/, "") : seed.name;
47168
+ const src = regionById.get(seed.iso);
47169
+ pushRegionLabel(
47170
+ seed.x,
47171
+ seed.y,
46573
47172
  text,
46574
- anchor: "middle",
46575
- color: contrastText(
46576
- r.fill,
46577
- palette.textOnFillLight,
46578
- palette.textOnFillDark
46579
- ),
46580
- halo: true,
46581
- lineNumber: r.lineNumber
46582
- });
47173
+ src ? regionFill(src) : neutralFill,
47174
+ seed.lineNumber
47175
+ );
46583
47176
  }
46584
47177
  }
46585
47178
  const poiLabelMode = resolved.directives.poiLabels ?? "auto";
@@ -46592,68 +47185,106 @@ function layoutMap(resolved, data, size, opts) {
46592
47185
  const src = poiById.get(p.id);
46593
47186
  return src?.label ?? src?.name ?? p.id;
46594
47187
  };
46595
- let pinCounter = 0;
46596
- for (const p of ordered) {
47188
+ const poiLabH = FONT * 1.25;
47189
+ const labelInfo = (p) => {
46597
47190
  const text = labelText(p);
46598
- const w = measureLegendText(text, FONT);
46599
- const h = FONT * 1.25;
46600
- const inline = { x: p.cx + p.r + 3, y: p.cy - h / 2, w, h };
46601
- if (!collides(inline)) {
46602
- obstacles.push(inline);
47191
+ return { text, w: measureLegendText(text, FONT) };
47192
+ };
47193
+ const pushInline = (p, text, w, side) => {
47194
+ const tx = side === "right" ? p.cx + p.r + 3 : p.cx - p.r - 3;
47195
+ obstacles.push({
47196
+ x: side === "right" ? tx : tx - w,
47197
+ y: p.cy - poiLabH / 2,
47198
+ w,
47199
+ h: poiLabH
47200
+ });
47201
+ labels.push({
47202
+ x: tx,
47203
+ y: p.cy + FONT / 3,
47204
+ text,
47205
+ anchor: side === "right" ? "start" : "end",
47206
+ color: palette.text,
47207
+ halo: true,
47208
+ haloColor: palette.bg,
47209
+ poiId: p.id,
47210
+ lineNumber: p.lineNumber
47211
+ });
47212
+ };
47213
+ const inlineFits = (p, w, side) => {
47214
+ const tx = side === "right" ? p.cx + p.r + 3 : p.cx - p.r - 3;
47215
+ const rect = {
47216
+ x: side === "right" ? tx : tx - w,
47217
+ y: p.cy - poiLabH / 2,
47218
+ w,
47219
+ h: poiLabH
47220
+ };
47221
+ return rect.x >= 0 && rect.x + rect.w <= width && !collides(rect);
47222
+ };
47223
+ const GROUP_R = 30;
47224
+ const groups = [];
47225
+ for (const p of ordered) {
47226
+ const near = groups.find(
47227
+ (g) => g.some((q) => Math.hypot(q.cx - p.cx, q.cy - p.cy) < GROUP_R)
47228
+ );
47229
+ if (near) near.push(p);
47230
+ else groups.push([p]);
47231
+ }
47232
+ const ROW_GAP2 = 3;
47233
+ const step = poiLabH + ROW_GAP2;
47234
+ const COL_GAP = 16;
47235
+ const placeColumn = (group) => {
47236
+ const items = group.map((p) => ({ p, ...labelInfo(p) })).sort((a, b) => a.p.cy - b.p.cy || (a.text < b.text ? -1 : 1));
47237
+ const left = Math.min(...items.map((o) => o.p.cx - o.p.r));
47238
+ const right = Math.max(...items.map((o) => o.p.cx + o.p.r));
47239
+ const cyMid = (Math.min(...items.map((o) => o.p.cy)) + Math.max(...items.map((o) => o.p.cy))) / 2;
47240
+ const maxW = Math.max(...items.map((o) => o.w));
47241
+ const side = right + COL_GAP + maxW <= width - 2 ? "right" : "left";
47242
+ const colX = side === "right" ? right + COL_GAP : left - COL_GAP;
47243
+ const totalH = items.length * step;
47244
+ let startY = cyMid - totalH / 2;
47245
+ startY = Math.max(2, Math.min(startY, height - totalH - 2));
47246
+ items.forEach((o, i) => {
47247
+ const rowCy = startY + i * step + step / 2;
47248
+ obstacles.push({
47249
+ x: side === "right" ? colX : colX - o.w,
47250
+ y: rowCy - poiLabH / 2,
47251
+ w: o.w,
47252
+ h: poiLabH
47253
+ });
46603
47254
  labels.push({
46604
- x: inline.x,
46605
- y: p.cy + FONT / 3,
46606
- text,
46607
- anchor: "start",
47255
+ x: colX,
47256
+ y: rowCy + FONT / 3,
47257
+ text: o.text,
47258
+ anchor: side === "right" ? "start" : "end",
46608
47259
  color: palette.text,
46609
47260
  halo: true,
46610
- lineNumber: p.lineNumber
47261
+ haloColor: palette.bg,
47262
+ leader: {
47263
+ x1: o.p.cx,
47264
+ y1: o.p.cy,
47265
+ x2: side === "right" ? colX - 2 : colX + 2,
47266
+ y2: rowCy
47267
+ },
47268
+ leaderColor: o.p.fill,
47269
+ poiId: o.p.id,
47270
+ lineNumber: o.p.lineNumber
46611
47271
  });
46612
- continue;
46613
- }
46614
- let placed = false;
46615
- for (let k = 1; k <= 2 && !placed; k++) {
46616
- for (const [dx, dy] of RING_DIRS) {
46617
- const cx = p.cx + dx * LEADER_STEP * k;
46618
- const cy = p.cy + dy * LEADER_STEP * k;
46619
- const rect = {
46620
- x: dx >= 0 ? cx : cx - w,
46621
- y: cy - h / 2,
46622
- w,
46623
- h
46624
- };
46625
- if (rect.x < 0 || rect.x + rect.w > width || rect.y < 0 || rect.y + rect.h > height) {
46626
- continue;
46627
- }
46628
- if (collides(rect)) continue;
46629
- obstacles.push(rect);
46630
- labels.push({
46631
- x: cx,
46632
- y: cy + FONT / 3,
46633
- text,
46634
- anchor: dx >= 0 ? "start" : "end",
46635
- color: palette.text,
46636
- halo: true,
46637
- leader: { x1: p.cx, y1: p.cy, x2: cx, y2: cy },
46638
- lineNumber: p.lineNumber
46639
- });
46640
- placed = true;
46641
- break;
47272
+ });
47273
+ };
47274
+ for (const g of groups) {
47275
+ if (g.length === 1) {
47276
+ const p = g[0];
47277
+ const { text, w } = labelInfo(p);
47278
+ if (inlineFits(p, w, "right")) {
47279
+ pushInline(p, text, w, "right");
47280
+ continue;
47281
+ }
47282
+ if (inlineFits(p, w, "left")) {
47283
+ pushInline(p, text, w, "left");
47284
+ continue;
46642
47285
  }
46643
47286
  }
46644
- if (placed) continue;
46645
- pinCounter += 1;
46646
- pinList.push({ pin: pinCounter, label: text });
46647
- labels.push({
46648
- x: p.cx + p.r + 2,
46649
- y: p.cy - p.r,
46650
- text: String(pinCounter),
46651
- anchor: "start",
46652
- color: palette.text,
46653
- halo: true,
46654
- pin: pinCounter,
46655
- lineNumber: p.lineNumber
46656
- });
47287
+ placeColumn(g);
46657
47288
  }
46658
47289
  }
46659
47290
  let legend = null;
@@ -46662,8 +47293,7 @@ function layoutMap(resolved, data, size, opts) {
46662
47293
  name: g.name,
46663
47294
  entries: g.entries.map((e) => ({ value: e.value, color: e.color }))
46664
47295
  }));
46665
- const hasAnything = tagGroups.length > 0 || hasRamp || sizeVals.length > 0 || weightVals.length > 0;
46666
- if (hasAnything) {
47296
+ if (tagGroups.length > 0 || hasRamp) {
46667
47297
  legend = {
46668
47298
  tagGroups,
46669
47299
  activeGroup,
@@ -46674,20 +47304,9 @@ function layoutMap(resolved, data, size, opts) {
46674
47304
  },
46675
47305
  min: rampMin,
46676
47306
  max: rampMax,
46677
- hue: rampHue
47307
+ hue: rampHue,
47308
+ base: rampBase
46678
47309
  }
46679
- },
46680
- ...sizeVals.length > 0 && {
46681
- size: {
46682
- ...resolved.directives.sizeMetric !== void 0 && {
46683
- metric: resolved.directives.sizeMetric
46684
- },
46685
- min: sizeMin,
46686
- max: sizeMax
46687
- }
46688
- },
46689
- ...weightVals.length > 0 && {
46690
- weight: { min: wMin, max: wMax }
46691
47310
  }
46692
47311
  };
46693
47312
  }
@@ -46695,26 +47314,28 @@ function layoutMap(resolved, data, size, opts) {
46695
47314
  return {
46696
47315
  width,
46697
47316
  height,
46698
- background: palette.bg,
47317
+ background: water,
46699
47318
  title: resolved.title,
46700
47319
  ...resolved.subtitle !== void 0 && { subtitle: resolved.subtitle },
46701
47320
  ...resolved.caption !== void 0 && { caption: resolved.caption },
46702
47321
  regions,
47322
+ rivers,
46703
47323
  legs,
46704
47324
  pois,
46705
47325
  labels,
46706
- pinList,
46707
- legend
47326
+ legend,
47327
+ insets,
47328
+ insetRegions
46708
47329
  };
46709
47330
  }
46710
- 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;
47331
+ 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;
46711
47332
  var init_layout15 = __esm({
46712
47333
  "src/map/layout.ts"() {
46713
47334
  "use strict";
46714
47335
  init_color_utils();
46715
- init_tag_groups();
46716
47336
  init_label_layout();
46717
47337
  init_legend_constants();
47338
+ init_title_constants();
46718
47339
  FIT_PAD = 24;
46719
47340
  RAMP_FLOOR = 15;
46720
47341
  R_DEFAULT = 6;
@@ -46723,23 +47344,32 @@ var init_layout15 = __esm({
46723
47344
  W_MIN = 1.25;
46724
47345
  W_MAX = 8;
46725
47346
  FONT = 11;
46726
- LEADER_STEP = 14;
46727
47347
  COLO_EPS = 1.5;
47348
+ LAND_TINT_LIGHT = 58;
47349
+ LAND_TINT_DARK = 75;
47350
+ TAG_TINT_LIGHT = 60;
47351
+ TAG_TINT_DARK = 68;
47352
+ WATER_TINT = 55;
47353
+ RIVER_WIDTH = 1.3;
47354
+ FOREIGN_TINT_LIGHT = 30;
47355
+ FOREIGN_TINT_DARK = 62;
46728
47356
  COLO_R = 9;
46729
47357
  GOLDEN_ANGLE = 2.399963229728653;
46730
47358
  FAN_STEP = 16;
46731
- TINY_REGION_AREA = 600;
46732
47359
  ARC_CURVE_FRAC = 0.18;
46733
- RING_DIRS = [
46734
- [1, 0],
46735
- [0, 1],
46736
- [-1, 0],
46737
- [0, -1],
46738
- [1, 1],
46739
- [-1, 1],
46740
- [-1, -1],
46741
- [1, -1]
46742
- ];
47360
+ usConusProjection = () => geoConicEqualArea().parallels([29.5, 45.5]).rotate([96, 0]);
47361
+ alaskaProjection = () => geoConicEqualArea().rotate([154, 0]).center([-2, 58.5]).parallels([55, 65]);
47362
+ hawaiiProjection = () => geoMercator();
47363
+ INSET_STATES = /* @__PURE__ */ new Set(["US-AK", "US-HI"]);
47364
+ US_NON_CONUS = /* @__PURE__ */ new Set([
47365
+ "US-AK",
47366
+ "US-HI",
47367
+ "US-AS",
47368
+ "US-GU",
47369
+ "US-MP",
47370
+ "US-PR",
47371
+ "US-VI"
47372
+ ]);
46743
47373
  }
46744
47374
  });
46745
47375
 
@@ -46750,7 +47380,7 @@ __export(renderer_exports16, {
46750
47380
  renderMapForExport: () => renderMapForExport
46751
47381
  });
46752
47382
  import * as d3Selection18 from "d3-selection";
46753
- function renderMap(container, resolved, data, palette, isDark, onClickItem, exportDims) {
47383
+ function renderMap(container, resolved, data, palette, isDark, onClickItem, exportDims, activeGroupOverride) {
46754
47384
  d3Selection18.select(container).selectAll(":not([data-d3-tooltip])").remove();
46755
47385
  const width = exportDims?.width ?? container.clientWidth;
46756
47386
  const height = exportDims?.height ?? container.clientHeight;
@@ -46761,27 +47391,29 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46761
47391
  { width, height },
46762
47392
  {
46763
47393
  palette,
46764
- isDark
47394
+ isDark,
47395
+ ...activeGroupOverride !== void 0 && {
47396
+ activeGroup: activeGroupOverride
47397
+ }
46765
47398
  }
46766
47399
  );
46767
- 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);
47400
+ 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);
46768
47401
  svg.append("rect").attr("width", width).attr("height", height).attr("fill", layout.background);
46769
- const arrowColor = mix(palette.text, palette.bg, 50);
46770
47402
  const defs = svg.append("defs");
46771
- 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);
46772
- const haloColor = layout.background;
46773
- if (layout.title) {
46774
- 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);
46775
- }
46776
- if (layout.subtitle) {
46777
- 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);
46778
- }
46779
- if (layout.caption) {
46780
- 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);
46781
- }
47403
+ const arrowSize = (w) => Math.min(15, 7 + w * 0.95);
47404
+ const haloColor = palette.bg;
46782
47405
  const gRegions = svg.append("g").attr("class", "dgmo-map-regions");
46783
- for (const r of layout.regions) {
46784
- const p = gRegions.append("path").attr("d", r.d).attr("fill", r.fill).attr("stroke", r.stroke).attr("stroke-width", 0.5);
47406
+ const drawRegion = (g, r, strokeWidth) => {
47407
+ const p = g.append("path").attr("d", r.d).attr("fill", r.fill).attr("stroke", r.stroke).attr("stroke-width", strokeWidth);
47408
+ if (r.layer !== "base") {
47409
+ p.classed("dgmo-map-region", true).attr("data-region", r.id);
47410
+ if (r.score !== void 0) p.attr("data-score", r.score);
47411
+ if (r.tags) {
47412
+ for (const [group, value] of Object.entries(r.tags)) {
47413
+ p.attr(`data-tag-${group.toLowerCase()}`, value.toLowerCase());
47414
+ }
47415
+ }
47416
+ }
46785
47417
  if (r.lineNumber >= 0) {
46786
47418
  p.attr("data-line-number", r.lineNumber);
46787
47419
  if (onClickItem) {
@@ -46791,11 +47423,31 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46791
47423
  );
46792
47424
  }
46793
47425
  }
47426
+ };
47427
+ for (const r of layout.regions) drawRegion(gRegions, r, 0.5);
47428
+ if (layout.rivers.length) {
47429
+ const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none");
47430
+ for (const r of layout.rivers) {
47431
+ gRivers.append("path").attr("d", r.d).attr("stroke", r.color).attr("stroke-width", r.width).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
47432
+ }
47433
+ }
47434
+ if (layout.insets.length) {
47435
+ const insetG = svg.append("g").attr("class", "dgmo-map-insets");
47436
+ for (const box of layout.insets) {
47437
+ const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
47438
+ 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");
47439
+ }
47440
+ for (const r of layout.insetRegions) drawRegion(insetG, r, 0.5);
46794
47441
  }
46795
47442
  const gLegs = svg.append("g").attr("class", "dgmo-map-legs").attr("fill", "none");
46796
- for (const leg of layout.legs) {
47443
+ layout.legs.forEach((leg, i) => {
46797
47444
  const p = gLegs.append("path").attr("d", leg.d).attr("stroke", leg.color).attr("stroke-width", leg.width).attr("stroke-linecap", "round");
46798
- if (leg.arrow) p.attr("marker-end", "url(#dgmo-map-arrow)");
47445
+ if (leg.arrow) {
47446
+ const id = `dgmo-map-arrow-${i}`;
47447
+ const s = arrowSize(leg.width);
47448
+ 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);
47449
+ p.attr("marker-end", `url(#${id})`);
47450
+ }
46799
47451
  if (leg.label !== void 0 && leg.labelX !== void 0) {
46800
47452
  emitText(
46801
47453
  gLegs,
@@ -46809,13 +47461,13 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46809
47461
  LABEL_FONT - 1
46810
47462
  );
46811
47463
  }
46812
- }
47464
+ });
46813
47465
  const gPois = svg.append("g").attr("class", "dgmo-map-pois");
46814
47466
  for (const poi of layout.pois) {
46815
47467
  if (poi.isOrigin) {
46816
47468
  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);
46817
47469
  }
46818
- 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);
47470
+ 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);
46819
47471
  if (onClickItem) {
46820
47472
  c.style("cursor", "pointer").on(
46821
47473
  "click",
@@ -46839,48 +47491,66 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46839
47491
  const gLabels = svg.append("g").attr("class", "dgmo-map-labels");
46840
47492
  for (const lab of layout.labels) {
46841
47493
  if (lab.leader) {
46842
- 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);
47494
+ 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(
47495
+ "stroke",
47496
+ lab.leaderColor ?? mix(palette.textMuted, palette.bg, 60)
47497
+ ).attr("stroke-width", lab.leaderColor ? 1 : 0.75);
47498
+ if (lab.poiId !== void 0) line12.attr("data-poi", lab.poiId);
46843
47499
  }
46844
- if (lab.pin !== void 0) {
46845
- 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);
46846
- }
46847
- emitText(
47500
+ const t = emitText(
46848
47501
  gLabels,
46849
47502
  lab.x,
46850
47503
  lab.y,
46851
47504
  lab.text,
46852
47505
  lab.anchor,
46853
47506
  lab.color,
46854
- haloColor,
47507
+ lab.haloColor,
46855
47508
  lab.halo,
46856
47509
  LABEL_FONT
46857
47510
  );
46858
- }
46859
- if (layout.pinList.length > 0) {
46860
- const gPins = svg.append("g").attr("class", "dgmo-map-pin-list").attr(
46861
- "transform",
46862
- `translate(12, ${height - layout.pinList.length * 14 - 8})`
46863
- );
46864
- layout.pinList.forEach((entry, i) => {
46865
- 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}`);
46866
- });
47511
+ if (lab.poiId !== void 0) {
47512
+ t.attr("data-poi", lab.poiId).style("cursor", "default");
47513
+ }
46867
47514
  }
46868
47515
  if (layout.legend) {
46869
47516
  const legendY = (layout.title ? TITLE_Y + TITLE_FONT_SIZE : 0) + (layout.subtitle ? TITLE_FONT_SIZE : 0) + 8;
46870
47517
  const legendG = svg.append("g").attr("class", "dgmo-map-legend").attr("transform", `translate(0, ${legendY})`);
46871
- const groups = layout.legend.tagGroups.filter((g) => g.entries.length > 0);
47518
+ const ramp = layout.legend.ramp;
47519
+ const scoreGroup = ramp ? {
47520
+ name: ramp.metric?.trim() || "Score",
47521
+ entries: [],
47522
+ gradient: {
47523
+ min: ramp.min,
47524
+ max: ramp.max,
47525
+ hue: ramp.hue,
47526
+ base: ramp.base
47527
+ }
47528
+ } : null;
47529
+ const tagGroups = layout.legend.tagGroups.filter((g) => g.entries.length > 0).map((g) => ({ name: g.name, entries: [...g.entries] }));
47530
+ const groups = [...scoreGroup ? [scoreGroup] : [], ...tagGroups];
46872
47531
  if (groups.length > 0) {
46873
47532
  const config = {
46874
- groups: groups.map((g) => ({ name: g.name, entries: [...g.entries] })),
47533
+ groups,
46875
47534
  position: { placement: "top-center", titleRelation: "below-title" },
46876
47535
  mode: exportDims ? "export" : "preview",
46877
- showEmptyGroups: false
47536
+ showEmptyGroups: false,
47537
+ // Keep inactive siblings visible as pills so the user can click to flip
47538
+ // the active colouring dimension (preview only — export shows just the
47539
+ // active group).
47540
+ showInactivePills: true
46878
47541
  };
46879
47542
  const state = { activeGroup: layout.legend.activeGroup };
46880
47543
  renderLegendD3(legendG, config, state, palette, isDark, void 0, width);
46881
47544
  }
46882
- const pinGap = layout.pinList.length ? layout.pinList.length * 14 + 14 : 0;
46883
- emitExtraLegend(svg, layout, palette, height, pinGap);
47545
+ }
47546
+ if (layout.title) {
47547
+ 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);
47548
+ }
47549
+ if (layout.subtitle) {
47550
+ 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);
47551
+ }
47552
+ if (layout.caption) {
47553
+ 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);
46884
47554
  }
46885
47555
  }
46886
47556
  function renderMapForExport(container, resolved, data, palette, isDark, exportDims) {
@@ -46891,54 +47561,7 @@ function emitText(g, x, y, text, anchor, color, halo, withHalo, fontSize) {
46891
47561
  if (withHalo) {
46892
47562
  t.attr("paint-order", "stroke fill").attr("stroke", halo).attr("stroke-width", 3).attr("stroke-linejoin", "round").attr("stroke-opacity", 0.7);
46893
47563
  }
46894
- }
46895
- function emitExtraLegend(svg, layout, palette, height, bottomGap) {
46896
- const { legend } = layout;
46897
- if (!legend) return;
46898
- if (!legend.ramp && !legend.size && !legend.weight) return;
46899
- const blocks = [];
46900
- const g = svg.append("g").attr("class", "dgmo-map-legend-keys").attr("transform", `translate(12, ${height - 56 - bottomGap})`);
46901
- let xCursor = 0;
46902
- if (legend.ramp) {
46903
- const ramp = legend.ramp;
46904
- blocks.push(() => {
46905
- const block = g.append("g").attr("transform", `translate(${xCursor},0)`);
46906
- const gradId = "dgmo-map-ramp";
46907
- const grad = block.append("defs").append("linearGradient").attr("id", gradId).attr("x1", "0%").attr("x2", "100%");
46908
- grad.append("stop").attr("offset", "0%").attr("stop-color", mix(ramp.hue, palette.bg, 15));
46909
- grad.append("stop").attr("offset", "100%").attr("stop-color", ramp.hue);
46910
- block.append("rect").attr("width", 80).attr("height", 8).attr("fill", `url(#${gradId})`);
46911
- block.append("text").attr("x", 0).attr("y", 22).attr("font-size", 9).attr("fill", palette.textMuted).text(String(ramp.min));
46912
- 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));
46913
- if (ramp.metric) {
46914
- block.append("text").attr("x", 0).attr("y", -4).attr("font-size", 9).attr("fill", palette.textMuted).text(ramp.metric);
46915
- }
46916
- xCursor += 110;
46917
- });
46918
- }
46919
- if (legend.size) {
46920
- const sz = legend.size;
46921
- blocks.push(() => {
46922
- const block = g.append("g").attr("transform", `translate(${xCursor},0)`);
46923
- [3, 6, 10].forEach((r, i) => {
46924
- block.append("circle").attr("cx", i * 26 + r).attr("cy", 8).attr("r", r).attr("fill", "none").attr("stroke", palette.textMuted);
46925
- });
46926
- block.append("text").attr("x", 0).attr("y", -4).attr("font-size", 9).attr("fill", palette.textMuted).text(sz.metric ?? "size");
46927
- xCursor += 110;
46928
- });
46929
- }
46930
- if (legend.weight) {
46931
- const wt = legend.weight;
46932
- blocks.push(() => {
46933
- const block = g.append("g").attr("transform", `translate(${xCursor},0)`);
46934
- [1, 3, 6].forEach((w, i) => {
46935
- 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);
46936
- });
46937
- block.append("text").attr("x", 0).attr("y", -4).attr("font-size", 9).attr("fill", palette.textMuted).text(wt.metric ?? "weight");
46938
- xCursor += 110;
46939
- });
46940
- }
46941
- for (const draw of blocks) draw();
47564
+ return t;
46942
47565
  }
46943
47566
  var LABEL_FONT;
46944
47567
  var init_renderer16 = __esm({
@@ -53466,18 +54089,18 @@ function renderWordCloud(container, parsed, palette, _isDark, onClickItem, expor
53466
54089
  }).start();
53467
54090
  }
53468
54091
  function renderWordCloudAsync(container, parsed, palette, _isDark, exportDims) {
53469
- return new Promise((resolve2) => {
54092
+ return new Promise((resolve) => {
53470
54093
  d3Selection23.select(container).selectAll(":not([data-d3-tooltip])").remove();
53471
54094
  const { words, cloudOptions } = parsed;
53472
54095
  const title = parsed.noTitle ? null : parsed.title;
53473
54096
  if (words.length === 0) {
53474
- resolve2();
54097
+ resolve();
53475
54098
  return;
53476
54099
  }
53477
54100
  const width = exportDims?.width ?? container.clientWidth;
53478
54101
  const height = exportDims?.height ?? container.clientHeight;
53479
54102
  if (width <= 0 || height <= 0) {
53480
- resolve2();
54103
+ resolve();
53481
54104
  return;
53482
54105
  }
53483
54106
  const titleHeight = title ? 40 : 0;
@@ -53506,7 +54129,7 @@ function renderWordCloudAsync(container, parsed, palette, _isDark, exportDims) {
53506
54129
  "transform",
53507
54130
  (d) => `translate(${d.x},${d.y}) rotate(${d.rotate})`
53508
54131
  ).text((d) => d.text);
53509
- resolve2();
54132
+ resolve();
53510
54133
  }).start();
53511
54134
  });
53512
54135
  }
@@ -56656,12 +57279,12 @@ var KNOWN_HEADER_OPTIONS = /* @__PURE__ */ new Set([
56656
57279
  "solid-fill",
56657
57280
  "active-tag"
56658
57281
  ]);
56659
- function dirname2(filePath) {
57282
+ function dirname(filePath) {
56660
57283
  const last = filePath.lastIndexOf("/");
56661
57284
  return last > 0 ? filePath.substring(0, last) : "/";
56662
57285
  }
56663
57286
  function resolvePath(base, relative) {
56664
- const parts = dirname2(base).split("/");
57287
+ const parts = dirname(base).split("/");
56665
57288
  for (const seg of relative.split("/")) {
56666
57289
  if (seg === "..") {
56667
57290
  if (parts.length > 1) parts.pop();
@@ -57564,10 +58187,13 @@ var COMPLETION_REGISTRY = /* @__PURE__ */ new Map([
57564
58187
  // keywords, not directives; metadata keys (score/size/label) live in the
57565
58188
  // reserved-key registry.
57566
58189
  withGlobals({
57567
- region: { description: "Force a basemap/extent (world | us-states)" },
58190
+ region: {
58191
+ description: "Basemap: us-states (force US state mesh + scoping) | world (inert \u2014 already the default)",
58192
+ values: ["us-states", "world"]
58193
+ },
57568
58194
  projection: {
57569
58195
  description: "Override the auto projection",
57570
- values: ["natural-earth", "albers-usa", "mercator"]
58196
+ values: ["equirectangular", "natural-earth", "albers-usa", "mercator"]
57571
58197
  },
57572
58198
  metric: { description: "Label for the region score ramp" },
57573
58199
  "size-metric": { description: "Label for the POI size channel" },
@@ -59160,6 +59786,8 @@ export {
59160
59786
  looksLikeSitemap,
59161
59787
  looksLikeState,
59162
59788
  makeDgmoError,
59789
+ mapBackgroundColor,
59790
+ mapNeutralLandColor,
59163
59791
  matchesContiguously,
59164
59792
  measurePertAnalysisBlock,
59165
59793
  migrateContent,