@diagrammo/dgmo 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/advanced.cjs +919 -298
  2. package/dist/advanced.d.cts +148 -54
  3. package/dist/advanced.d.ts +148 -54
  4. package/dist/advanced.js +922 -300
  5. package/dist/auto.cjs +904 -297
  6. package/dist/auto.js +117 -117
  7. package/dist/auto.mjs +909 -299
  8. package/dist/cli.cjs +159 -159
  9. package/dist/index.cjs +903 -296
  10. package/dist/index.js +908 -298
  11. package/dist/internal.cjs +919 -298
  12. package/dist/internal.d.cts +148 -54
  13. package/dist/internal.d.ts +148 -54
  14. package/dist/internal.js +922 -300
  15. package/dist/map-data/PROVENANCE.json +1 -1
  16. package/dist/map-data/lakes.json +1 -0
  17. package/dist/map-data/na-lakes.json +1 -0
  18. package/dist/map-data/na-land.json +1 -0
  19. package/dist/map-data/rivers.json +1 -0
  20. package/docs/language-reference.md +12 -7
  21. package/gallery/fixtures/map-region-scope.dgmo +15 -0
  22. package/package.json +4 -4
  23. package/src/advanced.ts +6 -2
  24. package/src/c4/parser.ts +6 -6
  25. package/src/completion.ts +6 -2
  26. package/src/echarts.ts +1 -1
  27. package/src/infra/parser.ts +10 -10
  28. package/src/journey-map/parser.ts +1 -1
  29. package/src/label-layout.ts +36 -0
  30. package/src/map/data/PROVENANCE.json +1 -1
  31. package/src/map/data/README.md +2 -0
  32. package/src/map/data/lakes.json +1 -0
  33. package/src/map/data/na-lakes.json +1 -0
  34. package/src/map/data/na-land.json +1 -0
  35. package/src/map/data/rivers.json +1 -0
  36. package/src/map/layout.ts +1022 -205
  37. package/src/map/load-data.ts +29 -2
  38. package/src/map/parser.ts +22 -13
  39. package/src/map/renderer.ts +200 -219
  40. package/src/map/resolved-types.ts +18 -1
  41. package/src/map/resolver.ts +79 -7
  42. package/src/map/types.ts +4 -0
  43. package/src/mindmap/parser.ts +1 -1
  44. package/src/sitemap/parser.ts +1 -1
  45. package/src/utils/legend-d3.ts +42 -0
  46. package/src/utils/legend-layout.ts +83 -3
  47. package/src/utils/legend-svg.ts +1 -8
  48. package/src/utils/legend-types.ts +44 -1
