@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/advanced.cjs CHANGED
@@ -333,6 +333,33 @@ function rectCircleOverlap(rect, circle) {
333
333
  const dy = nearestY - circle.cy;
334
334
  return dx * dx + dy * dy < circle.r * circle.r;
335
335
  }
336
+ function segmentRectOverlap(x0, y0, x1, y1, rect) {
337
+ const dx = x1 - x0;
338
+ const dy = y1 - y0;
339
+ let t0 = 0;
340
+ let t1 = 1;
341
+ const edges = [
342
+ [-dx, x0 - rect.x],
343
+ [dx, rect.x + rect.w - x0],
344
+ [-dy, y0 - rect.y],
345
+ [dy, rect.y + rect.h - y0]
346
+ ];
347
+ for (const [p, q] of edges) {
348
+ if (p === 0) {
349
+ if (q < 0) return false;
350
+ } else {
351
+ const t = q / p;
352
+ if (p < 0) {
353
+ if (t > t1) return false;
354
+ if (t > t0) t0 = t;
355
+ } else {
356
+ if (t < t0) return false;
357
+ if (t < t1) t1 = t;
358
+ }
359
+ }
360
+ }
361
+ return true;
362
+ }
336
363
  function computeQuadrantPointLabels(points, chartBounds, obstacles, pointRadius, fontSize) {
337
364
  const labelHeight = fontSize + 4;
338
365
  const stepSize = labelHeight + 2;
@@ -3278,6 +3305,57 @@ var init_legend_constants = __esm({
3278
3305
  });
3279
3306
 
3280
3307
  // src/utils/legend-layout.ts
3308
+ function fmtRamp(n) {
3309
+ return Number.isInteger(n) ? String(n) : String(Math.round(n * 10) / 10);
3310
+ }
3311
+ function gradientCapsuleWidth(name, gradient) {
3312
+ const pw = pillWidth(name);
3313
+ const minW = measureLegendText(fmtRamp(gradient.min), LEGEND_ENTRY_FONT_SIZE);
3314
+ const maxW = measureLegendText(fmtRamp(gradient.max), LEGEND_ENTRY_FONT_SIZE);
3315
+ return LEGEND_CAPSULE_PAD + pw + 4 + minW + RAMP_LABEL_GAP + RAMP_LEGEND_W + RAMP_LABEL_GAP + maxW + LEGEND_CAPSULE_PAD;
3316
+ }
3317
+ function buildGradientCapsuleLayout(group, gradient) {
3318
+ const pw = pillWidth(group.name);
3319
+ const minText = fmtRamp(gradient.min);
3320
+ const maxText = fmtRamp(gradient.max);
3321
+ const minW = measureLegendText(minText, LEGEND_ENTRY_FONT_SIZE);
3322
+ const gx = LEGEND_CAPSULE_PAD + pw + 4;
3323
+ const minX = gx;
3324
+ const rampX = gx + minW + RAMP_LABEL_GAP;
3325
+ const maxX = rampX + RAMP_LEGEND_W + RAMP_LABEL_GAP;
3326
+ const width = gradientCapsuleWidth(group.name, gradient);
3327
+ return {
3328
+ groupName: group.name,
3329
+ x: 0,
3330
+ y: 0,
3331
+ width,
3332
+ height: LEGEND_HEIGHT,
3333
+ pill: {
3334
+ groupName: group.name,
3335
+ x: LEGEND_CAPSULE_PAD,
3336
+ y: LEGEND_CAPSULE_PAD,
3337
+ width: pw,
3338
+ height: LEGEND_HEIGHT - LEGEND_CAPSULE_PAD * 2,
3339
+ isActive: true
3340
+ },
3341
+ entries: [],
3342
+ gradient: {
3343
+ rampX,
3344
+ rampY: (LEGEND_HEIGHT - RAMP_LEGEND_H) / 2,
3345
+ rampW: RAMP_LEGEND_W,
3346
+ rampH: RAMP_LEGEND_H,
3347
+ min: gradient.min,
3348
+ max: gradient.max,
3349
+ minText,
3350
+ minX,
3351
+ maxText,
3352
+ maxX,
3353
+ textY: LEGEND_HEIGHT / 2,
3354
+ hue: gradient.hue,
3355
+ base: gradient.base
3356
+ }
3357
+ };
3358
+ }
3281
3359
  function pillWidth(name) {
3282
3360
  return measureLegendText(name, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD;
3283
3361
  }
@@ -3420,7 +3498,7 @@ function computeLegendLayout(config, state, containerWidth) {
3420
3498
  };
3421
3499
  }
3422
3500
  const controlsGroupLayout = isExport ? void 0 : buildControlsGroupLayout(config, state);
3423
- const visibleGroups = config.showEmptyGroups ? groups : groups.filter((g) => g.entries.length > 0);
3501
+ const visibleGroups = config.showEmptyGroups ? groups : groups.filter((g) => g.entries.length > 0 || !!g.gradient);
3424
3502
  if (visibleGroups.length === 0 && (!configControls || configControls.length === 0) && !controlsGroupLayout) {
3425
3503
  return {
3426
3504
  height: 0,
@@ -3499,7 +3577,7 @@ function computeLegendLayout(config, state, containerWidth) {
3499
3577
  groupAvailW,
3500
3578
  config.capsulePillAddonWidth ?? 0
3501
3579
  );
3502
- } else if (!activeGroupName) {
3580
+ } else if (!activeGroupName || config.showInactivePills) {
3503
3581
  const pw = pillWidth(g.name);
3504
3582
  pills.push({
3505
3583
  groupName: g.name,
@@ -3537,6 +3615,7 @@ function computeLegendLayout(config, state, containerWidth) {
3537
3615
  };
3538
3616
  }
3539
3617
  function buildCapsuleLayout(group, containerWidth, addonWidth = 0) {
3618
+ if (group.gradient) return buildGradientCapsuleLayout(group, group.gradient);
3540
3619
  const pw = pillWidth(group.name);
3541
3620
  const info = capsuleWidth(
3542
3621
  group.name,
@@ -3711,7 +3790,7 @@ function getMaxLegendReservedHeight(config, containerWidth) {
3711
3790
  }
3712
3791
  return max;
3713
3792
  }
3714
- var CONTROL_PILL_PAD, CONTROL_FONT_SIZE, CONTROL_ICON_GAP, CONTROL_GAP;
3793
+ var CONTROL_PILL_PAD, CONTROL_FONT_SIZE, CONTROL_ICON_GAP, CONTROL_GAP, RAMP_LEGEND_W, RAMP_LEGEND_H, RAMP_LABEL_GAP;
3715
3794
  var init_legend_layout = __esm({
3716
3795
  "src/utils/legend-layout.ts"() {
3717
3796
  "use strict";
@@ -3720,6 +3799,9 @@ var init_legend_layout = __esm({
3720
3799
  CONTROL_FONT_SIZE = 11;
3721
3800
  CONTROL_ICON_GAP = 4;
3722
3801
  CONTROL_GAP = 8;
3802
+ RAMP_LEGEND_W = 80;
3803
+ RAMP_LEGEND_H = 8;
3804
+ RAMP_LABEL_GAP = 6;
3723
3805
  }
3724
3806
  });
3725
3807
 
@@ -3807,6 +3889,16 @@ function renderCapsule(parent, capsule, palette, groupBg, pillBorder, _isDark, c
3807
3889
  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);
3808
3890
  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);
3809
3891
  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);
3892
+ if (capsule.gradient) {
3893
+ const gr = capsule.gradient;
3894
+ const gradId = `dgmo-legend-ramp-${capsule.groupName.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
3895
+ const def = g.append("defs").append("linearGradient").attr("id", gradId);
3896
+ def.append("stop").attr("offset", "0%").attr("stop-color", mix(gr.hue, gr.base, 15));
3897
+ def.append("stop").attr("offset", "100%").attr("stop-color", gr.hue);
3898
+ 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);
3899
+ 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})`);
3900
+ 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);
3901
+ }
3810
3902
  for (const entry of capsule.entries) {
3811
3903
  const entryG = g.append("g").attr("data-legend-entry", entry.value.toLowerCase()).attr("data-series-name", entry.value).style("cursor", "pointer");
3812
3904
  entryG.append("circle").attr("cx", entry.dotCx).attr("cy", entry.dotCy).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
@@ -11228,23 +11320,22 @@ function parseC4(content, palette) {
11228
11320
  }
11229
11321
  }
11230
11322
  const parent = findParentElement(indent, stack);
11231
- if (parent) {
11232
- const descResult = tryStripDescriptionKeyword(trimmed);
11323
+ const descResult = tryStripDescriptionKeyword(trimmed);
11324
+ if (parent && descResult.isKeyword) {
11233
11325
  if (descResult.needsColon) {
11234
11326
  pushError(
11235
11327
  lineNumber,
11236
- `Use "description: ${descResult.text}" \u2014 colon is required.`,
11328
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`,
11237
11329
  "warning"
11238
11330
  );
11239
11331
  }
11240
- const descText = descResult.isKeyword ? descResult.text : trimmed;
11241
11332
  let desc = elementDescription.get(parent.element);
11242
11333
  if (!desc) {
11243
11334
  desc = [];
11244
11335
  elementDescription.set(parent.element, desc);
11245
11336
  parent.element.description = desc;
11246
11337
  }
11247
- desc.push(descText);
11338
+ desc.push(descResult.text);
11248
11339
  } else {
11249
11340
  pushError(lineNumber, `Unexpected content: "${trimmed}"`);
11250
11341
  }
@@ -11711,7 +11802,7 @@ function parseSitemap(content, palette) {
11711
11802
  if (descResult.needsColon) {
11712
11803
  pushWarning(
11713
11804
  lineNumber,
11714
- `Use "description: ${descResult.text}" \u2014 colon is required.`
11805
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`
11715
11806
  );
11716
11807
  }
11717
11808
  const parent = findParentNode(indent, indentStack);
@@ -12539,23 +12630,22 @@ function parseInfra(content) {
12539
12630
  }
12540
12631
  }
12541
12632
  const descResult = tryStripDescriptionKeyword(trimmed);
12542
- if (descResult.isKeyword && currentNode.isEdge) {
12543
- continue;
12544
- }
12545
- if (!currentNode.isEdge) {
12633
+ if (descResult.isKeyword) {
12634
+ if (currentNode.isEdge) {
12635
+ continue;
12636
+ }
12546
12637
  if (descResult.needsColon) {
12547
12638
  warn(
12548
12639
  lineNumber,
12549
- `Use "description: ${descResult.text}" \u2014 colon is required.`
12640
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`
12550
12641
  );
12551
12642
  }
12552
- const descText = descResult.isKeyword ? descResult.text : trimmed;
12553
- pushDescription(currentNode, descText);
12643
+ pushDescription(currentNode, descResult.text);
12554
12644
  continue;
12555
12645
  }
12556
12646
  warn(
12557
12647
  lineNumber,
12558
- `Unexpected line inside component '${currentNode.label}'. Expected a property (key: value), connection (-> Target), or description text.`
12648
+ `Unexpected line inside component '${currentNode.label}'. Expected a property (key: value), connection (-> Target), or a description (description: text).`
12559
12649
  );
12560
12650
  continue;
12561
12651
  }
@@ -15692,9 +15782,6 @@ function parseMap(content) {
15692
15782
  const pushWarning = (line12, message) => {
15693
15783
  diagnostics.push(makeDgmoError(line12, message, "warning"));
15694
15784
  };
15695
- const pushInfo = (line12, message) => {
15696
- diagnostics.push(makeDgmoError(line12, message, "warning"));
15697
- };
15698
15785
  const lines = content.split("\n");
15699
15786
  let firstIdx = 0;
15700
15787
  while (firstIdx < lines.length) {
@@ -15824,10 +15911,15 @@ function parseMap(content) {
15824
15911
  break;
15825
15912
  case "projection":
15826
15913
  dup(d.projection);
15827
- if (value && !["natural-earth", "albers-usa", "mercator"].includes(value))
15914
+ if (value && ![
15915
+ "equirectangular",
15916
+ "natural-earth",
15917
+ "albers-usa",
15918
+ "mercator"
15919
+ ].includes(value))
15828
15920
  pushWarning(
15829
15921
  line12,
15830
- `Unknown projection "${value}" (expected natural-earth | albers-usa | mercator).`
15922
+ `Unknown projection "${value}" (expected equirectangular | natural-earth | albers-usa | mercator).`
15831
15923
  );
15832
15924
  d.projection = value;
15833
15925
  break;
@@ -15966,17 +16058,21 @@ function parseMap(content) {
15966
16058
  scoreNum = void 0;
15967
16059
  }
15968
16060
  }
15969
- if (scoreNum !== void 0 && Object.keys(tags).length)
15970
- pushInfo(
15971
- line12,
15972
- "A region has both `score:` and a tag value \u2014 v1 renders only the score (bivariate is a future seam)."
15973
- );
16061
+ let regionName = split.name;
16062
+ let regionScope;
16063
+ const rToks = regionName.split(/\s+/);
16064
+ const rLast = rToks[rToks.length - 1];
16065
+ if (rToks.length > 1 && SCOPE_RE.test(rLast)) {
16066
+ regionName = rToks.slice(0, -1).join(" ");
16067
+ regionScope = rLast;
16068
+ }
15974
16069
  const region = {
15975
- name: split.name,
16070
+ name: regionName,
15976
16071
  tags,
15977
16072
  meta,
15978
16073
  lineNumber: line12
15979
16074
  };
16075
+ if (regionScope !== void 0) region.scope = regionScope;
15980
16076
  if (scoreNum !== void 0) region.score = scoreNum;
15981
16077
  regions.push(region);
15982
16078
  }
@@ -17100,7 +17196,7 @@ function parseMindmap(content, palette) {
17100
17196
  if (descResult.needsColon) {
17101
17197
  pushWarning(
17102
17198
  lineNumber,
17103
- `Use "description: ${descResult.text}" \u2014 colon is required.`
17199
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`
17104
17200
  );
17105
17201
  }
17106
17202
  const parent = findMetadataParent2(indent, indentStack);
@@ -18888,7 +18984,7 @@ function parseJourneyMap(content, palette) {
18888
18984
  if (descResult.needsColon) {
18889
18985
  warn(
18890
18986
  lineNumber,
18891
- `Use "description: ${descResult.text}" \u2014 colon is required.`
18987
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`
18892
18988
  );
18893
18989
  }
18894
18990
  currentStep.description = descResult.text;
@@ -45790,7 +45886,9 @@ function resolveMap(parsed, data) {
45790
45886
  const usScoped = parsed.directives.region === "us-states" || parsed.directives.defaultCountry?.toUpperCase() === "US" || parsed.regions.some((r) => {
45791
45887
  const f = fold(r.name);
45792
45888
  return usStateIndex.has(f) && !countryIndex.has(f);
45793
- }) || parsed.pois.some(
45889
+ }) || parsed.regions.some(
45890
+ (r) => r.scope === "US" || r.scope?.startsWith("US-")
45891
+ ) || parsed.pois.some(
45794
45892
  (p) => p.pos.kind === "name" && p.pos.scope?.startsWith("US-")
45795
45893
  );
45796
45894
  const regions = [];
@@ -45802,7 +45900,30 @@ function resolveMap(parsed, data) {
45802
45900
  const inCountry = countryIndex.get(f) ?? (REGION_ALIASES[f] ? countryIndex.get(REGION_ALIASES[f]) : void 0);
45803
45901
  const inState = usStateIndex.get(f);
45804
45902
  let chosen = null;
45805
- if (inCountry && inState) {
45903
+ const scope = r.scope;
45904
+ if (scope) {
45905
+ const wantsState = scope === "US" || scope.startsWith("US-");
45906
+ if (wantsState && inState) {
45907
+ if (scope.startsWith("US-") && inState.id !== scope) {
45908
+ err(
45909
+ r.lineNumber,
45910
+ `No subdivision "${r.name}" in scope ${scope} (it is ${inState.id}).`,
45911
+ "E_MAP_SCOPE_MISS"
45912
+ );
45913
+ continue;
45914
+ }
45915
+ chosen = { ...inState, layer: "us-state" };
45916
+ } else if (!wantsState && inCountry) {
45917
+ chosen = { ...inCountry, layer: "country" };
45918
+ } else {
45919
+ err(
45920
+ r.lineNumber,
45921
+ `No region "${r.name}" found in scope ${scope}.`,
45922
+ "E_MAP_SCOPE_MISS"
45923
+ );
45924
+ continue;
45925
+ }
45926
+ } else if (inCountry && inState) {
45806
45927
  if (usScoped) {
45807
45928
  chosen = { ...inState, layer: "us-state" };
45808
45929
  } else {
@@ -45810,7 +45931,7 @@ function resolveMap(parsed, data) {
45810
45931
  }
45811
45932
  warn(
45812
45933
  r.lineNumber,
45813
- `"${r.name}" is both a country and a US state \u2014 resolved as ${chosen.layer} (${chosen.id}).`,
45934
+ `"${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}").`,
45814
45935
  "W_MAP_REGION_AMBIGUOUS"
45815
45936
  );
45816
45937
  } else if (inState) {
@@ -45982,9 +46103,17 @@ function resolveMap(parsed, data) {
45982
46103
  };
45983
46104
  registerPoi(id, poi, p.lineNumber);
45984
46105
  }
46106
+ const declaredByName = /* @__PURE__ */ new Map();
46107
+ for (const p of pois) {
46108
+ const fn = p.name ? fold(p.name) : void 0;
46109
+ if (fn && fn !== p.id && !declaredByName.has(fn))
46110
+ declaredByName.set(fn, p.id);
46111
+ }
45985
46112
  const resolveEndpoint2 = (ref, line12) => {
45986
46113
  const f = fold(ref);
45987
46114
  if (registry.has(f)) return f;
46115
+ const aliased = declaredByName.get(f);
46116
+ if (aliased) return aliased;
45988
46117
  const got = lookupName(ref, void 0, line12, inferredCountry, true);
45989
46118
  if (got.kind !== "ok") return null;
45990
46119
  noteCountry(got.iso);
@@ -46064,23 +46193,29 @@ function resolveMap(parsed, data) {
46064
46193
  [-180, -85],
46065
46194
  [180, 85]
46066
46195
  ];
46067
- const extent2 = unioned ? pad(unioned, PAD_FRACTION) : DEFAULT_EXTENT;
46196
+ let extent2 = unioned ? pad(unioned, PAD_FRACTION) : DEFAULT_EXTENT;
46068
46197
  const lonSpan = extent2[1][0] - extent2[0][0];
46069
46198
  const latSpan = extent2[1][1] - extent2[0][1];
46070
46199
  const span = Math.max(lonSpan, latSpan);
46071
46200
  const usDominant = (inferredCountry === "US" || subdivisions.includes("us-states")) && !regions.some((r) => r.layer === "country" && r.iso !== "US") && !anyNonUsPoi;
46072
46201
  let projection;
46073
46202
  const override = parsed.directives.projection;
46074
- if (override === "natural-earth" || override === "albers-usa" || override === "mercator") {
46203
+ if (override === "equirectangular" || override === "natural-earth" || override === "albers-usa" || override === "mercator") {
46075
46204
  projection = override;
46076
46205
  } else if (usDominant) {
46077
46206
  projection = "albers-usa";
46078
46207
  } else if (span > WORLD_SPAN) {
46079
- projection = "natural-earth";
46208
+ projection = "equirectangular";
46080
46209
  } else if (span < MERCATOR_MAX_SPAN) {
46081
46210
  projection = "mercator";
46082
46211
  } else {
46083
- projection = "natural-earth";
46212
+ projection = "equirectangular";
46213
+ }
46214
+ if (lonSpan >= 180) {
46215
+ extent2 = [
46216
+ [-180, Math.min(extent2[0][1], WORLD_LAT_SOUTH)],
46217
+ [180, Math.max(extent2[1][1], WORLD_LAT_NORTH)]
46218
+ ];
46084
46219
  }
46085
46220
  result.regions = regions;
46086
46221
  result.pois = pois;
@@ -46128,7 +46263,7 @@ function firstError(diags) {
46128
46263
  const e = diags.find((d) => d.severity === "error");
46129
46264
  return e ? formatDgmoError(e) : null;
46130
46265
  }
46131
- var WORLD_SPAN, MERCATOR_MAX_SPAN, PAD_FRACTION, REGION_ALIASES;
46266
+ var WORLD_SPAN, MERCATOR_MAX_SPAN, PAD_FRACTION, WORLD_LAT_SOUTH, WORLD_LAT_NORTH, REGION_ALIASES;
46132
46267
  var init_resolver2 = __esm({
46133
46268
  "src/map/resolver.ts"() {
46134
46269
  "use strict";
@@ -46137,6 +46272,8 @@ var init_resolver2 = __esm({
46137
46272
  WORLD_SPAN = 90;
46138
46273
  MERCATOR_MAX_SPAN = 25;
46139
46274
  PAD_FRACTION = 0.05;
46275
+ WORLD_LAT_SOUTH = -58;
46276
+ WORLD_LAT_NORTH = 78;
46140
46277
  REGION_ALIASES = {
46141
46278
  // Common everyday names → the Natural-Earth display name actually shipped.
46142
46279
  "united states": "united states of america",
@@ -46166,14 +46303,22 @@ var load_data_exports = {};
46166
46303
  __export(load_data_exports, {
46167
46304
  loadMapData: () => loadMapData
46168
46305
  });
46169
- async function readJson(dir, name) {
46170
- return JSON.parse(await (0, import_promises.readFile)((0, import_node_path.resolve)(dir, name), "utf8"));
46306
+ async function loadNodeBuiltins() {
46307
+ const [{ readFile }, { fileURLToPath }, { dirname: dirname2, resolve }] = await Promise.all([
46308
+ import("fs/promises"),
46309
+ import("url"),
46310
+ import("path")
46311
+ ]);
46312
+ return { readFile, fileURLToPath, dirname: dirname2, resolve };
46171
46313
  }
46172
- async function firstExistingDir(baseDir) {
46314
+ async function readJson(nb, dir, name) {
46315
+ return JSON.parse(await nb.readFile(nb.resolve(dir, name), "utf8"));
46316
+ }
46317
+ async function firstExistingDir(nb, baseDir) {
46173
46318
  for (const rel of CANDIDATE_DIRS) {
46174
- const dir = (0, import_node_path.resolve)(baseDir, rel);
46319
+ const dir = nb.resolve(baseDir, rel);
46175
46320
  try {
46176
- await (0, import_promises.readFile)((0, import_node_path.resolve)(dir, FILES.gazetteer), "utf8");
46321
+ await nb.readFile(nb.resolve(dir, FILES.gazetteer), "utf8");
46177
46322
  return dir;
46178
46323
  } catch {
46179
46324
  }
@@ -46189,10 +46334,10 @@ function validate(data) {
46189
46334
  }
46190
46335
  return data;
46191
46336
  }
46192
- function moduleBaseDir() {
46337
+ function moduleBaseDir(nb) {
46193
46338
  try {
46194
46339
  const url = import_meta.url;
46195
- if (url) return (0, import_node_path.dirname)((0, import_node_url.fileURLToPath)(url));
46340
+ if (url) return nb.dirname(nb.fileURLToPath(url));
46196
46341
  } catch {
46197
46342
  }
46198
46343
  if (typeof __dirname !== "undefined") return __dirname;
@@ -46200,32 +46345,57 @@ function moduleBaseDir() {
46200
46345
  }
46201
46346
  function loadMapData() {
46202
46347
  cache ??= (async () => {
46203
- const dir = await firstExistingDir(moduleBaseDir());
46204
- const [worldCoarse, worldDetail, usStates, gazetteer] = await Promise.all([
46205
- readJson(dir, FILES.worldCoarse),
46206
- readJson(dir, FILES.worldDetail),
46207
- readJson(dir, FILES.usStates),
46208
- readJson(dir, FILES.gazetteer)
46348
+ const nb = await loadNodeBuiltins();
46349
+ const dir = await firstExistingDir(nb, moduleBaseDir(nb));
46350
+ const [
46351
+ worldCoarse,
46352
+ worldDetail,
46353
+ usStates,
46354
+ lakes,
46355
+ rivers,
46356
+ naLand,
46357
+ naLakes,
46358
+ gazetteer
46359
+ ] = await Promise.all([
46360
+ readJson(nb, dir, FILES.worldCoarse),
46361
+ readJson(nb, dir, FILES.worldDetail),
46362
+ readJson(nb, dir, FILES.usStates),
46363
+ // Lakes/rivers/NA assets are optional — older bundles may predate them.
46364
+ readJson(nb, dir, FILES.lakes).catch(() => void 0),
46365
+ readJson(nb, dir, FILES.rivers).catch(() => void 0),
46366
+ readJson(nb, dir, FILES.naLand).catch(() => void 0),
46367
+ readJson(nb, dir, FILES.naLakes).catch(() => void 0),
46368
+ readJson(nb, dir, FILES.gazetteer)
46209
46369
  ]);
46210
- return validate({ worldCoarse, worldDetail, usStates, gazetteer });
46370
+ return validate({
46371
+ worldCoarse,
46372
+ worldDetail,
46373
+ usStates,
46374
+ gazetteer,
46375
+ ...lakes && { lakes },
46376
+ ...rivers && { rivers },
46377
+ ...naLand && { naLand },
46378
+ ...naLakes && { naLakes }
46379
+ });
46211
46380
  })().catch((e) => {
46212
46381
  cache = void 0;
46213
46382
  throw e;
46214
46383
  });
46215
46384
  return cache;
46216
46385
  }
46217
- var import_promises, import_node_url, import_node_path, import_meta, FILES, CANDIDATE_DIRS, cache;
46386
+ var import_meta, FILES, CANDIDATE_DIRS, cache;
46218
46387
  var init_load_data = __esm({
46219
46388
  "src/map/load-data.ts"() {
46220
46389
  "use strict";
46221
- import_promises = require("fs/promises");
46222
- import_node_url = require("url");
46223
- import_node_path = require("path");
46224
46390
  import_meta = {};
46225
46391
  FILES = {
46226
46392
  worldCoarse: "world-coarse.json",
46227
46393
  worldDetail: "world-detail.json",
46228
46394
  usStates: "us-states.json",
46395
+ lakes: "lakes.json",
46396
+ rivers: "rivers.json",
46397
+ naLand: "na-land.json",
46398
+ naLakes: "na-lakes.json",
46229
46399
  gazetteer: "gazetteer.json"
46230
46400
  };
46231
46401
  CANDIDATE_DIRS = [
@@ -46253,36 +46423,74 @@ function decodeLayer(topo) {
46253
46423
  function projectionFor(family) {
46254
46424
  switch (family) {
46255
46425
  case "albers-usa":
46256
- return (0, import_d3_geo2.geoAlbersUsa)();
46426
+ return usConusProjection();
46257
46427
  case "mercator":
46258
46428
  return (0, import_d3_geo2.geoMercator)();
46259
46429
  case "natural-earth":
46260
- default:
46261
46430
  return (0, import_d3_geo2.geoNaturalEarth1)();
46431
+ case "equirectangular":
46432
+ default:
46433
+ return (0, import_d3_geo2.geoEquirectangular)();
46262
46434
  }
46263
46435
  }
46436
+ function mapBackgroundColor(palette) {
46437
+ return mix(palette.colors.blue, palette.bg, WATER_TINT);
46438
+ }
46439
+ function mapNeutralLandColor(palette, isDark) {
46440
+ return mix(
46441
+ palette.colors.green,
46442
+ palette.bg,
46443
+ isDark ? LAND_TINT_DARK : LAND_TINT_LIGHT
46444
+ );
46445
+ }
46264
46446
  function layoutMap(resolved, data, size, opts) {
46265
46447
  const { palette, isDark } = opts;
46266
46448
  const { width, height } = size;
46267
- const worldTopo = resolved.basemaps.world === "detail" ? data.worldDetail : data.worldCoarse;
46449
+ const wantsUsStates = resolved.basemaps.subdivisions.includes("us-states");
46450
+ const usCrisp = resolved.projection === "albers-usa" && wantsUsStates && !!data.naLand;
46451
+ const worldTopo = usCrisp ? data.naLand : resolved.basemaps.world === "detail" ? data.worldDetail : data.worldCoarse;
46268
46452
  const worldLayer = decodeLayer(worldTopo);
46269
- const usLayer = resolved.basemaps.subdivisions.includes("us-states") ? decodeLayer(data.usStates) : null;
46270
- const neutralFill = mix(palette.border, palette.bg, 32);
46271
- const regionStroke = mix(palette.border, palette.bg, 70);
46453
+ const usLayer = wantsUsStates ? decodeLayer(data.usStates) : null;
46454
+ const landTint = isDark ? LAND_TINT_DARK : LAND_TINT_LIGHT;
46455
+ const neutralFill = mix(palette.colors.green, palette.bg, landTint);
46456
+ const water = mapBackgroundColor(palette);
46457
+ const usContext = usLayer !== null;
46458
+ const foreignFill = mix(
46459
+ palette.colors.gray,
46460
+ palette.bg,
46461
+ isDark ? FOREIGN_TINT_DARK : FOREIGN_TINT_LIGHT
46462
+ );
46463
+ const regionStroke = isDark ? mix(palette.bg, palette.text, 78) : mix(palette.text, palette.bg, 78);
46272
46464
  const scores = resolved.regions.filter((r) => r.score !== void 0).map((r) => r.score);
46273
46465
  const scaleOverride = resolved.directives.scale;
46274
46466
  const rampMin = scaleOverride ? scaleOverride.min : Math.min(...scores);
46275
46467
  const rampMax = scaleOverride ? scaleOverride.max : Math.max(...scores);
46276
- const rampHue = palette.primary;
46468
+ const rampHue = palette.colors.red;
46277
46469
  const hasRamp = scores.length > 0;
46278
- const activeGroup = resolveActiveTagGroup(
46279
- resolved.tagGroups,
46280
- resolved.directives.activeTag
46281
- );
46470
+ const SCORE_NAME = hasRamp ? resolved.directives.metric?.trim() || "Score" : null;
46471
+ const matchColorGroup = (v) => {
46472
+ const lv = v.trim().toLowerCase();
46473
+ if (lv === "none") return null;
46474
+ if (SCORE_NAME && (lv === "score" || lv === SCORE_NAME.toLowerCase()))
46475
+ return SCORE_NAME;
46476
+ const tg = resolved.tagGroups.find((g) => g.name.toLowerCase() === lv);
46477
+ return tg ? tg.name : v;
46478
+ };
46479
+ const override = opts.activeGroup;
46480
+ let activeGroup;
46481
+ if (override !== void 0) {
46482
+ activeGroup = override === null ? null : matchColorGroup(override);
46483
+ } else if (resolved.directives.activeTag !== void 0) {
46484
+ activeGroup = matchColorGroup(resolved.directives.activeTag);
46485
+ } else {
46486
+ activeGroup = SCORE_NAME ?? (resolved.tagGroups.length > 0 ? resolved.tagGroups[0].name : null);
46487
+ }
46488
+ const activeIsScore = SCORE_NAME !== null && activeGroup === SCORE_NAME;
46489
+ const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
46282
46490
  const fillForScore = (s) => {
46283
46491
  const t = rampMax > rampMin ? (s - rampMin) / (rampMax - rampMin) : 1;
46284
46492
  const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
46285
- return mix(rampHue, isDark ? palette.surface : palette.bg, pct);
46493
+ return mix(rampHue, rampBase, pct);
46286
46494
  };
46287
46495
  const tagFill = (tags, groupName) => {
46288
46496
  if (!groupName) return null;
@@ -46296,40 +46504,40 @@ function layoutMap(resolved, data, size, opts) {
46296
46504
  (e) => e.value.toLowerCase() === val.toLowerCase()
46297
46505
  );
46298
46506
  if (!entry?.color) return null;
46299
- return shapeFill(palette, entry.color, isDark);
46507
+ return mix(
46508
+ entry.color,
46509
+ palette.bg,
46510
+ isDark ? TAG_TINT_DARK : TAG_TINT_LIGHT
46511
+ );
46512
+ };
46513
+ const regionFill = (r) => {
46514
+ if (activeIsScore) {
46515
+ return r.score !== void 0 ? fillForScore(r.score) : neutralFill;
46516
+ }
46517
+ return tagFill(r.tags, activeGroup) ?? neutralFill;
46300
46518
  };
46301
46519
  const regionById = new Map(resolved.regions.map((r) => [r.iso, r]));
46302
- const regionFeatures = [];
46303
- for (const r of resolved.regions) {
46304
- const f = r.layer === "us-state" ? usLayer?.get(r.iso) : worldLayer.get(r.iso);
46305
- if (f) regionFeatures.push(f);
46306
- }
46307
- const extentCorners = () => {
46520
+ const extentOutline = () => {
46308
46521
  const [[w, s], [e, n]] = resolved.extent;
46522
+ const N = 16;
46523
+ const coords = [];
46524
+ for (let i = 0; i <= N; i++) {
46525
+ const t = i / N;
46526
+ const lon = w + (e - w) * t;
46527
+ const lat = s + (n - s) * t;
46528
+ coords.push([lon, s], [lon, n], [w, lat], [e, lat]);
46529
+ }
46309
46530
  return {
46310
46531
  type: "Feature",
46311
46532
  properties: {},
46312
- geometry: {
46313
- type: "MultiPoint",
46314
- coordinates: [
46315
- [w, s],
46316
- [e, s],
46317
- [e, n],
46318
- [w, n]
46319
- ]
46320
- }
46533
+ geometry: { type: "MultiPoint", coordinates: coords }
46321
46534
  };
46322
46535
  };
46323
46536
  let fitFeatures;
46324
- if (resolved.projection === "albers-usa") {
46325
- if (regionFeatures.length > 0) fitFeatures = regionFeatures;
46326
- else if (usLayer) fitFeatures = [...usLayer.values()];
46327
- else {
46328
- const us = worldLayer.get("US");
46329
- fitFeatures = us ? [us] : [...worldLayer.values()];
46330
- }
46537
+ if (resolved.projection === "albers-usa" && usLayer) {
46538
+ fitFeatures = [...usLayer.entries()].filter(([iso]) => !US_NON_CONUS.has(iso)).map(([, f]) => f);
46331
46539
  } else {
46332
- fitFeatures = [extentCorners()];
46540
+ fitFeatures = [extentOutline()];
46333
46541
  }
46334
46542
  const fitTarget = { type: "FeatureCollection", features: fitFeatures };
46335
46543
  const projection = projectionFor(resolved.projection);
@@ -46338,32 +46546,311 @@ function layoutMap(resolved, data, size, opts) {
46338
46546
  if (centerLon > 180) centerLon -= 360;
46339
46547
  projection.rotate([-centerLon, 0]);
46340
46548
  }
46341
- projection.fitExtent(
46549
+ const TITLE_GAP = 16;
46550
+ let topPad = FIT_PAD;
46551
+ if (resolved.title && resolved.pois.length > 0) {
46552
+ const bannerBottom = (resolved.subtitle ? TITLE_Y + TITLE_FONT_SIZE : TITLE_Y) + TITLE_FONT_SIZE / 2;
46553
+ topPad = Math.max(FIT_PAD, bannerBottom + TITLE_GAP);
46554
+ }
46555
+ const fitBox = [
46556
+ [FIT_PAD, topPad],
46342
46557
  [
46343
- [FIT_PAD, FIT_PAD],
46344
- [
46345
- Math.max(FIT_PAD + 1, width - FIT_PAD),
46346
- Math.max(FIT_PAD + 1, height - FIT_PAD)
46347
- ]
46348
- ],
46349
- fitTarget
46350
- );
46351
- const path = (0, import_d3_geo2.geoPath)(projection);
46352
- const project = (lon, lat) => projection([lon, lat]) ?? null;
46558
+ Math.max(FIT_PAD + 1, width - FIT_PAD),
46559
+ Math.max(topPad + 1, height - FIT_PAD)
46560
+ ]
46561
+ ];
46562
+ projection.fitExtent(fitBox, fitTarget);
46563
+ const fitGB = (0, import_d3_geo2.geoBounds)(fitTarget);
46564
+ const fitIsGlobal = fitGB[1][0] - fitGB[0][0] >= 270 || fitGB[1][1] - fitGB[0][1] >= 130;
46565
+ let path;
46566
+ let project;
46567
+ if (fitIsGlobal) {
46568
+ const cb = (0, import_d3_geo2.geoPath)(projection).bounds(fitTarget);
46569
+ const bx0 = cb[0][0];
46570
+ const by0 = cb[0][1];
46571
+ const cw = cb[1][0] - bx0;
46572
+ const ch = cb[1][1] - by0;
46573
+ const ox = fitBox[0][0];
46574
+ const oy = fitBox[0][1];
46575
+ const sx = cw > 0 ? (fitBox[1][0] - ox) / cw : 1;
46576
+ const sy = ch > 0 ? (fitBox[1][1] - oy) / ch : 1;
46577
+ const stretch = (x, y) => [
46578
+ ox + (x - bx0) * sx,
46579
+ oy + (y - by0) * sy
46580
+ ];
46581
+ const baseProjection = projection;
46582
+ const tx = (0, import_d3_geo2.geoTransform)({
46583
+ point(x, y) {
46584
+ const [px, py] = stretch(x, y);
46585
+ this.stream.point(px, py);
46586
+ }
46587
+ });
46588
+ path = (0, import_d3_geo2.geoPath)({
46589
+ stream: (s) => baseProjection.stream(
46590
+ tx.stream(s)
46591
+ )
46592
+ });
46593
+ project = (lon, lat) => {
46594
+ const p = baseProjection([lon, lat]);
46595
+ return p ? stretch(p[0], p[1]) : null;
46596
+ };
46597
+ } else {
46598
+ path = (0, import_d3_geo2.geoPath)(projection);
46599
+ project = (lon, lat) => projection([lon, lat]) ?? null;
46600
+ }
46601
+ const insets = [];
46602
+ const insetRegions = [];
46603
+ const insetLabelSeeds = [];
46604
+ if (resolved.projection === "albers-usa" && usLayer) {
46605
+ const PAD = 8;
46606
+ const GAP = 12;
46607
+ const yB = height - FIT_PAD;
46608
+ const BW = 8;
46609
+ const coast = /* @__PURE__ */ new Map();
46610
+ const addPt = (lon, lat) => {
46611
+ const p = projection([lon, lat]);
46612
+ if (!p) return;
46613
+ const bi = Math.floor(p[0] / BW);
46614
+ const cur = coast.get(bi);
46615
+ if (cur === void 0 || p[1] > cur) coast.set(bi, p[1]);
46616
+ };
46617
+ const walk = (co) => {
46618
+ if (Array.isArray(co) && typeof co[0] === "number")
46619
+ addPt(co[0], co[1]);
46620
+ else if (Array.isArray(co)) for (const c of co) walk(c);
46621
+ };
46622
+ for (const [iso, f] of usLayer) {
46623
+ if (US_NON_CONUS.has(iso)) continue;
46624
+ walk(f.geometry.coordinates);
46625
+ }
46626
+ const at = (x) => {
46627
+ const bi = Math.floor(x / BW);
46628
+ let y = -Infinity;
46629
+ for (let k = bi - 1; k <= bi + 1; k++) {
46630
+ const v = coast.get(k);
46631
+ if (v !== void 0 && v > y) y = v;
46632
+ }
46633
+ return y;
46634
+ };
46635
+ const coastTop = (x0, xr) => {
46636
+ const n = 24;
46637
+ const pts = [];
46638
+ let maxY = -Infinity;
46639
+ for (let i = 0; i <= n; i++) {
46640
+ const x = x0 + (xr - x0) * i / n;
46641
+ const y = at(x);
46642
+ if (y > -Infinity) {
46643
+ pts.push([x, y]);
46644
+ if (y > maxY) maxY = y;
46645
+ }
46646
+ }
46647
+ if (pts.length === 0) return () => yB - height * 0.42;
46648
+ let m = 0;
46649
+ if (pts.length >= 2) {
46650
+ let sx = 0, sy = 0, sxx = 0, sxy = 0;
46651
+ for (const [x, y] of pts) {
46652
+ sx += x;
46653
+ sy += y;
46654
+ sxx += x * x;
46655
+ sxy += x * y;
46656
+ }
46657
+ const den = pts.length * sxx - sx * sx;
46658
+ if (den !== 0) m = (pts.length * sxy - sx * sy) / den;
46659
+ }
46660
+ m = Math.max(-0.35, Math.min(0.35, m));
46661
+ let c = -Infinity;
46662
+ for (const [x, y] of pts) {
46663
+ const need = y - m * x + GAP;
46664
+ if (need > c) c = need;
46665
+ }
46666
+ return (x) => m * x + c;
46667
+ };
46668
+ const placeInset = (iso, proj, boxX, iwReq) => {
46669
+ const f = usLayer.get(iso);
46670
+ if (!f) return boxX;
46671
+ const x0 = boxX;
46672
+ const iw = Math.min(iwReq, width - FIT_PAD - x0 - 2 * PAD);
46673
+ if (iw < 24) return boxX;
46674
+ const xr = x0 + iw + 2 * PAD;
46675
+ const top = coastTop(x0, xr);
46676
+ const yL = top(x0);
46677
+ const yR = top(xr);
46678
+ proj.fitWidth(iw, f);
46679
+ const bb = (0, import_d3_geo2.geoPath)(proj).bounds(f);
46680
+ const sh = Number.isFinite(bb[0][0]) ? bb[1][1] - bb[0][1] : iw;
46681
+ const needH = sh + 2 * PAD;
46682
+ let topFit = Math.max(yL, yR);
46683
+ const bottom = Math.min(topFit + needH, yB);
46684
+ if (bottom - topFit < needH) topFit = bottom - needH;
46685
+ const lift = topFit - Math.max(yL, yR);
46686
+ const topL = yL + lift;
46687
+ const topR = yR + lift;
46688
+ proj.fitExtent(
46689
+ [
46690
+ [x0 + PAD, topFit + PAD],
46691
+ [xr - PAD, bottom - PAD]
46692
+ ],
46693
+ f
46694
+ );
46695
+ const d = (0, import_d3_geo2.geoPath)(proj)(f) ?? "";
46696
+ if (!d) return xr;
46697
+ const r = regionById.get(iso);
46698
+ let fill2 = neutralFill;
46699
+ let lineNumber = -1;
46700
+ if (r?.layer === "us-state") {
46701
+ fill2 = regionFill(r);
46702
+ lineNumber = r.lineNumber;
46703
+ }
46704
+ insets.push({
46705
+ x: x0,
46706
+ y: Math.min(topL, topR),
46707
+ w: xr - x0,
46708
+ h: bottom - Math.min(topL, topR),
46709
+ points: [
46710
+ [x0, topL],
46711
+ [xr, topR],
46712
+ [xr, bottom],
46713
+ [x0, bottom]
46714
+ ]
46715
+ });
46716
+ insetRegions.push({
46717
+ id: iso,
46718
+ d,
46719
+ fill: fill2,
46720
+ stroke: regionStroke,
46721
+ lineNumber,
46722
+ layer: "us-state",
46723
+ ...r?.score !== void 0 && { score: r.score },
46724
+ ...r && Object.keys(r.tags).length > 0 && { tags: r.tags }
46725
+ });
46726
+ const ctr = (0, import_d3_geo2.geoPath)(proj).centroid(f);
46727
+ if (Number.isFinite(ctr[0])) {
46728
+ const name = f.properties?.name ?? iso;
46729
+ insetLabelSeeds.push({ x: ctr[0], y: ctr[1], iso, name, lineNumber });
46730
+ }
46731
+ return xr;
46732
+ };
46733
+ const akRight = placeInset(
46734
+ "US-AK",
46735
+ alaskaProjection(),
46736
+ FIT_PAD,
46737
+ width * 0.15
46738
+ );
46739
+ placeInset("US-HI", hawaiiProjection(), akRight + 24, width * 0.1);
46740
+ }
46741
+ const conusFit = resolved.projection === "albers-usa" && !!usLayer;
46742
+ const cullExtent = conusFit ? (0, import_d3_geo2.geoBounds)(fitTarget) : resolved.extent;
46743
+ const [[exW, exS], [exE, exN]] = cullExtent;
46744
+ const lonSpan = exE - exW;
46745
+ const latSpan = exN - exS;
46746
+ const isGlobalView = lonSpan >= 270 || latSpan >= 130;
46747
+ const padLon = Math.max(8, lonSpan * 0.35);
46748
+ const padLat = Math.max(8, latSpan * 0.35);
46749
+ const vW = exW - padLon;
46750
+ const vE = exE + padLon;
46751
+ const vS = exS - padLat;
46752
+ const vN = exN + padLat;
46753
+ const vLonCenter = (exW + exE) / 2;
46754
+ const normLon = (lon) => {
46755
+ let L = lon;
46756
+ while (L < vLonCenter - 180) L += 360;
46757
+ while (L > vLonCenter + 180) L -= 360;
46758
+ return L;
46759
+ };
46760
+ const ringOverlapsView = (ring) => {
46761
+ let anyIn = false;
46762
+ let loMin = Infinity, loMax = -Infinity, laMin = Infinity, laMax = -Infinity, rawMin = Infinity, rawMax = -Infinity;
46763
+ for (const [rawLon, lat] of ring) {
46764
+ const lon = normLon(rawLon);
46765
+ if (lon >= vW && lon <= vE && lat >= vS && lat <= vN) anyIn = true;
46766
+ if (lon < loMin) loMin = lon;
46767
+ if (lon > loMax) loMax = lon;
46768
+ if (rawLon < rawMin) rawMin = rawLon;
46769
+ if (rawLon > rawMax) rawMax = rawLon;
46770
+ if (lat < laMin) laMin = lat;
46771
+ if (lat > laMax) laMax = lat;
46772
+ }
46773
+ if (loMax - loMin > 270) return false;
46774
+ if (rawMax - rawMin > 180 && loMax - loMin < 90) return false;
46775
+ if (anyIn) return true;
46776
+ if (loMax - loMin > 180) return false;
46777
+ return !(loMax < vW || loMin > vE || laMax < vS || laMin > vN);
46778
+ };
46779
+ const cullFeatureToView = (f) => {
46780
+ if (isGlobalView) return f;
46781
+ const g = f.geometry;
46782
+ if (!g) return f;
46783
+ if (g.type === "Polygon") {
46784
+ const ring = g.coordinates[0];
46785
+ return ringOverlapsView(ring) ? f : null;
46786
+ }
46787
+ if (g.type === "MultiPolygon") {
46788
+ const polys = g.coordinates;
46789
+ const keep = polys.filter(
46790
+ (p) => ringOverlapsView(p[0])
46791
+ );
46792
+ if (!keep.length) return null;
46793
+ if (keep.length === polys.length) return f;
46794
+ return { ...f, geometry: { ...g, coordinates: keep } };
46795
+ }
46796
+ return f;
46797
+ };
46798
+ const SEAM_SLIVER_MAX_SPAN = 100;
46799
+ const ringIsFrameFiller = (ring) => {
46800
+ const lons = ring.map(([lon]) => lon).sort((a, b) => a - b);
46801
+ if (lons.length < 2) return false;
46802
+ let maxGap = -1;
46803
+ let gapIdx = 0;
46804
+ for (let i = 1; i < lons.length; i++) {
46805
+ const g = lons[i] - lons[i - 1];
46806
+ if (g > maxGap) {
46807
+ maxGap = g;
46808
+ gapIdx = i;
46809
+ }
46810
+ }
46811
+ const wrapGap = lons[0] + 360 - lons[lons.length - 1];
46812
+ if (wrapGap >= maxGap) return false;
46813
+ const span = 360 - maxGap;
46814
+ const east = lons[gapIdx - 1] + 360;
46815
+ return east > 180 && span < SEAM_SLIVER_MAX_SPAN;
46816
+ };
46817
+ const dropFrameFillers = (f) => {
46818
+ const g = f.geometry;
46819
+ if (!g) return f;
46820
+ if (g.type === "Polygon") {
46821
+ const ring = g.coordinates[0];
46822
+ return ringIsFrameFiller(ring) ? null : f;
46823
+ }
46824
+ if (g.type === "MultiPolygon") {
46825
+ const polys = g.coordinates;
46826
+ const keep = polys.filter(
46827
+ (p) => !ringIsFrameFiller(p[0])
46828
+ );
46829
+ if (!keep.length) return null;
46830
+ if (keep.length === polys.length) return f;
46831
+ return { ...f, geometry: { ...g, coordinates: keep } };
46832
+ }
46833
+ return f;
46834
+ };
46353
46835
  const regions = [];
46354
- const pushRegionLayer = (layerFeatures, layerKind) => {
46836
+ const pushRegionLayer = (layerFeatures, layerKind, shouldCull) => {
46355
46837
  for (const [iso, f] of layerFeatures) {
46356
- const d = path(f) ?? "";
46357
- if (!d) continue;
46838
+ if (layerKind === "us-state" && usContext && INSET_STATES.has(iso))
46839
+ continue;
46840
+ if (layerKind === "country" && usContext && iso === "US") continue;
46358
46841
  const r = regionById.get(iso);
46842
+ const viewF = shouldCull ? cullFeatureToView(f) : dropFrameFillers(f);
46843
+ if (!viewF) continue;
46844
+ const d = path(viewF) ?? "";
46845
+ if (!d) continue;
46359
46846
  const isThisLayer = r?.layer === layerKind;
46360
- let fill2 = neutralFill;
46847
+ const isForeign = layerKind === "country" && usContext && iso !== "US";
46848
+ let fill2 = isForeign ? foreignFill : neutralFill;
46361
46849
  let label;
46362
46850
  let lineNumber = -1;
46363
46851
  let layer = "base";
46364
46852
  if (isThisLayer) {
46365
- if (r.score !== void 0) fill2 = fillForScore(r.score);
46366
- else fill2 = tagFill(r.tags, activeGroup) ?? neutralFill;
46853
+ fill2 = regionFill(r);
46367
46854
  lineNumber = r.lineNumber;
46368
46855
  layer = layerKind;
46369
46856
  label = r.name;
@@ -46375,12 +46862,42 @@ function layoutMap(resolved, data, size, opts) {
46375
46862
  stroke: regionStroke,
46376
46863
  lineNumber,
46377
46864
  layer,
46378
- ...label !== void 0 && { label }
46865
+ ...label !== void 0 && { label },
46866
+ ...isThisLayer && r.score !== void 0 && { score: r.score },
46867
+ ...isThisLayer && Object.keys(r.tags).length > 0 && { tags: r.tags }
46379
46868
  });
46380
46869
  }
46381
46870
  };
46382
- pushRegionLayer(worldLayer, "country");
46383
- if (usLayer) pushRegionLayer(usLayer, "us-state");
46871
+ pushRegionLayer(worldLayer, "country", !isGlobalView);
46872
+ if (usLayer) pushRegionLayer(usLayer, "us-state", !conusFit && !isGlobalView);
46873
+ const lakesTopo = usCrisp && data.naLakes ? data.naLakes : data.lakes;
46874
+ if (lakesTopo) {
46875
+ for (const [, f] of decodeLayer(lakesTopo)) {
46876
+ const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
46877
+ if (!viewF) continue;
46878
+ const d = path(viewF) ?? "";
46879
+ if (!d) continue;
46880
+ regions.push({
46881
+ id: "lake",
46882
+ d,
46883
+ fill: water,
46884
+ stroke: "none",
46885
+ lineNumber: -1,
46886
+ layer: "base"
46887
+ });
46888
+ }
46889
+ }
46890
+ const riverColor = water;
46891
+ const rivers = [];
46892
+ if (data.rivers) {
46893
+ for (const [, f] of decodeLayer(data.rivers)) {
46894
+ const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
46895
+ if (!viewF) continue;
46896
+ const d = path(viewF) ?? "";
46897
+ if (!d) continue;
46898
+ rivers.push({ d, color: riverColor, width: RIVER_WIDTH });
46899
+ }
46900
+ }
46384
46901
  const sizeVals = resolved.pois.map((p) => Number(p.meta["size"])).filter((n) => Number.isFinite(n) && n > 0);
46385
46902
  const sizeMin = sizeVals.length ? Math.min(...sizeVals) : 0;
46386
46903
  const sizeMax = sizeVals.length ? Math.max(...sizeVals) : 0;
@@ -46401,8 +46918,8 @@ function layoutMap(resolved, data, size, opts) {
46401
46918
  if (hex) return { fill: hex, stroke: mix(hex, palette.text, 18) };
46402
46919
  }
46403
46920
  return {
46404
- fill: palette.accent,
46405
- stroke: mix(palette.accent, palette.text, 18)
46921
+ fill: palette.colors.orange,
46922
+ stroke: mix(palette.colors.orange, palette.text, 18)
46406
46923
  };
46407
46924
  };
46408
46925
  const routeNumberById = /* @__PURE__ */ new Map();
@@ -46440,7 +46957,7 @@ function layoutMap(resolved, data, size, opts) {
46440
46957
  cy += Math.sin(ang) * COLO_R;
46441
46958
  }
46442
46959
  const { fill: fill2, stroke: stroke2 } = poiFill(e.p);
46443
- poiScreen.set(e.p.id, { cx, cy });
46960
+ poiScreen.set(e.p.id, { cx, cy, r: radiusFor(e.p) });
46444
46961
  const num = routeNumberById.get(e.p.id);
46445
46962
  pois.push({
46446
46963
  id: e.p.id,
@@ -46457,17 +46974,36 @@ function layoutMap(resolved, data, size, opts) {
46457
46974
  });
46458
46975
  }
46459
46976
  const legs = [];
46977
+ const RIM_GAP = 1.5;
46460
46978
  const legPath = (a, b, curved, offset) => {
46461
- if (!curved && offset === 0) return `M${a.cx},${a.cy}L${b.cx},${b.cy}`;
46462
46979
  const mx = (a.cx + b.cx) / 2;
46463
46980
  const my = (a.cy + b.cy) / 2;
46464
46981
  const dx = b.cx - a.cx;
46465
46982
  const dy = b.cy - a.cy;
46466
46983
  const len = Math.hypot(dx, dy) || 1;
46984
+ const trimA = Math.min(a.r + RIM_GAP, len * 0.45);
46985
+ const trimB = Math.min(b.r + RIM_GAP, len * 0.45);
46986
+ if (!curved && offset === 0) {
46987
+ const ux = dx / len;
46988
+ const uy = dy / len;
46989
+ const ax2 = a.cx + ux * trimA;
46990
+ const ay2 = a.cy + uy * trimA;
46991
+ const bx2 = b.cx - ux * trimB;
46992
+ const by2 = b.cy - uy * trimB;
46993
+ return `M${ax2},${ay2}L${bx2},${by2}`;
46994
+ }
46467
46995
  const nx = -dy / len;
46468
46996
  const ny = dx / len;
46469
46997
  const bow = offset !== 0 ? offset : len * ARC_CURVE_FRAC;
46470
- return `M${a.cx},${a.cy}Q${mx + nx * bow},${my + ny * bow} ${b.cx},${b.cy}`;
46998
+ const px = mx + nx * bow;
46999
+ const py = my + ny * bow;
47000
+ const ta = Math.hypot(px - a.cx, py - a.cy) || 1;
47001
+ const tb = Math.hypot(b.cx - px, b.cy - py) || 1;
47002
+ const ax = a.cx + (px - a.cx) / ta * trimA;
47003
+ const ay = a.cy + (py - a.cy) / ta * trimA;
47004
+ const bx = b.cx - (b.cx - px) / tb * trimB;
47005
+ const by = b.cy - (b.cy - py) / tb * trimB;
47006
+ return `M${ax},${ay}Q${px},${py} ${bx},${by}`;
46471
47007
  };
46472
47008
  for (const rt of resolved.routes) {
46473
47009
  const curved = rt.meta["style"] === "arc";
@@ -46478,7 +47014,7 @@ function layoutMap(resolved, data, size, opts) {
46478
47014
  legs.push({
46479
47015
  d: legPath(a, b, curved, 0),
46480
47016
  width: W_MIN,
46481
- color: mix(palette.text, palette.bg, 55),
47017
+ color: mix(palette.text, palette.bg, 72),
46482
47018
  arrow: true,
46483
47019
  lineNumber: rt.lineNumber
46484
47020
  });
@@ -46514,7 +47050,7 @@ function layoutMap(resolved, data, size, opts) {
46514
47050
  legs.push({
46515
47051
  d: legPath(a, b, curved, offset),
46516
47052
  width: widthFor(e),
46517
- color: mix(palette.text, palette.bg, 45),
47053
+ color: mix(palette.text, palette.bg, 66),
46518
47054
  arrow: e.directed,
46519
47055
  lineNumber: e.lineNumber,
46520
47056
  ...e.label !== void 0 && {
@@ -46526,38 +47062,92 @@ function layoutMap(resolved, data, size, opts) {
46526
47062
  });
46527
47063
  }
46528
47064
  const labels = [];
46529
- const pinList = [];
46530
47065
  const obstacles = [];
46531
47066
  const markers = pois.map((p) => ({
46532
47067
  cx: p.cx,
46533
47068
  cy: p.cy,
46534
47069
  r: p.r
46535
47070
  }));
46536
- const collides = (rect) => markers.some((m) => rectCircleOverlap(rect, m)) || obstacles.some((o) => rectsOverlap(rect, o));
47071
+ const legSegments = [];
47072
+ for (const leg of legs) {
47073
+ const m = /^M(-?[\d.]+),(-?[\d.]+)(?:L(-?[\d.]+),(-?[\d.]+)|Q(-?[\d.]+),(-?[\d.]+) (-?[\d.]+),(-?[\d.]+))$/.exec(
47074
+ leg.d
47075
+ );
47076
+ if (!m) continue;
47077
+ const x0 = +m[1];
47078
+ const y0 = +m[2];
47079
+ if (m[3] !== void 0) {
47080
+ legSegments.push([x0, y0, +m[3], +m[4]]);
47081
+ } else {
47082
+ const cx = +m[5];
47083
+ const cy = +m[6];
47084
+ const ex = +m[7];
47085
+ const ey = +m[8];
47086
+ const N = 8;
47087
+ let px = x0;
47088
+ let py = y0;
47089
+ for (let i = 1; i <= N; i++) {
47090
+ const t = i / N;
47091
+ const u = 1 - t;
47092
+ const qx = u * u * x0 + 2 * u * t * cx + t * t * ex;
47093
+ const qy = u * u * y0 + 2 * u * t * cy + t * t * ey;
47094
+ legSegments.push([px, py, qx, qy]);
47095
+ px = qx;
47096
+ py = qy;
47097
+ }
47098
+ }
47099
+ }
47100
+ 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));
46537
47101
  const regionLabelMode = resolved.directives.regionLabels ?? "off";
47102
+ const LABEL_PADX = 6;
47103
+ const LABEL_PADY = 3;
47104
+ const labelW = (text) => measureLegendText(text, FONT) + 2 * LABEL_PADX;
47105
+ const labelH = FONT + 2 * LABEL_PADY;
47106
+ const pushRegionLabel = (x, y, text, fill2, lineNumber) => {
47107
+ const color = contrastText(
47108
+ fill2,
47109
+ palette.textOnFillLight,
47110
+ palette.textOnFillDark
47111
+ );
47112
+ const haloColor = color === palette.textOnFillLight ? palette.textOnFillDark : palette.textOnFillLight;
47113
+ labels.push({
47114
+ x,
47115
+ y,
47116
+ text,
47117
+ anchor: "middle",
47118
+ color,
47119
+ halo: true,
47120
+ haloColor,
47121
+ lineNumber
47122
+ });
47123
+ };
47124
+ const WORLD_LABEL_ANCHORS = {
47125
+ US: [-98.5, 39.5]
47126
+ // CONUS geographic centre (near Lebanon, Kansas)
47127
+ };
46538
47128
  if (regionLabelMode === "full" || regionLabelMode === "abbrev") {
46539
47129
  for (const r of regions) {
46540
47130
  if (r.layer === "base" || r.label === void 0) continue;
46541
47131
  const f = r.layer === "us-state" ? usLayer?.get(r.id) : worldLayer.get(r.id);
46542
47132
  if (!f) continue;
46543
47133
  const [[x0, y0], [x1, y1]] = path.bounds(f);
46544
- if ((x1 - x0) * (y1 - y0) < TINY_REGION_AREA) continue;
46545
- const c = path.centroid(f);
46546
- if (!Number.isFinite(c[0])) continue;
46547
47134
  const text = regionLabelMode === "abbrev" ? r.id.replace(/^US-/, "") : r.label;
46548
- labels.push({
46549
- x: c[0],
46550
- y: c[1],
47135
+ if (labelW(text) > x1 - x0 || labelH > y1 - y0) continue;
47136
+ const anchor = r.layer !== "us-state" ? WORLD_LABEL_ANCHORS[r.id] : void 0;
47137
+ const c = anchor ? project(anchor[0], anchor[1]) : path.centroid(f);
47138
+ if (!c || !Number.isFinite(c[0])) continue;
47139
+ pushRegionLabel(c[0], c[1], text, r.fill, r.lineNumber);
47140
+ }
47141
+ for (const seed of insetLabelSeeds) {
47142
+ const text = regionLabelMode === "abbrev" ? seed.iso.replace(/^US-/, "") : seed.name;
47143
+ const src = regionById.get(seed.iso);
47144
+ pushRegionLabel(
47145
+ seed.x,
47146
+ seed.y,
46551
47147
  text,
46552
- anchor: "middle",
46553
- color: contrastText(
46554
- r.fill,
46555
- palette.textOnFillLight,
46556
- palette.textOnFillDark
46557
- ),
46558
- halo: true,
46559
- lineNumber: r.lineNumber
46560
- });
47148
+ src ? regionFill(src) : neutralFill,
47149
+ seed.lineNumber
47150
+ );
46561
47151
  }
46562
47152
  }
46563
47153
  const poiLabelMode = resolved.directives.poiLabels ?? "auto";
@@ -46570,68 +47160,106 @@ function layoutMap(resolved, data, size, opts) {
46570
47160
  const src = poiById.get(p.id);
46571
47161
  return src?.label ?? src?.name ?? p.id;
46572
47162
  };
46573
- let pinCounter = 0;
46574
- for (const p of ordered) {
47163
+ const poiLabH = FONT * 1.25;
47164
+ const labelInfo = (p) => {
46575
47165
  const text = labelText(p);
46576
- const w = measureLegendText(text, FONT);
46577
- const h = FONT * 1.25;
46578
- const inline = { x: p.cx + p.r + 3, y: p.cy - h / 2, w, h };
46579
- if (!collides(inline)) {
46580
- obstacles.push(inline);
47166
+ return { text, w: measureLegendText(text, FONT) };
47167
+ };
47168
+ const pushInline = (p, text, w, side) => {
47169
+ const tx = side === "right" ? p.cx + p.r + 3 : p.cx - p.r - 3;
47170
+ obstacles.push({
47171
+ x: side === "right" ? tx : tx - w,
47172
+ y: p.cy - poiLabH / 2,
47173
+ w,
47174
+ h: poiLabH
47175
+ });
47176
+ labels.push({
47177
+ x: tx,
47178
+ y: p.cy + FONT / 3,
47179
+ text,
47180
+ anchor: side === "right" ? "start" : "end",
47181
+ color: palette.text,
47182
+ halo: true,
47183
+ haloColor: palette.bg,
47184
+ poiId: p.id,
47185
+ lineNumber: p.lineNumber
47186
+ });
47187
+ };
47188
+ const inlineFits = (p, w, side) => {
47189
+ const tx = side === "right" ? p.cx + p.r + 3 : p.cx - p.r - 3;
47190
+ const rect = {
47191
+ x: side === "right" ? tx : tx - w,
47192
+ y: p.cy - poiLabH / 2,
47193
+ w,
47194
+ h: poiLabH
47195
+ };
47196
+ return rect.x >= 0 && rect.x + rect.w <= width && !collides(rect);
47197
+ };
47198
+ const GROUP_R = 30;
47199
+ const groups = [];
47200
+ for (const p of ordered) {
47201
+ const near = groups.find(
47202
+ (g) => g.some((q) => Math.hypot(q.cx - p.cx, q.cy - p.cy) < GROUP_R)
47203
+ );
47204
+ if (near) near.push(p);
47205
+ else groups.push([p]);
47206
+ }
47207
+ const ROW_GAP2 = 3;
47208
+ const step = poiLabH + ROW_GAP2;
47209
+ const COL_GAP = 16;
47210
+ const placeColumn = (group) => {
47211
+ const items = group.map((p) => ({ p, ...labelInfo(p) })).sort((a, b) => a.p.cy - b.p.cy || (a.text < b.text ? -1 : 1));
47212
+ const left = Math.min(...items.map((o) => o.p.cx - o.p.r));
47213
+ const right = Math.max(...items.map((o) => o.p.cx + o.p.r));
47214
+ const cyMid = (Math.min(...items.map((o) => o.p.cy)) + Math.max(...items.map((o) => o.p.cy))) / 2;
47215
+ const maxW = Math.max(...items.map((o) => o.w));
47216
+ const side = right + COL_GAP + maxW <= width - 2 ? "right" : "left";
47217
+ const colX = side === "right" ? right + COL_GAP : left - COL_GAP;
47218
+ const totalH = items.length * step;
47219
+ let startY = cyMid - totalH / 2;
47220
+ startY = Math.max(2, Math.min(startY, height - totalH - 2));
47221
+ items.forEach((o, i) => {
47222
+ const rowCy = startY + i * step + step / 2;
47223
+ obstacles.push({
47224
+ x: side === "right" ? colX : colX - o.w,
47225
+ y: rowCy - poiLabH / 2,
47226
+ w: o.w,
47227
+ h: poiLabH
47228
+ });
46581
47229
  labels.push({
46582
- x: inline.x,
46583
- y: p.cy + FONT / 3,
46584
- text,
46585
- anchor: "start",
47230
+ x: colX,
47231
+ y: rowCy + FONT / 3,
47232
+ text: o.text,
47233
+ anchor: side === "right" ? "start" : "end",
46586
47234
  color: palette.text,
46587
47235
  halo: true,
46588
- lineNumber: p.lineNumber
47236
+ haloColor: palette.bg,
47237
+ leader: {
47238
+ x1: o.p.cx,
47239
+ y1: o.p.cy,
47240
+ x2: side === "right" ? colX - 2 : colX + 2,
47241
+ y2: rowCy
47242
+ },
47243
+ leaderColor: o.p.fill,
47244
+ poiId: o.p.id,
47245
+ lineNumber: o.p.lineNumber
46589
47246
  });
46590
- continue;
46591
- }
46592
- let placed = false;
46593
- for (let k = 1; k <= 2 && !placed; k++) {
46594
- for (const [dx, dy] of RING_DIRS) {
46595
- const cx = p.cx + dx * LEADER_STEP * k;
46596
- const cy = p.cy + dy * LEADER_STEP * k;
46597
- const rect = {
46598
- x: dx >= 0 ? cx : cx - w,
46599
- y: cy - h / 2,
46600
- w,
46601
- h
46602
- };
46603
- if (rect.x < 0 || rect.x + rect.w > width || rect.y < 0 || rect.y + rect.h > height) {
46604
- continue;
46605
- }
46606
- if (collides(rect)) continue;
46607
- obstacles.push(rect);
46608
- labels.push({
46609
- x: cx,
46610
- y: cy + FONT / 3,
46611
- text,
46612
- anchor: dx >= 0 ? "start" : "end",
46613
- color: palette.text,
46614
- halo: true,
46615
- leader: { x1: p.cx, y1: p.cy, x2: cx, y2: cy },
46616
- lineNumber: p.lineNumber
46617
- });
46618
- placed = true;
46619
- break;
47247
+ });
47248
+ };
47249
+ for (const g of groups) {
47250
+ if (g.length === 1) {
47251
+ const p = g[0];
47252
+ const { text, w } = labelInfo(p);
47253
+ if (inlineFits(p, w, "right")) {
47254
+ pushInline(p, text, w, "right");
47255
+ continue;
47256
+ }
47257
+ if (inlineFits(p, w, "left")) {
47258
+ pushInline(p, text, w, "left");
47259
+ continue;
46620
47260
  }
46621
47261
  }
46622
- if (placed) continue;
46623
- pinCounter += 1;
46624
- pinList.push({ pin: pinCounter, label: text });
46625
- labels.push({
46626
- x: p.cx + p.r + 2,
46627
- y: p.cy - p.r,
46628
- text: String(pinCounter),
46629
- anchor: "start",
46630
- color: palette.text,
46631
- halo: true,
46632
- pin: pinCounter,
46633
- lineNumber: p.lineNumber
46634
- });
47262
+ placeColumn(g);
46635
47263
  }
46636
47264
  }
46637
47265
  let legend = null;
@@ -46640,8 +47268,7 @@ function layoutMap(resolved, data, size, opts) {
46640
47268
  name: g.name,
46641
47269
  entries: g.entries.map((e) => ({ value: e.value, color: e.color }))
46642
47270
  }));
46643
- const hasAnything = tagGroups.length > 0 || hasRamp || sizeVals.length > 0 || weightVals.length > 0;
46644
- if (hasAnything) {
47271
+ if (tagGroups.length > 0 || hasRamp) {
46645
47272
  legend = {
46646
47273
  tagGroups,
46647
47274
  activeGroup,
@@ -46652,20 +47279,9 @@ function layoutMap(resolved, data, size, opts) {
46652
47279
  },
46653
47280
  min: rampMin,
46654
47281
  max: rampMax,
46655
- hue: rampHue
47282
+ hue: rampHue,
47283
+ base: rampBase
46656
47284
  }
46657
- },
46658
- ...sizeVals.length > 0 && {
46659
- size: {
46660
- ...resolved.directives.sizeMetric !== void 0 && {
46661
- metric: resolved.directives.sizeMetric
46662
- },
46663
- min: sizeMin,
46664
- max: sizeMax
46665
- }
46666
- },
46667
- ...weightVals.length > 0 && {
46668
- weight: { min: wMin, max: wMax }
46669
47285
  }
46670
47286
  };
46671
47287
  }
@@ -46673,28 +47289,30 @@ function layoutMap(resolved, data, size, opts) {
46673
47289
  return {
46674
47290
  width,
46675
47291
  height,
46676
- background: palette.bg,
47292
+ background: water,
46677
47293
  title: resolved.title,
46678
47294
  ...resolved.subtitle !== void 0 && { subtitle: resolved.subtitle },
46679
47295
  ...resolved.caption !== void 0 && { caption: resolved.caption },
46680
47296
  regions,
47297
+ rivers,
46681
47298
  legs,
46682
47299
  pois,
46683
47300
  labels,
46684
- pinList,
46685
- legend
47301
+ legend,
47302
+ insets,
47303
+ insetRegions
46686
47304
  };
46687
47305
  }
46688
- var import_d3_geo2, import_topojson_client2, FIT_PAD, RAMP_FLOOR, R_DEFAULT, R_MIN, R_MAX, W_MIN, W_MAX, FONT, LEADER_STEP, COLO_EPS, COLO_R, GOLDEN_ANGLE, FAN_STEP, TINY_REGION_AREA, ARC_CURVE_FRAC, RING_DIRS;
47306
+ var import_d3_geo2, import_topojson_client2, FIT_PAD, RAMP_FLOOR, R_DEFAULT, R_MIN, R_MAX, W_MIN, W_MAX, FONT, COLO_EPS, LAND_TINT_LIGHT, LAND_TINT_DARK, TAG_TINT_LIGHT, TAG_TINT_DARK, WATER_TINT, RIVER_WIDTH, FOREIGN_TINT_LIGHT, FOREIGN_TINT_DARK, COLO_R, GOLDEN_ANGLE, FAN_STEP, ARC_CURVE_FRAC, usConusProjection, alaskaProjection, hawaiiProjection, INSET_STATES, US_NON_CONUS;
46689
47307
  var init_layout15 = __esm({
46690
47308
  "src/map/layout.ts"() {
46691
47309
  "use strict";
46692
47310
  import_d3_geo2 = require("d3-geo");
46693
47311
  import_topojson_client2 = require("topojson-client");
46694
47312
  init_color_utils();
46695
- init_tag_groups();
46696
47313
  init_label_layout();
46697
47314
  init_legend_constants();
47315
+ init_title_constants();
46698
47316
  FIT_PAD = 24;
46699
47317
  RAMP_FLOOR = 15;
46700
47318
  R_DEFAULT = 6;
@@ -46703,23 +47321,32 @@ var init_layout15 = __esm({
46703
47321
  W_MIN = 1.25;
46704
47322
  W_MAX = 8;
46705
47323
  FONT = 11;
46706
- LEADER_STEP = 14;
46707
47324
  COLO_EPS = 1.5;
47325
+ LAND_TINT_LIGHT = 58;
47326
+ LAND_TINT_DARK = 75;
47327
+ TAG_TINT_LIGHT = 60;
47328
+ TAG_TINT_DARK = 68;
47329
+ WATER_TINT = 55;
47330
+ RIVER_WIDTH = 1.3;
47331
+ FOREIGN_TINT_LIGHT = 30;
47332
+ FOREIGN_TINT_DARK = 62;
46708
47333
  COLO_R = 9;
46709
47334
  GOLDEN_ANGLE = 2.399963229728653;
46710
47335
  FAN_STEP = 16;
46711
- TINY_REGION_AREA = 600;
46712
47336
  ARC_CURVE_FRAC = 0.18;
46713
- RING_DIRS = [
46714
- [1, 0],
46715
- [0, 1],
46716
- [-1, 0],
46717
- [0, -1],
46718
- [1, 1],
46719
- [-1, 1],
46720
- [-1, -1],
46721
- [1, -1]
46722
- ];
47337
+ usConusProjection = () => (0, import_d3_geo2.geoConicEqualArea)().parallels([29.5, 45.5]).rotate([96, 0]);
47338
+ alaskaProjection = () => (0, import_d3_geo2.geoConicEqualArea)().rotate([154, 0]).center([-2, 58.5]).parallels([55, 65]);
47339
+ hawaiiProjection = () => (0, import_d3_geo2.geoMercator)();
47340
+ INSET_STATES = /* @__PURE__ */ new Set(["US-AK", "US-HI"]);
47341
+ US_NON_CONUS = /* @__PURE__ */ new Set([
47342
+ "US-AK",
47343
+ "US-HI",
47344
+ "US-AS",
47345
+ "US-GU",
47346
+ "US-MP",
47347
+ "US-PR",
47348
+ "US-VI"
47349
+ ]);
46723
47350
  }
46724
47351
  });
46725
47352
 
@@ -46729,7 +47356,7 @@ __export(renderer_exports16, {
46729
47356
  renderMap: () => renderMap,
46730
47357
  renderMapForExport: () => renderMapForExport
46731
47358
  });
46732
- function renderMap(container, resolved, data, palette, isDark, onClickItem, exportDims) {
47359
+ function renderMap(container, resolved, data, palette, isDark, onClickItem, exportDims, activeGroupOverride) {
46733
47360
  d3Selection18.select(container).selectAll(":not([data-d3-tooltip])").remove();
46734
47361
  const width = exportDims?.width ?? container.clientWidth;
46735
47362
  const height = exportDims?.height ?? container.clientHeight;
@@ -46740,27 +47367,29 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46740
47367
  { width, height },
46741
47368
  {
46742
47369
  palette,
46743
- isDark
47370
+ isDark,
47371
+ ...activeGroupOverride !== void 0 && {
47372
+ activeGroup: activeGroupOverride
47373
+ }
46744
47374
  }
46745
47375
  );
46746
- 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);
47376
+ 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);
46747
47377
  svg.append("rect").attr("width", width).attr("height", height).attr("fill", layout.background);
46748
- const arrowColor = mix(palette.text, palette.bg, 50);
46749
47378
  const defs = svg.append("defs");
46750
- 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);
46751
- const haloColor = layout.background;
46752
- if (layout.title) {
46753
- 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);
46754
- }
46755
- if (layout.subtitle) {
46756
- 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);
46757
- }
46758
- if (layout.caption) {
46759
- 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);
46760
- }
47379
+ const arrowSize = (w) => Math.min(15, 7 + w * 0.95);
47380
+ const haloColor = palette.bg;
46761
47381
  const gRegions = svg.append("g").attr("class", "dgmo-map-regions");
46762
- for (const r of layout.regions) {
46763
- const p = gRegions.append("path").attr("d", r.d).attr("fill", r.fill).attr("stroke", r.stroke).attr("stroke-width", 0.5);
47382
+ const drawRegion = (g, r, strokeWidth) => {
47383
+ const p = g.append("path").attr("d", r.d).attr("fill", r.fill).attr("stroke", r.stroke).attr("stroke-width", strokeWidth);
47384
+ if (r.layer !== "base") {
47385
+ p.classed("dgmo-map-region", true).attr("data-region", r.id);
47386
+ if (r.score !== void 0) p.attr("data-score", r.score);
47387
+ if (r.tags) {
47388
+ for (const [group, value] of Object.entries(r.tags)) {
47389
+ p.attr(`data-tag-${group.toLowerCase()}`, value.toLowerCase());
47390
+ }
47391
+ }
47392
+ }
46764
47393
  if (r.lineNumber >= 0) {
46765
47394
  p.attr("data-line-number", r.lineNumber);
46766
47395
  if (onClickItem) {
@@ -46770,11 +47399,31 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46770
47399
  );
46771
47400
  }
46772
47401
  }
47402
+ };
47403
+ for (const r of layout.regions) drawRegion(gRegions, r, 0.5);
47404
+ if (layout.rivers.length) {
47405
+ const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none");
47406
+ for (const r of layout.rivers) {
47407
+ gRivers.append("path").attr("d", r.d).attr("stroke", r.color).attr("stroke-width", r.width).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
47408
+ }
47409
+ }
47410
+ if (layout.insets.length) {
47411
+ const insetG = svg.append("g").attr("class", "dgmo-map-insets");
47412
+ for (const box of layout.insets) {
47413
+ const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
47414
+ 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");
47415
+ }
47416
+ for (const r of layout.insetRegions) drawRegion(insetG, r, 0.5);
46773
47417
  }
46774
47418
  const gLegs = svg.append("g").attr("class", "dgmo-map-legs").attr("fill", "none");
46775
- for (const leg of layout.legs) {
47419
+ layout.legs.forEach((leg, i) => {
46776
47420
  const p = gLegs.append("path").attr("d", leg.d).attr("stroke", leg.color).attr("stroke-width", leg.width).attr("stroke-linecap", "round");
46777
- if (leg.arrow) p.attr("marker-end", "url(#dgmo-map-arrow)");
47421
+ if (leg.arrow) {
47422
+ const id = `dgmo-map-arrow-${i}`;
47423
+ const s = arrowSize(leg.width);
47424
+ 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);
47425
+ p.attr("marker-end", `url(#${id})`);
47426
+ }
46778
47427
  if (leg.label !== void 0 && leg.labelX !== void 0) {
46779
47428
  emitText(
46780
47429
  gLegs,
@@ -46788,13 +47437,13 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46788
47437
  LABEL_FONT - 1
46789
47438
  );
46790
47439
  }
46791
- }
47440
+ });
46792
47441
  const gPois = svg.append("g").attr("class", "dgmo-map-pois");
46793
47442
  for (const poi of layout.pois) {
46794
47443
  if (poi.isOrigin) {
46795
47444
  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);
46796
47445
  }
46797
- 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);
47446
+ 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);
46798
47447
  if (onClickItem) {
46799
47448
  c.style("cursor", "pointer").on(
46800
47449
  "click",
@@ -46818,48 +47467,66 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46818
47467
  const gLabels = svg.append("g").attr("class", "dgmo-map-labels");
46819
47468
  for (const lab of layout.labels) {
46820
47469
  if (lab.leader) {
46821
- 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);
47470
+ 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(
47471
+ "stroke",
47472
+ lab.leaderColor ?? mix(palette.textMuted, palette.bg, 60)
47473
+ ).attr("stroke-width", lab.leaderColor ? 1 : 0.75);
47474
+ if (lab.poiId !== void 0) line12.attr("data-poi", lab.poiId);
46822
47475
  }
46823
- if (lab.pin !== void 0) {
46824
- 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);
46825
- }
46826
- emitText(
47476
+ const t = emitText(
46827
47477
  gLabels,
46828
47478
  lab.x,
46829
47479
  lab.y,
46830
47480
  lab.text,
46831
47481
  lab.anchor,
46832
47482
  lab.color,
46833
- haloColor,
47483
+ lab.haloColor,
46834
47484
  lab.halo,
46835
47485
  LABEL_FONT
46836
47486
  );
46837
- }
46838
- if (layout.pinList.length > 0) {
46839
- const gPins = svg.append("g").attr("class", "dgmo-map-pin-list").attr(
46840
- "transform",
46841
- `translate(12, ${height - layout.pinList.length * 14 - 8})`
46842
- );
46843
- layout.pinList.forEach((entry, i) => {
46844
- 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}`);
46845
- });
47487
+ if (lab.poiId !== void 0) {
47488
+ t.attr("data-poi", lab.poiId).style("cursor", "default");
47489
+ }
46846
47490
  }
46847
47491
  if (layout.legend) {
46848
47492
  const legendY = (layout.title ? TITLE_Y + TITLE_FONT_SIZE : 0) + (layout.subtitle ? TITLE_FONT_SIZE : 0) + 8;
46849
47493
  const legendG = svg.append("g").attr("class", "dgmo-map-legend").attr("transform", `translate(0, ${legendY})`);
46850
- const groups = layout.legend.tagGroups.filter((g) => g.entries.length > 0);
47494
+ const ramp = layout.legend.ramp;
47495
+ const scoreGroup = ramp ? {
47496
+ name: ramp.metric?.trim() || "Score",
47497
+ entries: [],
47498
+ gradient: {
47499
+ min: ramp.min,
47500
+ max: ramp.max,
47501
+ hue: ramp.hue,
47502
+ base: ramp.base
47503
+ }
47504
+ } : null;
47505
+ const tagGroups = layout.legend.tagGroups.filter((g) => g.entries.length > 0).map((g) => ({ name: g.name, entries: [...g.entries] }));
47506
+ const groups = [...scoreGroup ? [scoreGroup] : [], ...tagGroups];
46851
47507
  if (groups.length > 0) {
46852
47508
  const config = {
46853
- groups: groups.map((g) => ({ name: g.name, entries: [...g.entries] })),
47509
+ groups,
46854
47510
  position: { placement: "top-center", titleRelation: "below-title" },
46855
47511
  mode: exportDims ? "export" : "preview",
46856
- showEmptyGroups: false
47512
+ showEmptyGroups: false,
47513
+ // Keep inactive siblings visible as pills so the user can click to flip
47514
+ // the active colouring dimension (preview only — export shows just the
47515
+ // active group).
47516
+ showInactivePills: true
46857
47517
  };
46858
47518
  const state = { activeGroup: layout.legend.activeGroup };
46859
47519
  renderLegendD3(legendG, config, state, palette, isDark, void 0, width);
46860
47520
  }
46861
- const pinGap = layout.pinList.length ? layout.pinList.length * 14 + 14 : 0;
46862
- emitExtraLegend(svg, layout, palette, height, pinGap);
47521
+ }
47522
+ if (layout.title) {
47523
+ 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);
47524
+ }
47525
+ if (layout.subtitle) {
47526
+ 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);
47527
+ }
47528
+ if (layout.caption) {
47529
+ 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);
46863
47530
  }
46864
47531
  }
46865
47532
  function renderMapForExport(container, resolved, data, palette, isDark, exportDims) {
@@ -46870,54 +47537,7 @@ function emitText(g, x, y, text, anchor, color, halo, withHalo, fontSize) {
46870
47537
  if (withHalo) {
46871
47538
  t.attr("paint-order", "stroke fill").attr("stroke", halo).attr("stroke-width", 3).attr("stroke-linejoin", "round").attr("stroke-opacity", 0.7);
46872
47539
  }
46873
- }
46874
- function emitExtraLegend(svg, layout, palette, height, bottomGap) {
46875
- const { legend } = layout;
46876
- if (!legend) return;
46877
- if (!legend.ramp && !legend.size && !legend.weight) return;
46878
- const blocks = [];
46879
- const g = svg.append("g").attr("class", "dgmo-map-legend-keys").attr("transform", `translate(12, ${height - 56 - bottomGap})`);
46880
- let xCursor = 0;
46881
- if (legend.ramp) {
46882
- const ramp = legend.ramp;
46883
- blocks.push(() => {
46884
- const block = g.append("g").attr("transform", `translate(${xCursor},0)`);
46885
- const gradId = "dgmo-map-ramp";
46886
- const grad = block.append("defs").append("linearGradient").attr("id", gradId).attr("x1", "0%").attr("x2", "100%");
46887
- grad.append("stop").attr("offset", "0%").attr("stop-color", mix(ramp.hue, palette.bg, 15));
46888
- grad.append("stop").attr("offset", "100%").attr("stop-color", ramp.hue);
46889
- block.append("rect").attr("width", 80).attr("height", 8).attr("fill", `url(#${gradId})`);
46890
- block.append("text").attr("x", 0).attr("y", 22).attr("font-size", 9).attr("fill", palette.textMuted).text(String(ramp.min));
46891
- 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));
46892
- if (ramp.metric) {
46893
- block.append("text").attr("x", 0).attr("y", -4).attr("font-size", 9).attr("fill", palette.textMuted).text(ramp.metric);
46894
- }
46895
- xCursor += 110;
46896
- });
46897
- }
46898
- if (legend.size) {
46899
- const sz = legend.size;
46900
- blocks.push(() => {
46901
- const block = g.append("g").attr("transform", `translate(${xCursor},0)`);
46902
- [3, 6, 10].forEach((r, i) => {
46903
- block.append("circle").attr("cx", i * 26 + r).attr("cy", 8).attr("r", r).attr("fill", "none").attr("stroke", palette.textMuted);
46904
- });
46905
- block.append("text").attr("x", 0).attr("y", -4).attr("font-size", 9).attr("fill", palette.textMuted).text(sz.metric ?? "size");
46906
- xCursor += 110;
46907
- });
46908
- }
46909
- if (legend.weight) {
46910
- const wt = legend.weight;
46911
- blocks.push(() => {
46912
- const block = g.append("g").attr("transform", `translate(${xCursor},0)`);
46913
- [1, 3, 6].forEach((w, i) => {
46914
- 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);
46915
- });
46916
- block.append("text").attr("x", 0).attr("y", -4).attr("font-size", 9).attr("fill", palette.textMuted).text(wt.metric ?? "weight");
46917
- xCursor += 110;
46918
- });
46919
- }
46920
- for (const draw of blocks) draw();
47540
+ return t;
46921
47541
  }