package/dist/internal.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",
@@ -46201,13 +46338,36 @@ function moduleBaseDir() {
46201
46338
  function loadMapData() {
46202
46339
  cache ??= (async () => {
46203
46340
  const dir = await firstExistingDir(moduleBaseDir());
46204
- const [worldCoarse, worldDetail, usStates, gazetteer] = await Promise.all([
46341
+ const [
46342
+ worldCoarse,
46343
+ worldDetail,
46344
+ usStates,
46345
+ lakes,
46346
+ rivers,
46347
+ naLand,
46348
+ naLakes,
46349
+ gazetteer
46350
+ ] = await Promise.all([
46205
46351
  readJson(dir, FILES.worldCoarse),
46206
46352
  readJson(dir, FILES.worldDetail),
46207
46353
  readJson(dir, FILES.usStates),
46354
+ // Lakes/rivers/NA assets are optional — older bundles may predate them.
46355
+ readJson(dir, FILES.lakes).catch(() => void 0),
46356
+ readJson(dir, FILES.rivers).catch(() => void 0),
46357
+ readJson(dir, FILES.naLand).catch(() => void 0),
46358
+ readJson(dir, FILES.naLakes).catch(() => void 0),
46208
46359
  readJson(dir, FILES.gazetteer)
46209
46360
  ]);
46210
- return validate({ worldCoarse, worldDetail, usStates, gazetteer });
46361
+ return validate({
46362
+ worldCoarse,
46363
+ worldDetail,
46364
+ usStates,
46365
+ gazetteer,
46366
+ ...lakes && { lakes },
46367
+ ...rivers && { rivers },
46368
+ ...naLand && { naLand },
46369
+ ...naLakes && { naLakes }
46370
+ });
46211
46371
  })().catch((e) => {
46212
46372
  cache = void 0;
46213
46373
  throw e;
@@ -46226,6 +46386,10 @@ var init_load_data = __esm({
46226
46386
  worldCoarse: "world-coarse.json",
46227
46387
  worldDetail: "world-detail.json",
46228
46388
  usStates: "us-states.json",
46389
+ lakes: "lakes.json",
46390
+ rivers: "rivers.json",
46391
+ naLand: "na-land.json",
46392
+ naLakes: "na-lakes.json",
46229
46393
  gazetteer: "gazetteer.json"
46230
46394
  };
46231
46395
  CANDIDATE_DIRS = [
@@ -46253,36 +46417,74 @@ function decodeLayer(topo) {
46253
46417
  function projectionFor(family) {
46254
46418
  switch (family) {
46255
46419
  case "albers-usa":
46256
- return (0, import_d3_geo2.geoAlbersUsa)();
46420
+ return usConusProjection();
46257
46421
  case "mercator":
46258
46422
  return (0, import_d3_geo2.geoMercator)();
46259
46423
  case "natural-earth":
46260
- default:
46261
46424
  return (0, import_d3_geo2.geoNaturalEarth1)();
46425
+ case "equirectangular":
46426
+ default:
46427
+ return (0, import_d3_geo2.geoEquirectangular)();
46262
46428
  }
46263
46429
  }
46430
+ function mapBackgroundColor(palette) {
46431
+ return mix(palette.colors.blue, palette.bg, WATER_TINT);
46432
+ }
46433
+ function mapNeutralLandColor(palette, isDark) {
46434
+ return mix(
46435
+ palette.colors.green,
46436
+ palette.bg,
46437
+ isDark ? LAND_TINT_DARK : LAND_TINT_LIGHT
46438
+ );
46439
+ }
46264
46440
  function layoutMap(resolved, data, size, opts) {
46265
46441
  const { palette, isDark } = opts;
46266
46442
  const { width, height } = size;
46267
- const worldTopo = resolved.basemaps.world === "detail" ? data.worldDetail : data.worldCoarse;
46443
+ const wantsUsStates = resolved.basemaps.subdivisions.includes("us-states");
46444
+ const usCrisp = resolved.projection === "albers-usa" && wantsUsStates && !!data.naLand;
46445
+ const worldTopo = usCrisp ? data.naLand : resolved.basemaps.world === "detail" ? data.worldDetail : data.worldCoarse;
46268
46446
  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);
46447
+ const usLayer = wantsUsStates ? decodeLayer(data.usStates) : null;
46448
+ const landTint = isDark ? LAND_TINT_DARK : LAND_TINT_LIGHT;
46449
+ const neutralFill = mix(palette.colors.green, palette.bg, landTint);
46450
+ const water = mapBackgroundColor(palette);
46451
+ const usContext = usLayer !== null;
46452
+ const foreignFill = mix(
46453
+ palette.colors.gray,
46454
+ palette.bg,
46455
+ isDark ? FOREIGN_TINT_DARK : FOREIGN_TINT_LIGHT
46456
+ );
46457
+ const regionStroke = isDark ? mix(palette.bg, palette.text, 78) : mix(palette.text, palette.bg, 78);
46272
46458
  const scores = resolved.regions.filter((r) => r.score !== void 0).map((r) => r.score);
46273
46459
  const scaleOverride = resolved.directives.scale;
46274
46460
  const rampMin = scaleOverride ? scaleOverride.min : Math.min(...scores);
46275
46461
  const rampMax = scaleOverride ? scaleOverride.max : Math.max(...scores);
46276
- const rampHue = palette.primary;
46462
+ const rampHue = palette.colors.red;
46277
46463
  const hasRamp = scores.length > 0;
46278
- const activeGroup = resolveActiveTagGroup(
46279
- resolved.tagGroups,
46280
- resolved.directives.activeTag
46281
- );
46464
+ const SCORE_NAME = hasRamp ? resolved.directives.metric?.trim() || "Score" : null;
46465
+ const matchColorGroup = (v) => {
46466
+ const lv = v.trim().toLowerCase();
46467
+ if (lv === "none") return null;
46468
+ if (SCORE_NAME && (lv === "score" || lv === SCORE_NAME.toLowerCase()))
46469
+ return SCORE_NAME;
46470
+ const tg = resolved.tagGroups.find((g) => g.name.toLowerCase() === lv);
46471
+ return tg ? tg.name : v;
46472
+ };
46473
+ const override = opts.activeGroup;
46474
+ let activeGroup;
46475
+ if (override !== void 0) {
46476
+ activeGroup = override === null ? null : matchColorGroup(override);
46477
+ } else if (resolved.directives.activeTag !== void 0) {
46478
+ activeGroup = matchColorGroup(resolved.directives.activeTag);
46479
+ } else {
46480
+ activeGroup = SCORE_NAME ?? (resolved.tagGroups.length > 0 ? resolved.tagGroups[0].name : null);
46481
+ }
46482
+ const activeIsScore = SCORE_NAME !== null && activeGroup === SCORE_NAME;
46483
+ const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
46282
46484
  const fillForScore = (s) => {
46283
46485
  const t = rampMax > rampMin ? (s - rampMin) / (rampMax - rampMin) : 1;
46284
46486
  const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
46285
- return mix(rampHue, isDark ? palette.surface : palette.bg, pct);
46487
+ return mix(rampHue, rampBase, pct);
46286
46488
  };
46287
46489
  const tagFill = (tags, groupName) => {
46288
46490
  if (!groupName) return null;
@@ -46296,40 +46498,40 @@ function layoutMap(resolved, data, size, opts) {
46296
46498
  (e) => e.value.toLowerCase() === val.toLowerCase()
46297
46499
  );
46298
46500
  if (!entry?.color) return null;
46299
- return shapeFill(palette, entry.color, isDark);
46501
+ return mix(
46502
+ entry.color,
46503
+ palette.bg,
46504
+ isDark ? TAG_TINT_DARK : TAG_TINT_LIGHT
46505
+ );
46506
+ };
46507
+ const regionFill = (r) => {
46508
+ if (activeIsScore) {
46509
+ return r.score !== void 0 ? fillForScore(r.score) : neutralFill;
46510
+ }
46511
+ return tagFill(r.tags, activeGroup) ?? neutralFill;
46300
46512
  };
46301
46513
  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 = () => {
46514
+ const extentOutline = () => {
46308
46515
  const [[w, s], [e, n]] = resolved.extent;
46516
+ const N = 16;
46517
+ const coords = [];
46518
+ for (let i = 0; i <= N; i++) {
46519
+ const t = i / N;
46520
+ const lon = w + (e - w) * t;
46521
+ const lat = s + (n - s) * t;
46522
+ coords.push([lon, s], [lon, n], [w, lat], [e, lat]);
46523
+ }
46309
46524
  return {
46310
46525
  type: "Feature",
46311
46526
  properties: {},
46312
- geometry: {
46313
- type: "MultiPoint",
46314
- coordinates: [
46315
- [w, s],
46316
- [e, s],
46317
- [e, n],
46318
- [w, n]
46319
- ]
46320
- }
46527
+ geometry: { type: "MultiPoint", coordinates: coords }
46321
46528
  };
46322
46529
  };
46323
46530
  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
- }
46531
+ if (resolved.projection === "albers-usa" && usLayer) {
46532
+ fitFeatures = [...usLayer.entries()].filter(([iso]) => !US_NON_CONUS.has(iso)).map(([, f]) => f);
46331
46533
  } else {
46332
- fitFeatures = [extentCorners()];
46534
+ fitFeatures = [extentOutline()];
46333
46535
  }
46334
46536
  const fitTarget = { type: "FeatureCollection", features: fitFeatures };
46335
46537
  const projection = projectionFor(resolved.projection);
@@ -46338,32 +46540,311 @@ function layoutMap(resolved, data, size, opts) {
46338
46540
  if (centerLon > 180) centerLon -= 360;
46339
46541
  projection.rotate([-centerLon, 0]);
46340
46542
  }
46341
- projection.fitExtent(
46543
+ const TITLE_GAP = 16;
46544
+ let topPad = FIT_PAD;
46545
+ if (resolved.title && resolved.pois.length > 0) {
46546
+ const bannerBottom = (resolved.subtitle ? TITLE_Y + TITLE_FONT_SIZE : TITLE_Y) + TITLE_FONT_SIZE / 2;
46547
+ topPad = Math.max(FIT_PAD, bannerBottom + TITLE_GAP);
46548
+ }
46549
+ const fitBox = [
46550
+ [FIT_PAD, topPad],
46342
46551
  [
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;
46552
+ Math.max(FIT_PAD + 1, width - FIT_PAD),
46553
+ Math.max(topPad + 1, height - FIT_PAD)
46554
+ ]
46555
+ ];
46556
+ projection.fitExtent(fitBox, fitTarget);
46557
+ const fitGB = (0, import_d3_geo2.geoBounds)(fitTarget);
46558
+ const fitIsGlobal = fitGB[1][0] - fitGB[0][0] >= 270 || fitGB[1][1] - fitGB[0][1] >= 130;
46559
+ let path;
46560
+ let project;
46561
+ if (fitIsGlobal) {
46562
+ const cb = (0, import_d3_geo2.geoPath)(projection).bounds(fitTarget);
46563
+ const bx0 = cb[0][0];
46564
+ const by0 = cb[0][1];
46565
+ const cw = cb[1][0] - bx0;
46566
+ const ch = cb[1][1] - by0;
46567
+ const ox = fitBox[0][0];
46568
+ const oy = fitBox[0][1];
46569
+ const sx = cw > 0 ? (fitBox[1][0] - ox) / cw : 1;
46570
+ const sy = ch > 0 ? (fitBox[1][1] - oy) / ch : 1;
46571
+ const stretch = (x, y) => [
46572
+ ox + (x - bx0) * sx,
46573
+ oy + (y - by0) * sy
46574
+ ];
46575
+ const baseProjection = projection;
46576
+ const tx = (0, import_d3_geo2.geoTransform)({
46577
+ point(x, y) {
46578
+ const [px, py] = stretch(x, y);
46579
+ this.stream.point(px, py);
46580
+ }
46581
+ });
46582
+ path = (0, import_d3_geo2.geoPath)({
46583
+ stream: (s) => baseProjection.stream(
46584
+ tx.stream(s)
46585
+ )
46586
+ });
46587
+ project = (lon, lat) => {
46588
+ const p = baseProjection([lon, lat]);
46589
+ return p ? stretch(p[0], p[1]) : null;
46590
+ };
46591
+ } else {
46592
+ path = (0, import_d3_geo2.geoPath)(projection);
46593
+ project = (lon, lat) => projection([lon, lat]) ?? null;
46594
+ }
46595
+ const insets = [];
46596
+ const insetRegions = [];
46597
+ const insetLabelSeeds = [];
46598
+ if (resolved.projection === "albers-usa" && usLayer) {
46599
+ const PAD = 8;
46600
+ const GAP = 12;
46601
+ const yB = height - FIT_PAD;
46602
+ const BW = 8;
46603
+ const coast = /* @__PURE__ */ new Map();
46604
+ const addPt = (lon, lat) => {
46605
+ const p = projection([lon, lat]);
46606
+ if (!p) return;
46607
+ const bi = Math.floor(p[0] / BW);
46608
+ const cur = coast.get(bi);
46609
+ if (cur === void 0 || p[1] > cur) coast.set(bi, p[1]);
46610
+ };
46611
+ const walk = (co) => {
46612
+ if (Array.isArray(co) && typeof co[0] === "number")
46613
+ addPt(co[0], co[1]);
46614
+ else if (Array.isArray(co)) for (const c of co) walk(c);
46615
+ };
46616
+ for (const [iso, f] of usLayer) {
46617
+ if (US_NON_CONUS.has(iso)) continue;
46618
+ walk(f.geometry.coordinates);
46619
+ }
46620
+ const at = (x) => {
46621
+ const bi = Math.floor(x / BW);
46622
+ let y = -Infinity;
46623
+ for (let k = bi - 1; k <= bi + 1; k++) {
46624
+ const v = coast.get(k);
46625
+ if (v !== void 0 && v > y) y = v;
46626
+ }
46627
+ return y;
46628
+ };
46629
+ const coastTop = (x0, xr) => {
46630
+ const n = 24;
46631
+ const pts = [];
46632
+ let maxY = -Infinity;
46633
+ for (let i = 0; i <= n; i++) {
46634
+ const x = x0 + (xr - x0) * i / n;
46635
+ const y = at(x);
46636
+ if (y > -Infinity) {
46637
+ pts.push([x, y]);
46638
+ if (y > maxY) maxY = y;
46639
+ }
46640
+ }
46641
+ if (pts.length === 0) return () => yB - height * 0.42;
46642
+ let m = 0;
46643
+ if (pts.length >= 2) {
46644
+ let sx = 0, sy = 0, sxx = 0, sxy = 0;
46645
+ for (const [x, y] of pts) {
46646
+ sx += x;
46647
+ sy += y;
46648
+ sxx += x * x;
46649
+ sxy += x * y;
46650
+ }
46651
+ const den = pts.length * sxx - sx * sx;
46652
+ if (den !== 0) m = (pts.length * sxy - sx * sy) / den;
46653
+ }
46654
+ m = Math.max(-0.35, Math.min(0.35, m));
46655
+ let c = -Infinity;
46656
+ for (const [x, y] of pts) {
46657
+ const need = y - m * x + GAP;
46658
+ if (need > c) c = need;
46659
+ }
46660
+ return (x) => m * x + c;
46661
+ };
46662
+ const placeInset = (iso, proj, boxX, iwReq) => {
46663
+ const f = usLayer.get(iso);
46664
+ if (!f) return boxX;
46665
+ const x0 = boxX;
46666
+ const iw = Math.min(iwReq, width - FIT_PAD - x0 - 2 * PAD);
46667
+ if (iw < 24) return boxX;
46668
+ const xr = x0 + iw + 2 * PAD;
46669
+ const top = coastTop(x0, xr);
46670
+ const yL = top(x0);
46671
+ const yR = top(xr);
46672
+ proj.fitWidth(iw, f);
46673
+ const bb = (0, import_d3_geo2.geoPath)(proj).bounds(f);
46674
+ const sh = Number.isFinite(bb[0][0]) ? bb[1][1] - bb[0][1] : iw;
46675
+ const needH = sh + 2 * PAD;
46676
+ let topFit = Math.max(yL, yR);
46677
+ const bottom = Math.min(topFit + needH, yB);
46678
+ if (bottom - topFit < needH) topFit = bottom - needH;
46679
+ const lift = topFit - Math.max(yL, yR);
46680
+ const topL = yL + lift;
46681
+ const topR = yR + lift;
46682
+ proj.fitExtent(
46683
+ [
46684
+ [x0 + PAD, topFit + PAD],
46685
+ [xr - PAD, bottom - PAD]
46686
+ ],
46687
+ f
46688
+ );
46689
+ const d = (0, import_d3_geo2.geoPath)(proj)(f) ?? "";
46690
+ if (!d) return xr;
46691
+ const r = regionById.get(iso);
46692
+ let fill2 = neutralFill;
46693
+ let lineNumber = -1;
46694
+ if (r?.layer === "us-state") {
46695
+ fill2 = regionFill(r);
46696
+ lineNumber = r.lineNumber;
46697
+ }
46698
+ insets.push({
46699
+ x: x0,
46700
+ y: Math.min(topL, topR),
46701
+ w: xr - x0,
46702
+ h: bottom - Math.min(topL, topR),
46703
+ points: [
46704
+ [x0, topL],
46705
+ [xr, topR],
46706
+ [xr, bottom],
46707
+ [x0, bottom]
46708
+ ]
46709
+ });
46710
+ insetRegions.push({
46711
+ id: iso,
46712
+ d,
46713
+ fill: fill2,
46714
+ stroke: regionStroke,
46715
+ lineNumber,
46716
+ layer: "us-state",
46717
+ ...r?.score !== void 0 && { score: r.score },
46718
+ ...r && Object.keys(r.tags).length > 0 && { tags: r.tags }
46719
+ });
46720
+ const ctr = (0, import_d3_geo2.geoPath)(proj).centroid(f);
46721
+ if (Number.isFinite(ctr[0])) {
46722
+ const name = f.properties?.name ?? iso;
46723
+ insetLabelSeeds.push({ x: ctr[0], y: ctr[1], iso, name, lineNumber });
46724
+ }
46725
+ return xr;
46726
+ };
46727
+ const akRight = placeInset(
46728
+ "US-AK",
46729
+ alaskaProjection(),
46730
+ FIT_PAD,
46731
+ width * 0.15
46732
+ );
46733
+ placeInset("US-HI", hawaiiProjection(), akRight + 24, width * 0.1);
46734
+ }
46735
+ const conusFit = resolved.projection === "albers-usa" && !!usLayer;
46736
+ const cullExtent = conusFit ? (0, import_d3_geo2.geoBounds)(fitTarget) : resolved.extent;
46737
+ const [[exW, exS], [exE, exN]] = cullExtent;
46738
+ const lonSpan = exE - exW;
46739
+ const latSpan = exN - exS;
46740
+ const isGlobalView = lonSpan >= 270 || latSpan >= 130;
46741
+ const padLon = Math.max(8, lonSpan * 0.35);
46742
+ const padLat = Math.max(8, latSpan * 0.35);
46743
+ const vW = exW - padLon;
46744
+ const vE = exE + padLon;
46745
+ const vS = exS - padLat;
46746
+ const vN = exN + padLat;
46747
+ const vLonCenter = (exW + exE) / 2;
46748
+ const normLon = (lon) => {
46749
+ let L = lon;
46750
+ while (L < vLonCenter - 180) L += 360;
46751
+ while (L > vLonCenter + 180) L -= 360;
46752
+ return L;
46753
+ };
46754
+ const ringOverlapsView = (ring) => {
46755
+ let anyIn = false;
46756
+ let loMin = Infinity, loMax = -Infinity, laMin = Infinity, laMax = -Infinity, rawMin = Infinity, rawMax = -Infinity;
46757
+ for (const [rawLon, lat] of ring) {
46758
+ const lon = normLon(rawLon);
46759
+ if (lon >= vW && lon <= vE && lat >= vS && lat <= vN) anyIn = true;
46760
+ if (lon < loMin) loMin = lon;
46761
+ if (lon > loMax) loMax = lon;
46762
+ if (rawLon < rawMin) rawMin = rawLon;
46763
+ if (rawLon > rawMax) rawMax = rawLon;
46764
+ if (lat < laMin) laMin = lat;
46765
+ if (lat > laMax) laMax = lat;
46766
+ }
46767
+ if (loMax - loMin > 270) return false;
46768
+ if (rawMax - rawMin > 180 && loMax - loMin < 90) return false;
46769
+ if (anyIn) return true;
46770
+ if (loMax - loMin > 180) return false;
46771
+ return !(loMax < vW || loMin > vE || laMax < vS || laMin > vN);
46772
+ };
46773
+ const cullFeatureToView = (f) => {
46774
+ if (isGlobalView) return f;
46775
+ const g = f.geometry;
46776
+ if (!g) return f;
46777
+ if (g.type === "Polygon") {
46778
+ const ring = g.coordinates[0];
46779
+ return ringOverlapsView(ring) ? f : null;
46780
+ }
46781
+ if (g.type === "MultiPolygon") {
46782
+ const polys = g.coordinates;
46783
+ const keep = polys.filter(
46784
+ (p) => ringOverlapsView(p[0])
46785
+ );
46786
+ if (!keep.length) return null;
46787
+ if (keep.length === polys.length) return f;
46788
+ return { ...f, geometry: { ...g, coordinates: keep } };
46789
+ }
46790
+ return f;
46791
+ };
46792
+ const SEAM_SLIVER_MAX_SPAN = 100;
46793
+ const ringIsFrameFiller = (ring) => {
46794
+ const lons = ring.map(([lon]) => lon).sort((a, b) => a - b);
46795
+ if (lons.length < 2) return false;
46796
+ let maxGap = -1;
46797
+ let gapIdx = 0;
46798
+ for (let i = 1; i < lons.length; i++) {
46799
+ const g = lons[i] - lons[i - 1];
46800
+ if (g > maxGap) {
46801
+ maxGap = g;
46802
+ gapIdx = i;
46803
+ }
46804
+ }
46805
+ const wrapGap = lons[0] + 360 - lons[lons.length - 1];
46806
+ if (wrapGap >= maxGap) return false;
46807
+ const span = 360 - maxGap;
46808
+ const east = lons[gapIdx - 1] + 360;
46809
+ return east > 180 && span < SEAM_SLIVER_MAX_SPAN;
46810
+ };
46811
+ const dropFrameFillers = (f) => {
46812
+ const g = f.geometry;
46813
+ if (!g) return f;
46814
+ if (g.type === "Polygon") {
46815
+ const ring = g.coordinates[0];
46816
+ return ringIsFrameFiller(ring) ? null : f;
46817
+ }
46818
+ if (g.type === "MultiPolygon") {
46819
+ const polys = g.coordinates;
46820
+ const keep = polys.filter(
46821
+ (p) => !ringIsFrameFiller(p[0])
46822
+ );
46823
+ if (!keep.length) return null;
46824
+ if (keep.length === polys.length) return f;
46825
+ return { ...f, geometry: { ...g, coordinates: keep } };
46826
+ }
46827
+ return f;
46828
+ };
46353
46829
  const regions = [];
46354
- const pushRegionLayer = (layerFeatures, layerKind) => {
46830
+ const pushRegionLayer = (layerFeatures, layerKind, shouldCull) => {
46355
46831
  for (const [iso, f] of layerFeatures) {
46356
- const d = path(f) ?? "";
46357
- if (!d) continue;
46832
+ if (layerKind === "us-state" && usContext && INSET_STATES.has(iso))
46833
+ continue;
46834
+ if (layerKind === "country" && usContext && iso === "US") continue;
46358
46835
  const r = regionById.get(iso);
46836
+ const viewF = shouldCull ? cullFeatureToView(f) : dropFrameFillers(f);
46837
+ if (!viewF) continue;
46838
+ const d = path(viewF) ?? "";
46839
+ if (!d) continue;
46359
46840
  const isThisLayer = r?.layer === layerKind;
46360
- let fill2 = neutralFill;
46841
+ const isForeign = layerKind === "country" && usContext && iso !== "US";
46842
+ let fill2 = isForeign ? foreignFill : neutralFill;
46361
46843
  let label;
46362
46844
  let lineNumber = -1;
46363
46845
  let layer = "base";
46364
46846
  if (isThisLayer) {
46365
- if (r.score !== void 0) fill2 = fillForScore(r.score);
46366
- else fill2 = tagFill(r.tags, activeGroup) ?? neutralFill;
46847
+ fill2 = regionFill(r);
46367
46848
  lineNumber = r.lineNumber;
46368
46849
  layer = layerKind;
46369
46850
  label = r.name;
@@ -46375,12 +46856,42 @@ function layoutMap(resolved, data, size, opts) {
46375
46856
  stroke: regionStroke,
46376
46857
  lineNumber,
46377
46858
  layer,
46378
- ...label !== void 0 && { label }
46859
+ ...label !== void 0 && { label },
46860
+ ...isThisLayer && r.score !== void 0 && { score: r.score },
46861
+ ...isThisLayer && Object.keys(r.tags).length > 0 && { tags: r.tags }
46379
46862
  });
46380
46863
  }
46381
46864
  };
46382
- pushRegionLayer(worldLayer, "country");
46383
- if (usLayer) pushRegionLayer(usLayer, "us-state");
46865
+ pushRegionLayer(worldLayer, "country", !isGlobalView);
46866
+ if (usLayer) pushRegionLayer(usLayer, "us-state", !conusFit && !isGlobalView);
46867
+ const lakesTopo = usCrisp && data.naLakes ? data.naLakes : data.lakes;
46868
+ if (lakesTopo) {
46869
+ for (const [, f] of decodeLayer(lakesTopo)) {
46870
+ const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
46871
+ if (!viewF) continue;
46872
+ const d = path(viewF) ?? "";
46873
+ if (!d) continue;
46874
+ regions.push({
46875
+ id: "lake",
46876
+ d,
46877
+ fill: water,
46878
+ stroke: "none",
46879
+ lineNumber: -1,
46880
+ layer: "base"
46881
+ });
46882
+ }
46883
+ }
46884
+ const riverColor = water;
46885
+ const rivers = [];
46886
+ if (data.rivers) {
46887
+ for (const [, f] of decodeLayer(data.rivers)) {
46888
+ const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
46889
+ if (!viewF) continue;
46890
+ const d = path(viewF) ?? "";
46891
+ if (!d) continue;
46892
+ rivers.push({ d, color: riverColor, width: RIVER_WIDTH });
46893
+ }
46894
+ }
46384
46895
  const sizeVals = resolved.pois.map((p) => Number(p.meta["size"])).filter((n) => Number.isFinite(n) && n > 0);
46385
46896
  const sizeMin = sizeVals.length ? Math.min(...sizeVals) : 0;
46386
46897
  const sizeMax = sizeVals.length ? Math.max(...sizeVals) : 0;
@@ -46401,8 +46912,8 @@ function layoutMap(resolved, data, size, opts) {
46401
46912
  if (hex) return { fill: hex, stroke: mix(hex, palette.text, 18) };
46402
46913
  }
46403
46914
  return {
46404
- fill: palette.accent,
46405
- stroke: mix(palette.accent, palette.text, 18)
46915
+ fill: palette.colors.orange,
46916
+ stroke: mix(palette.colors.orange, palette.text, 18)
46406
46917
  };
46407
46918
  };
46408
46919
  const routeNumberById = /* @__PURE__ */ new Map();
@@ -46440,7 +46951,7 @@ function layoutMap(resolved, data, size, opts) {
46440
46951
  cy += Math.sin(ang) * COLO_R;
46441
46952
  }
46442
46953
  const { fill: fill2, stroke: stroke2 } = poiFill(e.p);
46443
- poiScreen.set(e.p.id, { cx, cy });
46954
+ poiScreen.set(e.p.id, { cx, cy, r: radiusFor(e.p) });
46444
46955
  const num = routeNumberById.get(e.p.id);
46445
46956
  pois.push({
46446
46957
  id: e.p.id,
@@ -46457,17 +46968,36 @@ function layoutMap(resolved, data, size, opts) {
46457
46968
  });
46458
46969
  }
46459
46970
  const legs = [];
46971
+ const RIM_GAP = 1.5;
46460
46972
  const legPath = (a, b, curved, offset) => {
46461
- if (!curved && offset === 0) return `M${a.cx},${a.cy}L${b.cx},${b.cy}`;
46462
46973
  const mx = (a.cx + b.cx) / 2;
46463
46974
  const my = (a.cy + b.cy) / 2;
46464
46975
  const dx = b.cx - a.cx;
46465
46976
  const dy = b.cy - a.cy;
46466
46977
  const len = Math.hypot(dx, dy) || 1;
46978
+ const trimA = Math.min(a.r + RIM_GAP, len * 0.45);
46979
+ const trimB = Math.min(b.r + RIM_GAP, len * 0.45);
46980
+ if (!curved && offset === 0) {
46981
+ const ux = dx / len;
46982
+ const uy = dy / len;
46983
+ const ax2 = a.cx + ux * trimA;
46984
+ const ay2 = a.cy + uy * trimA;
46985
+ const bx2 = b.cx - ux * trimB;
46986
+ const by2 = b.cy - uy * trimB;
46987
+ return `M${ax2},${ay2}L${bx2},${by2}`;
46988
+ }
46467
46989
  const nx = -dy / len;
46468
46990
  const ny = dx / len;
46469
46991
  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}`;
46992
+ const px = mx + nx * bow;
46993
+ const py = my + ny * bow;
46994
+ const ta = Math.hypot(px - a.cx, py - a.cy) || 1;
46995
+ const tb = Math.hypot(b.cx - px, b.cy - py) || 1;
46996
+ const ax = a.cx + (px - a.cx) / ta * trimA;
46997
+ const ay = a.cy + (py - a.cy) / ta * trimA;
46998
+ const bx = b.cx - (b.cx - px) / tb * trimB;
46999
+ const by = b.cy - (b.cy - py) / tb * trimB;
47000
+ return `M${ax},${ay}Q${px},${py} ${bx},${by}`;
46471
47001
  };
46472
47002
  for (const rt of resolved.routes) {
46473
47003
  const curved = rt.meta["style"] === "arc";
@@ -46478,7 +47008,7 @@ function layoutMap(resolved, data, size, opts) {
46478
47008
  legs.push({
46479
47009
  d: legPath(a, b, curved, 0),
46480
47010
  width: W_MIN,
46481
- color: mix(palette.text, palette.bg, 55),
47011
+ color: mix(palette.text, palette.bg, 72),
46482
47012
  arrow: true,
46483
47013
  lineNumber: rt.lineNumber
46484
47014
  });
@@ -46514,7 +47044,7 @@ function layoutMap(resolved, data, size, opts) {
46514
47044
  legs.push({
46515
47045
  d: legPath(a, b, curved, offset),
46516
47046
  width: widthFor(e),
46517
- color: mix(palette.text, palette.bg, 45),
47047
+ color: mix(palette.text, palette.bg, 66),
46518
47048
  arrow: e.directed,
46519
47049
  lineNumber: e.lineNumber,
46520
47050
  ...e.label !== void 0 && {
@@ -46526,38 +47056,92 @@ function layoutMap(resolved, data, size, opts) {
46526
47056
  });
46527
47057
  }
46528
47058
  const labels = [];
46529
- const pinList = [];
46530
47059
  const obstacles = [];
46531
47060
  const markers = pois.map((p) => ({
46532
47061
  cx: p.cx,
46533
47062
  cy: p.cy,
46534
47063
  r: p.r
46535
47064
  }));
46536
- const collides = (rect) => markers.some((m) => rectCircleOverlap(rect, m)) || obstacles.some((o) => rectsOverlap(rect, o));
47065
+ const legSegments = [];
47066
+ for (const leg of legs) {
47067
+ const m = /^M(-?[\d.]+),(-?[\d.]+)(?:L(-?[\d.]+),(-?[\d.]+)|Q(-?[\d.]+),(-?[\d.]+) (-?[\d.]+),(-?[\d.]+))$/.exec(
47068
+ leg.d
47069
+ );
47070
+ if (!m) continue;
47071
+ const x0 = +m[1];
47072
+ const y0 = +m[2];
47073
+ if (m[3] !== void 0) {
47074
+ legSegments.push([x0, y0, +m[3], +m[4]]);
47075
+ } else {
47076
+ const cx = +m[5];
47077
+ const cy = +m[6];
47078
+ const ex = +m[7];
47079
+ const ey = +m[8];
47080
+ const N = 8;
47081
+ let px = x0;
47082
+ let py = y0;
47083
+ for (let i = 1; i <= N; i++) {
47084
+ const t = i / N;
47085
+ const u = 1 - t;
47086
+ const qx = u * u * x0 + 2 * u * t * cx + t * t * ex;
47087
+ const qy = u * u * y0 + 2 * u * t * cy + t * t * ey;
47088
+ legSegments.push([px, py, qx, qy]);
47089
+ px = qx;
47090
+ py = qy;
47091
+ }
47092
+ }
47093
+ }
47094
+ 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
47095
  const regionLabelMode = resolved.directives.regionLabels ?? "off";
47096
+ const LABEL_PADX = 6;
47097
+ const LABEL_PADY = 3;
47098
+ const labelW = (text) => measureLegendText(text, FONT) + 2 * LABEL_PADX;
47099
+ const labelH = FONT + 2 * LABEL_PADY;
47100
+ const pushRegionLabel = (x, y, text, fill2, lineNumber) => {
47101
+ const color = contrastText(
47102
+ fill2,
47103
+ palette.textOnFillLight,
47104
+ palette.textOnFillDark
47105
+ );
47106
+ const haloColor = color === palette.textOnFillLight ? palette.textOnFillDark : palette.textOnFillLight;
47107
+ labels.push({
47108
+ x,
47109
+ y,
47110
+ text,
47111
+ anchor: "middle",
47112
+ color,
47113
+ halo: true,
47114
+ haloColor,
47115
+ lineNumber
47116
+ });
47117
+ };
47118
+ const WORLD_LABEL_ANCHORS = {
47119
+ US: [-98.5, 39.5]
47120
+ // CONUS geographic centre (near Lebanon, Kansas)
47121
+ };
46538
47122
  if (regionLabelMode === "full" || regionLabelMode === "abbrev") {
46539
47123
  for (const r of regions) {
46540
47124
  if (r.layer === "base" || r.label === void 0) continue;
46541
47125
  const f = r.layer === "us-state" ? usLayer?.get(r.id) : worldLayer.get(r.id);
46542
47126
  if (!f) continue;
46543
47127
  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
47128
  const text = regionLabelMode === "abbrev" ? r.id.replace(/^US-/, "") : r.label;
46548
- labels.push({
46549
- x: c[0],
46550
- y: c[1],
47129
+ if (labelW(text) > x1 - x0 || labelH > y1 - y0) continue;
47130
+ const anchor = r.layer !== "us-state" ? WORLD_LABEL_ANCHORS[r.id] : void 0;
47131
+ const c = anchor ? project(anchor[0], anchor[1]) : path.centroid(f);
47132
+ if (!c || !Number.isFinite(c[0])) continue;
47133
+ pushRegionLabel(c[0], c[1], text, r.fill, r.lineNumber);
47134
+ }
47135
+ for (const seed of insetLabelSeeds) {
47136
+ const text = regionLabelMode === "abbrev" ? seed.iso.replace(/^US-/, "") : seed.name;
47137
+ const src = regionById.get(seed.iso);
47138
+ pushRegionLabel(
47139
+ seed.x,
47140
+ seed.y,
46551
47141
  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
- });
47142
+ src ? regionFill(src) : neutralFill,
47143
+ seed.lineNumber
47144
+ );
46561
47145
  }
46562
47146
  }
46563
47147
  const poiLabelMode = resolved.directives.poiLabels ?? "auto";
@@ -46570,68 +47154,106 @@ function layoutMap(resolved, data, size, opts) {
46570
47154
  const src = poiById.get(p.id);
46571
47155
  return src?.label ?? src?.name ?? p.id;
46572
47156
  };
46573
- let pinCounter = 0;
46574
- for (const p of ordered) {
47157
+ const poiLabH = FONT * 1.25;
47158
+ const labelInfo = (p) => {
46575
47159
  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);
47160
+ return { text, w: measureLegendText(text, FONT) };
47161
+ };
47162
+ const pushInline = (p, text, w, side) => {
47163
+ const tx = side === "right" ? p.cx + p.r + 3 : p.cx - p.r - 3;
47164
+ obstacles.push({
47165
+ x: side === "right" ? tx : tx - w,
47166
+ y: p.cy - poiLabH / 2,
47167
+ w,
47168
+ h: poiLabH
47169
+ });
47170
+ labels.push({
47171
+ x: tx,
47172
+ y: p.cy + FONT / 3,
47173
+ text,
47174
+ anchor: side === "right" ? "start" : "end",
47175
+ color: palette.text,
47176
+ halo: true,
47177
+ haloColor: palette.bg,
47178
+ poiId: p.id,
47179
+ lineNumber: p.lineNumber
47180
+ });
47181
+ };
47182
+ const inlineFits = (p, w, side) => {
47183
+ const tx = side === "right" ? p.cx + p.r + 3 : p.cx - p.r - 3;
47184
+ const rect = {
47185
+ x: side === "right" ? tx : tx - w,
47186
+ y: p.cy - poiLabH / 2,
47187
+ w,
47188
+ h: poiLabH
47189
+ };
47190
+ return rect.x >= 0 && rect.x + rect.w <= width && !collides(rect);
47191
+ };
47192
+ const GROUP_R = 30;
47193
+ const groups = [];
47194
+ for (const p of ordered) {
47195
+ const near = groups.find(
47196
+ (g) => g.some((q) => Math.hypot(q.cx - p.cx, q.cy - p.cy) < GROUP_R)
47197
+ );
47198
+ if (near) near.push(p);
47199
+ else groups.push([p]);
47200
+ }
47201
+ const ROW_GAP2 = 3;
47202
+ const step = poiLabH + ROW_GAP2;
47203
+ const COL_GAP = 16;
47204
+ const placeColumn = (group) => {
47205
+ const items = group.map((p) => ({ p, ...labelInfo(p) })).sort((a, b) => a.p.cy - b.p.cy || (a.text < b.text ? -1 : 1));
47206
+ const left = Math.min(...items.map((o) => o.p.cx - o.p.r));
47207
+ const right = Math.max(...items.map((o) => o.p.cx + o.p.r));
47208
+ const cyMid = (Math.min(...items.map((o) => o.p.cy)) + Math.max(...items.map((o) => o.p.cy))) / 2;
47209
+ const maxW = Math.max(...items.map((o) => o.w));
47210
+ const side = right + COL_GAP + maxW <= width - 2 ? "right" : "left";
47211
+ const colX = side === "right" ? right + COL_GAP : left - COL_GAP;
47212
+ const totalH = items.length * step;
47213
+ let startY = cyMid - totalH / 2;
47214
+ startY = Math.max(2, Math.min(startY, height - totalH - 2));
47215
+ items.forEach((o, i) => {
47216
+ const rowCy = startY + i * step + step / 2;
47217
+ obstacles.push({
47218
+ x: side === "right" ? colX : colX - o.w,
47219
+ y: rowCy - poiLabH / 2,
47220
+ w: o.w,
47221
+ h: poiLabH
47222
+ });
46581
47223
  labels.push({
46582
- x: inline.x,
46583
- y: p.cy + FONT / 3,
46584
- text,
46585
- anchor: "start",
47224
+ x: colX,
47225
+ y: rowCy + FONT / 3,
47226
+ text: o.text,
47227
+ anchor: side === "right" ? "start" : "end",
46586
47228
  color: palette.text,
46587
47229
  halo: true,
46588
- lineNumber: p.lineNumber
47230
+ haloColor: palette.bg,
47231
+ leader: {
47232
+ x1: o.p.cx,
47233
+ y1: o.p.cy,
47234
+ x2: side === "right" ? colX - 2 : colX + 2,
47235
+ y2: rowCy
47236
+ },
47237
+ leaderColor: o.p.fill,
47238
+ poiId: o.p.id,
47239
+ lineNumber: o.p.lineNumber
46589
47240
  });
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;
47241
+ });
47242
+ };
47243
+ for (const g of groups) {
47244
+ if (g.length === 1) {
47245
+ const p = g[0];
47246
+ const { text, w } = labelInfo(p);
47247
+ if (inlineFits(p, w, "right")) {
47248
+ pushInline(p, text, w, "right");
47249
+ continue;
47250
+ }
47251
+ if (inlineFits(p, w, "left")) {
47252
+ pushInline(p, text, w, "left");
47253
+ continue;
46620
47254
  }
46621
47255
  }
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
- });
47256
+ placeColumn(g);
46635
47257
  }
46636
47258
  }
46637
47259
  let legend = null;
@@ -46640,8 +47262,7 @@ function layoutMap(resolved, data, size, opts) {
46640
47262
  name: g.name,
46641
47263
  entries: g.entries.map((e) => ({ value: e.value, color: e.color }))
46642
47264
  }));
46643
- const hasAnything = tagGroups.length > 0 || hasRamp || sizeVals.length > 0 || weightVals.length > 0;
46644
- if (hasAnything) {
47265
+ if (tagGroups.length > 0 || hasRamp) {
46645
47266
  legend = {
46646
47267
  tagGroups,
46647
47268
  activeGroup,
@@ -46652,20 +47273,9 @@ function layoutMap(resolved, data, size, opts) {
46652
47273
  },
46653
47274
  min: rampMin,
46654
47275
  max: rampMax,
46655
- hue: rampHue
47276
+ hue: rampHue,
47277
+ base: rampBase
46656
47278
  }
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
47279
  }
46670
47280
  };
46671
47281
  }
@@ -46673,28 +47283,30 @@ function layoutMap(resolved, data, size, opts) {
46673
47283
  return {
46674
47284
  width,
46675
47285
  height,
46676
- background: palette.bg,
47286
+ background: water,
46677
47287
  title: resolved.title,
46678
47288
  ...resolved.subtitle !== void 0 && { subtitle: resolved.subtitle },
46679
47289
  ...resolved.caption !== void 0 && { caption: resolved.caption },
46680
47290
  regions,
47291
+ rivers,
46681
47292
  legs,
46682
47293
  pois,
46683
47294
  labels,
46684
- pinList,
46685
- legend
47295
+ legend,
47296
+ insets,
47297
+ insetRegions
46686
47298
  };
46687
47299
  }
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;
47300
+ 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
47301
  var init_layout15 = __esm({
46690
47302
  "src/map/layout.ts"() {
46691
47303
  "use strict";
46692
47304
  import_d3_geo2 = require("d3-geo");
46693
47305
  import_topojson_client2 = require("topojson-client");
46694
47306
  init_color_utils();
46695
- init_tag_groups();
46696
47307
  init_label_layout();
46697
47308
  init_legend_constants();
47309
+ init_title_constants();
46698
47310
  FIT_PAD = 24;
46699
47311
  RAMP_FLOOR = 15;
46700
47312
  R_DEFAULT = 6;
@@ -46703,23 +47315,32 @@ var init_layout15 = __esm({
46703
47315
  W_MIN = 1.25;
46704
47316
  W_MAX = 8;
46705
47317
  FONT = 11;
46706
- LEADER_STEP = 14;
46707
47318
  COLO_EPS = 1.5;
47319
+ LAND_TINT_LIGHT = 58;
47320
+ LAND_TINT_DARK = 75;
47321
+ TAG_TINT_LIGHT = 60;
47322
+ TAG_TINT_DARK = 68;
47323
+ WATER_TINT = 55;
47324
+ RIVER_WIDTH = 1.3;
47325
+ FOREIGN_TINT_LIGHT = 30;
47326
+ FOREIGN_TINT_DARK = 62;
46708
47327
  COLO_R = 9;
46709
47328
  GOLDEN_ANGLE = 2.399963229728653;
46710
47329
  FAN_STEP = 16;
46711
- TINY_REGION_AREA = 600;
46712
47330
  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
- ];
47331
+ usConusProjection = () => (0, import_d3_geo2.geoConicEqualArea)().parallels([29.5, 45.5]).rotate([96, 0]);
47332
+ alaskaProjection = () => (0, import_d3_geo2.geoConicEqualArea)().rotate([154, 0]).center([-2, 58.5]).parallels([55, 65]);
47333
+ hawaiiProjection = () => (0, import_d3_geo2.geoMercator)();
47334
+ INSET_STATES = /* @__PURE__ */ new Set(["US-AK", "US-HI"]);
47335
+ US_NON_CONUS = /* @__PURE__ */ new Set([
47336
+ "US-AK",
47337
+ "US-HI",
47338
+ "US-AS",
47339
+ "US-GU",
47340
+ "US-MP",
47341
+ "US-PR",
47342
+ "US-VI"
47343
+ ]);
46723
47344
  }
46724
47345
  });
46725
47346
 
@@ -46729,7 +47350,7 @@ __export(renderer_exports16, {
46729
47350
  renderMap: () => renderMap,
46730
47351
  renderMapForExport: () => renderMapForExport
46731
47352
  });
46732
- function renderMap(container, resolved, data, palette, isDark, onClickItem, exportDims) {
47353
+ function renderMap(container, resolved, data, palette, isDark, onClickItem, exportDims, activeGroupOverride) {
46733
47354
  d3Selection18.select(container).selectAll(":not([data-d3-tooltip])").remove();
46734
47355
  const width = exportDims?.width ?? container.clientWidth;
46735
47356
  const height = exportDims?.height ?? container.clientHeight;
@@ -46740,27 +47361,29 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46740
47361
  { width, height },
46741
47362
  {
46742
47363
  palette,
46743
- isDark
47364
+ isDark,
47365
+ ...activeGroupOverride !== void 0 && {
47366
+ activeGroup: activeGroupOverride
47367
+ }
46744
47368
  }
46745
47369
  );
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);
47370
+ 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
47371
  svg.append("rect").attr("width", width).attr("height", height).attr("fill", layout.background);
46748
- const arrowColor = mix(palette.text, palette.bg, 50);
46749
47372
  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
- }
47373
+ const arrowSize = (w) => Math.min(15, 7 + w * 0.95);
47374
+ const haloColor = palette.bg;
46761
47375
  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);
47376
+ const drawRegion = (g, r, strokeWidth) => {
47377
+ const p = g.append("path").attr("d", r.d).attr("fill", r.fill).attr("stroke", r.stroke).attr("stroke-width", strokeWidth);
47378
+ if (r.layer !== "base") {
47379
+ p.classed("dgmo-map-region", true).attr("data-region", r.id);
47380
+ if (r.score !== void 0) p.attr("data-score", r.score);
47381
+ if (r.tags) {
47382
+ for (const [group, value] of Object.entries(r.tags)) {
47383
+ p.attr(`data-tag-${group.toLowerCase()}`, value.toLowerCase());
47384
+ }
47385
+ }
47386
+ }
46764
47387
  if (r.lineNumber >= 0) {
46765
47388
  p.attr("data-line-number", r.lineNumber);
46766
47389
  if (onClickItem) {
@@ -46770,11 +47393,31 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46770
47393
  );
46771
47394
  }
46772
47395
  }
47396
+ };
47397
+ for (const r of layout.regions) drawRegion(gRegions, r, 0.5);
47398
+ if (layout.rivers.length) {
47399
+ const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none");
47400
+ for (const r of layout.rivers) {
47401
+ gRivers.append("path").attr("d", r.d).attr("stroke", r.color).attr("stroke-width", r.width).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
47402
+ }
47403
+ }
47404
+ if (layout.insets.length) {
47405
+ const insetG = svg.append("g").attr("class", "dgmo-map-insets");
47406
+ for (const box of layout.insets) {
47407
+ const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
47408
+ 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");
47409
+ }
47410
+ for (const r of layout.insetRegions) drawRegion(insetG, r, 0.5);
46773
47411
  }
46774
47412
  const gLegs = svg.append("g").attr("class", "dgmo-map-legs").attr("fill", "none");
46775
- for (const leg of layout.legs) {
47413
+ layout.legs.forEach((leg, i) => {
46776
47414
  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)");
47415
+ if (leg.arrow) {
47416
+ const id = `dgmo-map-arrow-${i}`;
47417
+ const s = arrowSize(leg.width);
47418
+ 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);
47419
+ p.attr("marker-end", `url(#${id})`);
47420
+ }
46778
47421
  if (leg.label !== void 0 && leg.labelX !== void 0) {
46779
47422
  emitText(
46780
47423
  gLegs,
@@ -46788,13 +47431,13 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46788
47431
  LABEL_FONT - 1
46789
47432
  );
46790
47433
  }
46791
- }
47434
+ });
46792
47435
  const gPois = svg.append("g").attr("class", "dgmo-map-pois");
46793
47436
  for (const poi of layout.pois) {
46794
47437
  if (poi.isOrigin) {
46795
47438
  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
47439
  }
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);
47440
+ 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
47441
  if (onClickItem) {
46799
47442
  c.style("cursor", "pointer").on(
46800
47443
  "click",
@@ -46818,48 +47461,66 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46818
47461
  const gLabels = svg.append("g").attr("class", "dgmo-map-labels");
46819
47462
  for (const lab of layout.labels) {
46820
47463
  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);
47464
+ 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(
47465
+ "stroke",
47466
+ lab.leaderColor ?? mix(palette.textMuted, palette.bg, 60)
47467
+ ).attr("stroke-width", lab.leaderColor ? 1 : 0.75);
47468
+ if (lab.poiId !== void 0) line12.attr("data-poi", lab.poiId);
46822
47469
  }
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(
47470
+ const t = emitText(
46827
47471
  gLabels,
46828
47472
  lab.x,
46829
47473
  lab.y,
46830
47474
  lab.text,
46831
47475
  lab.anchor,
46832
47476
  lab.color,
46833
- haloColor,
47477
+ lab.haloColor,
46834
47478
  lab.halo,
46835
47479
  LABEL_FONT
46836
47480
  );
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
- });
47481
+ if (lab.poiId !== void 0) {
47482
+ t.attr("data-poi", lab.poiId).style("cursor", "default");
47483
+ }
46846
47484
  }
46847
47485
  if (layout.legend) {
46848
47486
  const legendY = (layout.title ? TITLE_Y + TITLE_FONT_SIZE : 0) + (layout.subtitle ? TITLE_FONT_SIZE : 0) + 8;
46849
47487
  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);
47488
+ const ramp = layout.legend.ramp;
47489
+ const scoreGroup = ramp ? {
47490
+ name: ramp.metric?.trim() || "Score",
47491
+ entries: [],
47492
+ gradient: {
47493
+ min: ramp.min,
47494
+ max: ramp.max,
47495
+ hue: ramp.hue,
47496
+ base: ramp.base
47497
+ }
47498
+ } : null;
47499
+ const tagGroups = layout.legend.tagGroups.filter((g) => g.entries.length > 0).map((g) => ({ name: g.name, entries: [...g.entries] }));
47500
+ const groups = [...scoreGroup ? [scoreGroup] : [], ...tagGroups];
46851
47501
  if (groups.length > 0) {
46852
47502
  const config = {
46853
- groups: groups.map((g) => ({ name: g.name, entries: [...g.entries] })),
47503
+ groups,
46854
47504
  position: { placement: "top-center", titleRelation: "below-title" },
46855
47505
  mode: exportDims ? "export" : "preview",
46856
- showEmptyGroups: false
47506
+ showEmptyGroups: false,
47507
+ // Keep inactive siblings visible as pills so the user can click to flip
47508
+ // the active colouring dimension (preview only — export shows just the
47509
+ // active group).
47510
+ showInactivePills: true
46857
47511
  };
46858
47512
  const state = { activeGroup: layout.legend.activeGroup };
46859
47513
  renderLegendD3(legendG, config, state, palette, isDark, void 0, width);
46860
47514
  }
46861
- const pinGap = layout.pinList.length ? layout.pinList.length * 14 + 14 : 0;
46862
- emitExtraLegend(svg, layout, palette, height, pinGap);
47515
+ }
47516
+ if (layout.title) {
47517
+ 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);
47518
+ }
47519
+ if (layout.subtitle) {
47520
+ 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);
47521
+ }
47522
+ if (layout.caption) {
47523
+ 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
47524
  }
46864
47525
  }
46865
47526
  function renderMapForExport(container, resolved, data, palette, isDark, exportDims) {
@@ -46870,54 +47531,7 @@ function emitText(g, x, y, text, anchor, color, halo, withHalo, fontSize) {
46870
47531
  if (withHalo) {
46871
47532
  t.attr("paint-order", "stroke fill").attr("stroke", halo).attr("stroke-width", 3).attr("stroke-linejoin", "round").attr("stroke-opacity", 0.7);
46872
47533
  }
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();
47534
+ return t;
46921
47535
  }
46922
47536
  var d3Selection18, LABEL_FONT;
46923
47537
  var init_renderer16 = __esm({
@@ -56038,6 +56652,8 @@ __export(internal_exports, {
56038
56652
  looksLikeSitemap: () => looksLikeSitemap,
56039
56653
  looksLikeState: () => looksLikeState,
56040
56654
  makeDgmoError: () => makeDgmoError,
56655
+ mapBackgroundColor: () => mapBackgroundColor,
56656
+ mapNeutralLandColor: () => mapNeutralLandColor,
56041
56657
  matchesContiguously: () => matchesContiguously,
56042
56658
  measurePertAnalysisBlock: () => measurePertAnalysisBlock,
56043
56659
  migrateContent: () => migrateContent,
@@ -57822,10 +58438,13 @@ var COMPLETION_REGISTRY = /* @__PURE__ */ new Map([
57822
58438
  // keywords, not directives; metadata keys (score/size/label) live in the
57823
58439
  // reserved-key registry.
57824
58440
  withGlobals({
57825
- region: { description: "Force a basemap/extent (world | us-states)" },
58441
+ region: {
58442
+ description: "Basemap: us-states (force US state mesh + scoping) | world (inert \u2014 already the default)",
58443
+ values: ["us-states", "world"]
58444
+ },
57826
58445
  projection: {
57827
58446
  description: "Override the auto projection",
57828
- values: ["natural-earth", "albers-usa", "mercator"]
58447
+ values: ["equirectangular", "natural-earth", "albers-usa", "mercator"]
57829
58448
  },
57830
58449
  metric: { description: "Label for the region score ramp" },
57831
58450
  "size-metric": { description: "Label for the POI size channel" },
@@ -59413,6 +60032,8 @@ function formatLineDiff(path, original, migrated) {
59413
60032
  looksLikeSitemap,
59414
60033
  looksLikeState,
59415
60034
  makeDgmoError,
60035
+ mapBackgroundColor,
60036
+ mapNeutralLandColor,
59416
60037
  matchesContiguously,
59417
60038
  measurePertAnalysisBlock,
59418
60039
  migrateContent,