46922
47542
  var d3Selection18, LABEL_FONT;
46923
47543
  var init_renderer16 = __esm({
@@ -53441,18 +54061,18 @@ function renderWordCloud(container, parsed, palette, _isDark, onClickItem, expor
53441
54061
  }).start();
53442
54062
  }
53443
54063
  function renderWordCloudAsync(container, parsed, palette, _isDark, exportDims) {
53444
- return new Promise((resolve2) => {
54064
+ return new Promise((resolve) => {
53445
54065
  d3Selection23.select(container).selectAll(":not([data-d3-tooltip])").remove();
53446
54066
  const { words, cloudOptions } = parsed;
53447
54067
  const title = parsed.noTitle ? null : parsed.title;
53448
54068
  if (words.length === 0) {
53449
- resolve2();
54069
+ resolve();
53450
54070
  return;
53451
54071
  }
53452
54072
  const width = exportDims?.width ?? container.clientWidth;
53453
54073
  const height = exportDims?.height ?? container.clientHeight;
53454
54074
  if (width <= 0 || height <= 0) {
53455
- resolve2();
54075
+ resolve();
53456
54076
  return;
53457
54077
  }
53458
54078
  const titleHeight = title ? 40 : 0;
@@ -53481,7 +54101,7 @@ function renderWordCloudAsync(container, parsed, palette, _isDark, exportDims) {
53481
54101
  "transform",
53482
54102
  (d) => `translate(${d.x},${d.y}) rotate(${d.rotate})`
53483
54103
  ).text((d) => d.text);
53484
- resolve2();
54104
+ resolve();
53485
54105
  }).start();
53486
54106
  });
53487
54107
  }
@@ -56038,6 +56658,8 @@ __export(advanced_exports, {
56038
56658
  looksLikeSitemap: () => looksLikeSitemap,
56039
56659
  looksLikeState: () => looksLikeState,
56040
56660
  makeDgmoError: () => makeDgmoError,
56661
+ mapBackgroundColor: () => mapBackgroundColor,
56662
+ mapNeutralLandColor: () => mapNeutralLandColor,
56041
56663
  matchesContiguously: () => matchesContiguously,
56042
56664
  measurePertAnalysisBlock: () => measurePertAnalysisBlock,
56043
56665
  migrateContent: () => migrateContent,
@@ -56912,12 +57534,12 @@ var KNOWN_HEADER_OPTIONS = /* @__PURE__ */ new Set([
56912
57534
  "solid-fill",
56913
57535
  "active-tag"
56914
57536
  ]);
56915
- function dirname2(filePath) {
57537
+ function dirname(filePath) {
56916
57538
  const last = filePath.lastIndexOf("/");
56917
57539
  return last > 0 ? filePath.substring(0, last) : "/";
56918
57540
  }
56919
57541
  function resolvePath(base, relative) {
56920
- const parts = dirname2(base).split("/");
57542
+ const parts = dirname(base).split("/");
56921
57543
  for (const seg of relative.split("/")) {
56922
57544
  if (seg === "..") {
56923
57545
  if (parts.length > 1) parts.pop();
@@ -57820,10 +58442,13 @@ var COMPLETION_REGISTRY = /* @__PURE__ */ new Map([
57820
58442
  // keywords, not directives; metadata keys (score/size/label) live in the
57821
58443
  // reserved-key registry.
57822
58444
  withGlobals({
57823
- region: { description: "Force a basemap/extent (world | us-states)" },
58445
+ region: {
58446
+ description: "Basemap: us-states (force US state mesh + scoping) | world (inert \u2014 already the default)",
58447
+ values: ["us-states", "world"]
58448
+ },
57824
58449
  projection: {
57825
58450
  description: "Override the auto projection",
57826
- values: ["natural-earth", "albers-usa", "mercator"]
58451
+ values: ["equirectangular", "natural-earth", "albers-usa", "mercator"]
57827
58452
  },
57828
58453
  metric: { description: "Label for the region score ramp" },
57829
58454
  "size-metric": { description: "Label for the POI size channel" },
@@ -59041,7 +59666,7 @@ init_dgmo_router();
59041
59666
 
59042
59667
  // src/migrate/index.ts
59043
59668
  var import_node_fs = require("fs");
59044
- var import_node_path2 = require("path");
59669
+ var import_node_path = require("path");
59045
59670
  init_dgmo_router();
59046
59671
 
59047
59672
  // src/migrate/line-classifier.ts
@@ -59411,6 +60036,8 @@ function formatLineDiff(path, original, migrated) {
59411
60036
  looksLikeSitemap,
59412
60037
  looksLikeState,
59413
60038
  makeDgmoError,
60039
+ mapBackgroundColor,
60040
+ mapNeutralLandColor,
59414
60041
  matchesContiguously,
59415
60042
  measurePertAnalysisBlock,
59416
60043
  migrateContent,