@diagrammo/dgmo 0.21.0 → 0.21.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 (43) hide show
  1. package/dist/advanced.cjs +556 -195
  2. package/dist/advanced.js +555 -195
  3. package/dist/auto.cjs +322 -196
  4. package/dist/auto.js +113 -113
  5. package/dist/auto.mjs +322 -196
  6. package/dist/cli.cjs +156 -156
  7. package/dist/editor.cjs +1 -0
  8. package/dist/editor.js +1 -0
  9. package/dist/highlight.cjs +1 -0
  10. package/dist/highlight.js +1 -0
  11. package/dist/index.cjs +320 -195
  12. package/dist/index.js +320 -195
  13. package/dist/internal.cjs +556 -195
  14. package/dist/internal.js +555 -195
  15. package/dist/map-data/PROVENANCE.json +1 -1
  16. package/dist/map-data/mountain-ranges.json +1 -0
  17. package/docs/language-reference.md +27 -25
  18. package/gallery/fixtures/map-direct-color.dgmo +10 -0
  19. package/package.json +1 -1
  20. package/src/advanced.ts +14 -0
  21. package/src/completion.ts +1 -0
  22. package/src/d3.ts +15 -9
  23. package/src/editor/keywords.ts +1 -0
  24. package/src/map/data/PROVENANCE.json +1 -1
  25. package/src/map/data/mountain-ranges.json +1 -0
  26. package/src/map/geo-query.ts +277 -0
  27. package/src/map/geo.ts +258 -1
  28. package/src/map/invert.ts +111 -0
  29. package/src/map/layout.ts +233 -113
  30. package/src/map/load-data.ts +7 -1
  31. package/src/map/parser.ts +22 -2
  32. package/src/map/renderer.ts +44 -0
  33. package/src/map/resolved-types.ts +8 -0
  34. package/src/map/resolver.ts +40 -19
  35. package/src/map/types.ts +18 -0
  36. package/dist/advanced.d.cts +0 -5331
  37. package/dist/advanced.d.ts +0 -5331
  38. package/dist/auto.d.cts +0 -39
  39. package/dist/auto.d.ts +0 -39
  40. package/dist/index.d.cts +0 -336
  41. package/dist/index.d.ts +0 -336
  42. package/dist/internal.d.cts +0 -5331
  43. package/dist/internal.d.ts +0 -5331
package/dist/auto.mjs CHANGED
@@ -15891,10 +15891,13 @@ function parseMap(content) {
15891
15891
  );
15892
15892
  d.projection = value;
15893
15893
  break;
15894
- case "region-metric":
15894
+ case "region-metric": {
15895
15895
  dup(d.regionMetric);
15896
- d.regionMetric = value;
15896
+ const { label: rmLabel, colorName: rmColor } = peelTrailingColorName(value);
15897
+ d.regionMetric = rmLabel;
15898
+ if (rmColor) d.regionMetricColor = rmColor;
15897
15899
  break;
15900
+ }
15898
15901
  case "poi-metric":
15899
15902
  dup(d.poiMetric);
15900
15903
  d.poiMetric = value;
@@ -15943,6 +15946,12 @@ function parseMap(content) {
15943
15946
  case "no-legend":
15944
15947
  d.noLegend = true;
15945
15948
  break;
15949
+ case "no-insets":
15950
+ d.noInsets = true;
15951
+ break;
15952
+ case "relief":
15953
+ d.relief = true;
15954
+ break;
15946
15955
  case "muted":
15947
15956
  case "natural":
15948
15957
  if (d.basemapStyle !== void 0 && d.basemapStyle !== key)
@@ -16055,6 +16064,7 @@ function parseMap(content) {
16055
16064
  };
16056
16065
  if (regionScope !== void 0) region.scope = regionScope;
16057
16066
  if (valueNum !== void 0) region.value = valueNum;
16067
+ if (split.color) region.color = split.color;
16058
16068
  regions.push(region);
16059
16069
  }
16060
16070
  function handlePoi(rest, line12, indent) {
@@ -16079,6 +16089,7 @@ function parseMap(content) {
16079
16089
  const poi = { pos, tags, meta, lineNumber: line12 };
16080
16090
  if (split.alias) poi.alias = split.alias;
16081
16091
  if (label !== void 0) poi.label = label;
16092
+ if (split.color) poi.color = split.color;
16082
16093
  pois.push(poi);
16083
16094
  open.poi = { poi, indent };
16084
16095
  }
@@ -16283,6 +16294,8 @@ var init_parser12 = __esm({
16283
16294
  "default-state",
16284
16295
  "active-tag",
16285
16296
  "no-legend",
16297
+ "no-insets",
16298
+ "relief",
16286
16299
  "subtitle",
16287
16300
  "caption"
16288
16301
  ]);
@@ -45481,7 +45494,7 @@ var init_renderer15 = __esm({
45481
45494
 
45482
45495
  // src/map/geo.ts
45483
45496
  import { feature } from "topojson-client";
45484
- import { geoBounds } from "d3-geo";
45497
+ import { geoBounds, geoArea } from "d3-geo";
45485
45498
  function geomObject(topo) {
45486
45499
  const key = Object.keys(topo.objects)[0];
45487
45500
  return topo.objects[key];
@@ -45509,6 +45522,74 @@ function featureBbox(topo, geomId) {
45509
45522
  [b[1][0], b[1][1]]
45510
45523
  ];
45511
45524
  }
45525
+ function explodePolygons(gj) {
45526
+ const g = gj.geometry ?? gj;
45527
+ const t = g.type;
45528
+ const coords = g.coordinates;
45529
+ if (t === "Polygon") {
45530
+ return [
45531
+ { type: "Feature", geometry: { type: "Polygon", coordinates: coords } }
45532
+ ];
45533
+ }
45534
+ if (t === "MultiPolygon") {
45535
+ return coords.map((rings) => ({
45536
+ type: "Feature",
45537
+ geometry: { type: "Polygon", coordinates: rings }
45538
+ }));
45539
+ }
45540
+ return [];
45541
+ }
45542
+ function bboxGap(a, b) {
45543
+ const lonGap = Math.max(0, a[0][0] - b[1][0], b[0][0] - a[1][0]);
45544
+ const latGap = Math.max(0, a[0][1] - b[1][1], b[0][1] - a[1][1]);
45545
+ return Math.max(lonGap, latGap);
45546
+ }
45547
+ function featureBboxPrimary(topo, geomId) {
45548
+ const geom = geomObject(topo).geometries.find((g) => g.id === geomId);
45549
+ if (!geom) return null;
45550
+ const gj = feature(topo, geom);
45551
+ const parts = explodePolygons(gj);
45552
+ if (parts.length <= 1) return featureBbox(topo, geomId);
45553
+ const polys = parts.map((p) => {
45554
+ const b = geoBounds(p);
45555
+ if (!b || !Number.isFinite(b[0][0])) return null;
45556
+ const wraps = b[1][0] < b[0][0];
45557
+ const bbox = [
45558
+ [b[0][0], b[0][1]],
45559
+ [b[1][0], b[1][1]]
45560
+ ];
45561
+ return { bbox, area: geoArea(p), wraps };
45562
+ }).filter(
45563
+ (p) => p !== null
45564
+ );
45565
+ if (polys.length <= 1 || polys.some((p) => p.wraps))
45566
+ return featureBbox(topo, geomId);
45567
+ const maxArea = Math.max(...polys.map((p) => p.area));
45568
+ const anchor = polys.find((p) => p.area === maxArea);
45569
+ const cluster = [
45570
+ [anchor.bbox[0][0], anchor.bbox[0][1]],
45571
+ [anchor.bbox[1][0], anchor.bbox[1][1]]
45572
+ ];
45573
+ const remaining = polys.filter((p) => p !== anchor);
45574
+ let added = true;
45575
+ while (added) {
45576
+ added = false;
45577
+ for (let i = remaining.length - 1; i >= 0; i--) {
45578
+ const p = remaining[i];
45579
+ const near = bboxGap(p.bbox, cluster) <= DETACH_GAP_DEG;
45580
+ const large = p.area >= DETACH_AREA_FRAC * maxArea;
45581
+ if (near || large) {
45582
+ cluster[0][0] = Math.min(cluster[0][0], p.bbox[0][0]);
45583
+ cluster[0][1] = Math.min(cluster[0][1], p.bbox[0][1]);
45584
+ cluster[1][0] = Math.max(cluster[1][0], p.bbox[1][0]);
45585
+ cluster[1][1] = Math.max(cluster[1][1], p.bbox[1][1]);
45586
+ remaining.splice(i, 1);
45587
+ added = true;
45588
+ }
45589
+ }
45590
+ }
45591
+ return cluster;
45592
+ }
45512
45593
  function unionExtent(boxes, points) {
45513
45594
  const lats = [];
45514
45595
  const lons = [];
@@ -45547,11 +45628,13 @@ function unionLongitudes(lons) {
45547
45628
  }
45548
45629
  return { west: pts[gapIdx], east: pts[gapIdx - 1] + 360 };
45549
45630
  }
45550
- var fold;
45631
+ var fold, DETACH_GAP_DEG, DETACH_AREA_FRAC;
45551
45632
  var init_geo = __esm({
45552
45633
  "src/map/geo.ts"() {
45553
45634
  "use strict";
45554
45635
  fold = (s) => s.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase().trim();
45636
+ DETACH_GAP_DEG = 10;
45637
+ DETACH_AREA_FRAC = 0.25;
45555
45638
  }
45556
45639
  });
45557
45640
 
@@ -45655,12 +45738,12 @@ function resolveMap(parsed, data) {
45655
45738
  chosen = { ...inState, layer: "us-state" };
45656
45739
  } else {
45657
45740
  chosen = { ...inCountry, layer: "country" };
45741
+ warn2(
45742
+ r.lineNumber,
45743
+ `"${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}").`,
45744
+ "W_MAP_REGION_AMBIGUOUS"
45745
+ );
45658
45746
  }
45659
- warn2(
45660
- r.lineNumber,
45661
- `"${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}").`,
45662
- "W_MAP_REGION_AMBIGUOUS"
45663
- );
45664
45747
  } else if (inState) {
45665
45748
  chosen = { ...inState, layer: "us-state" };
45666
45749
  } else if (inCountry) {
@@ -45684,6 +45767,7 @@ function resolveMap(parsed, data) {
45684
45767
  name: chosen.name,
45685
45768
  layer: chosen.layer,
45686
45769
  ...r.value !== void 0 && { value: r.value },
45770
+ ...r.color !== void 0 && { color: r.color },
45687
45771
  tags: r.tags,
45688
45772
  meta: r.meta,
45689
45773
  lineNumber: r.lineNumber
@@ -45825,6 +45909,7 @@ function resolveMap(parsed, data) {
45825
45909
  lat,
45826
45910
  lon,
45827
45911
  ...p.label !== void 0 && { label: p.label },
45912
+ ...p.color !== void 0 && { color: p.color },
45828
45913
  tags: p.tags,
45829
45914
  meta: p.meta,
45830
45915
  lineNumber: p.lineNumber
@@ -45967,7 +46052,7 @@ function resolveMap(parsed, data) {
45967
46052
  }
45968
46053
  for (const r of regions) {
45969
46054
  if (r.layer === "country") {
45970
- const bb = featureBbox(data.worldCoarse, r.iso);
46055
+ const bb = featureBboxPrimary(data.worldCoarse, r.iso);
45971
46056
  if (bb) regionBoxes.push(bb);
45972
46057
  }
45973
46058
  }
@@ -45981,6 +46066,7 @@ function resolveMap(parsed, data) {
45981
46066
  const lonSpan = extent2[1][0] - extent2[0][0];
45982
46067
  const latSpan = extent2[1][1] - extent2[0][1];
45983
46068
  const span = Math.max(lonSpan, latSpan);
46069
+ const maxAbsLat = Math.max(Math.abs(extent2[0][1]), Math.abs(extent2[1][1]));
45984
46070
  const usDominant = (subdivisions.includes("us-states") || regions.some((r) => r.layer === "us-state")) && !regions.some((r) => r.layer === "country" && r.iso !== "US") && !anyNonUsPoi;
45985
46071
  let projection;
45986
46072
  const override = parsed.directives.projection;
@@ -45988,12 +46074,10 @@ function resolveMap(parsed, data) {
45988
46074
  projection = override;
45989
46075
  } else if (usDominant) {
45990
46076
  projection = "albers-usa";
45991
- } else if (span > WORLD_SPAN) {
46077
+ } else if (span > WORLD_SPAN || maxAbsLat > MERCATOR_MAX_LAT) {
45992
46078
  projection = "equirectangular";
45993
- } else if (span < MERCATOR_MAX_SPAN) {
45994
- projection = "mercator";
45995
46079
  } else {
45996
- projection = "equirectangular";
46080
+ projection = "mercator";
45997
46081
  }
45998
46082
  if (lonSpan >= 180) {
45999
46083
  extent2 = [
@@ -46047,14 +46131,14 @@ function firstError(diags) {
46047
46131
  const e = diags.find((d) => d.severity === "error");
46048
46132
  return e ? formatDgmoError(e) : null;
46049
46133
  }
46050
- var WORLD_SPAN, MERCATOR_MAX_SPAN, PAD_FRACTION, WORLD_LAT_SOUTH, WORLD_LAT_NORTH, REGION_ALIASES, US_STATE_POSTAL;
46134
+ var WORLD_SPAN, MERCATOR_MAX_LAT, PAD_FRACTION, WORLD_LAT_SOUTH, WORLD_LAT_NORTH, REGION_ALIASES, US_STATE_POSTAL;
46051
46135
  var init_resolver2 = __esm({
46052
46136
  "src/map/resolver.ts"() {
46053
46137
  "use strict";
46054
46138
  init_diagnostics();
46055
46139
  init_geo();
46056
46140
  WORLD_SPAN = 90;
46057
- MERCATOR_MAX_SPAN = 25;
46141
+ MERCATOR_MAX_LAT = 80;
46058
46142
  PAD_FRACTION = 0.05;
46059
46143
  WORLD_LAT_SOUTH = -58;
46060
46144
  WORLD_LAT_NORTH = 78;
@@ -46135,114 +46219,6 @@ var init_resolver2 = __esm({
46135
46219
  }
46136
46220
  });
46137
46221
 
46138
- // src/map/load-data.ts
46139
- var load_data_exports = {};
46140
- __export(load_data_exports, {
46141
- loadMapData: () => loadMapData
46142
- });
46143
- async function loadNodeBuiltins() {
46144
- const [{ readFile }, { fileURLToPath }, { dirname, resolve }] = await Promise.all([
46145
- import("fs/promises"),
46146
- import("url"),
46147
- import("path")
46148
- ]);
46149
- return { readFile, fileURLToPath, dirname, resolve };
46150
- }
46151
- async function readJson(nb, dir, name) {
46152
- return JSON.parse(await nb.readFile(nb.resolve(dir, name), "utf8"));
46153
- }
46154
- async function firstExistingDir(nb, baseDir) {
46155
- for (const rel of CANDIDATE_DIRS) {
46156
- const dir = nb.resolve(baseDir, rel);
46157
- try {
46158
- await nb.readFile(nb.resolve(dir, FILES.gazetteer), "utf8");
46159
- return dir;
46160
- } catch {
46161
- }
46162
- }
46163
- throw new Error(
46164
- `map data assets not found near ${baseDir} (looked in ${CANDIDATE_DIRS.join(", ")}). Run \`pnpm build:map-data\` and \`pnpm build\`.`
46165
- );
46166
- }
46167
- function validate(data) {
46168
- const topoOk = (t) => !!t && t.type === "Topology" && !!t.objects;
46169
- if (!topoOk(data.worldCoarse) || !topoOk(data.worldDetail) || !topoOk(data.usStates) || !data.gazetteer || !Array.isArray(data.gazetteer.cities) || !data.gazetteer.byName) {
46170
- throw new Error("map data assets are malformed (failed shape validation)");
46171
- }
46172
- return data;
46173
- }
46174
- function moduleBaseDir(nb) {
46175
- try {
46176
- const url = import.meta.url;
46177
- if (url) return nb.dirname(nb.fileURLToPath(url));
46178
- } catch {
46179
- }
46180
- if (typeof __dirname !== "undefined") return __dirname;
46181
- return process.cwd();
46182
- }
46183
- function loadMapData() {
46184
- cache ??= (async () => {
46185
- const nb = await loadNodeBuiltins();
46186
- const dir = await firstExistingDir(nb, moduleBaseDir(nb));
46187
- const [
46188
- worldCoarse,
46189
- worldDetail,
46190
- usStates,
46191
- lakes,
46192
- rivers,
46193
- naLand,
46194
- naLakes,
46195
- gazetteer
46196
- ] = await Promise.all([
46197
- readJson(nb, dir, FILES.worldCoarse),
46198
- readJson(nb, dir, FILES.worldDetail),
46199
- readJson(nb, dir, FILES.usStates),
46200
- // Lakes/rivers/NA assets are optional — older bundles may predate them.
46201
- readJson(nb, dir, FILES.lakes).catch(() => void 0),
46202
- readJson(nb, dir, FILES.rivers).catch(() => void 0),
46203
- readJson(nb, dir, FILES.naLand).catch(() => void 0),
46204
- readJson(nb, dir, FILES.naLakes).catch(() => void 0),
46205
- readJson(nb, dir, FILES.gazetteer)
46206
- ]);
46207
- return validate({
46208
- worldCoarse,
46209
- worldDetail,
46210
- usStates,
46211
- gazetteer,
46212
- ...lakes && { lakes },
46213
- ...rivers && { rivers },
46214
- ...naLand && { naLand },
46215
- ...naLakes && { naLakes }
46216
- });
46217
- })().catch((e) => {
46218
- cache = void 0;
46219
- throw e;
46220
- });
46221
- return cache;
46222
- }
46223
- var FILES, CANDIDATE_DIRS, cache;
46224
- var init_load_data = __esm({
46225
- "src/map/load-data.ts"() {
46226
- "use strict";
46227
- FILES = {
46228
- worldCoarse: "world-coarse.json",
46229
- worldDetail: "world-detail.json",
46230
- usStates: "us-states.json",
46231
- lakes: "lakes.json",
46232
- rivers: "rivers.json",
46233
- naLand: "na-land.json",
46234
- naLakes: "na-lakes.json",
46235
- gazetteer: "gazetteer.json"
46236
- };
46237
- CANDIDATE_DIRS = [
46238
- "./data",
46239
- "./map-data",
46240
- "../map-data",
46241
- "../src/map/data"
46242
- ];
46243
- }
46244
- });
46245
-
46246
46222
  // src/map/layout.ts
46247
46223
  import {
46248
46224
  geoPath,
@@ -46279,18 +46255,14 @@ function projectionFor(family) {
46279
46255
  return geoEquirectangular();
46280
46256
  }
46281
46257
  }
46282
- function mapBackgroundColor(palette, isDark = false, dataActive = false) {
46283
- if (dataActive)
46284
- return mix(
46285
- palette.colors.gray,
46286
- palette.bg,
46287
- isDark ? MUTED_WATER_DARK : MUTED_WATER_LIGHT
46288
- );
46289
- return mix(palette.colors.blue, palette.bg, WATER_TINT);
46258
+ function mapBackgroundColor(palette, isDark = false, _dataActive = false) {
46259
+ return mix(
46260
+ palette.colors.blue,
46261
+ palette.bg,
46262
+ isDark ? WATER_TINT_DARK : WATER_TINT_LIGHT
46263
+ );
46290
46264
  }
46291
- function mapNeutralLandColor(palette, isDark, dataActive = false) {
46292
- if (dataActive)
46293
- return isDark ? mix(palette.colors.gray, palette.bg, MUTED_LAND_DARK) : palette.bg;
46265
+ function mapNeutralLandColor(palette, isDark, _dataActive = false) {
46294
46266
  return mix(
46295
46267
  palette.colors.green,
46296
46268
  palette.bg,
@@ -46322,7 +46294,7 @@ function layoutMap(resolved, data, size, opts) {
46322
46294
  const scaleOverride = resolved.directives.scale;
46323
46295
  const rampMin = scaleOverride ? scaleOverride.min : Math.min(...values);
46324
46296
  const rampMax = scaleOverride ? scaleOverride.max : Math.max(...values);
46325
- const rampHue = palette.colors.red;
46297
+ const rampHue = resolveColor(resolved.directives.regionMetricColor ?? "", palette) ?? palette.colors.red;
46326
46298
  const hasRamp = values.length > 0;
46327
46299
  const VALUE_NAME = hasRamp ? resolved.directives.regionMetric?.trim() || "Value" : null;
46328
46300
  const matchColorGroup = (v) => {
@@ -46345,6 +46317,7 @@ function layoutMap(resolved, data, size, opts) {
46345
46317
  const mutedBasemap = resolved.directives.basemapStyle === "muted" ? true : resolved.directives.basemapStyle === "natural" ? false : activeGroup !== null;
46346
46318
  const neutralFill = mapNeutralLandColor(palette, isDark, mutedBasemap);
46347
46319
  const water = mapBackgroundColor(palette, isDark, mutedBasemap);
46320
+ const lakeStroke = mix(regionStroke, water, 45);
46348
46321
  const foreignFill = mix(
46349
46322
  palette.colors.gray,
46350
46323
  palette.bg,
@@ -46374,7 +46347,14 @@ function layoutMap(resolved, data, size, opts) {
46374
46347
  isDark ? TAG_TINT_DARK : TAG_TINT_LIGHT
46375
46348
  );
46376
46349
  };
46350
+ const directFill = (name) => {
46351
+ const hex = name ? resolveColor(name, palette) : null;
46352
+ if (!hex) return null;
46353
+ return mix(hex, palette.bg, isDark ? TAG_TINT_DARK : TAG_TINT_LIGHT);
46354
+ };
46377
46355
  const regionFill = (r) => {
46356
+ const direct = directFill(r.color);
46357
+ if (direct) return direct;
46378
46358
  if (activeIsScore) {
46379
46359
  return r.value !== void 0 ? fillForValue(r.value) : neutralFill;
46380
46360
  }
@@ -46428,6 +46408,7 @@ function layoutMap(resolved, data, size, opts) {
46428
46408
  const fitIsGlobal = fitGB[1][0] - fitGB[0][0] >= 270 || fitGB[1][1] - fitGB[0][1] >= 130;
46429
46409
  let path;
46430
46410
  let project;
46411
+ let stretchParams = null;
46431
46412
  if (fitIsGlobal) {
46432
46413
  const cb = geoPath(projection).bounds(fitTarget);
46433
46414
  const bx0 = cb[0][0];
@@ -46438,6 +46419,7 @@ function layoutMap(resolved, data, size, opts) {
46438
46419
  const oy = fitBox[0][1];
46439
46420
  const sx = cw > 0 ? (fitBox[1][0] - ox) / cw : 1;
46440
46421
  const sy = ch > 0 ? (fitBox[1][1] - oy) / ch : 1;
46422
+ stretchParams = { sx, sy, ox, oy, bx0, by0 };
46441
46423
  const stretch = (x, y) => [
46442
46424
  ox + (x - bx0) * sx,
46443
46425
  oy + (y - by0) * sy
@@ -46469,7 +46451,7 @@ function layoutMap(resolved, data, size, opts) {
46469
46451
  const insets = [];
46470
46452
  const insetRegions = [];
46471
46453
  const insetLabelSeeds = [];
46472
- if (resolved.projection === "albers-usa" && usLayer) {
46454
+ if (resolved.projection === "albers-usa" && usLayer && !resolved.directives.noInsets) {
46473
46455
  const PAD = 8;
46474
46456
  const GAP = 12;
46475
46457
  const yB = height - FIT_PAD;
@@ -46500,38 +46482,14 @@ function layoutMap(resolved, data, size, opts) {
46500
46482
  }
46501
46483
  return y;
46502
46484
  };
46503
- const coastTop = (x0, xr) => {
46485
+ const coastFloor = (x0, xr) => {
46504
46486
  const n = 24;
46505
- const pts = [];
46506
46487
  let maxY = -Infinity;
46507
46488
  for (let i = 0; i <= n; i++) {
46508
- const x = x0 + (xr - x0) * i / n;
46509
- const y = at(x);
46510
- if (y > -Infinity) {
46511
- pts.push([x, y]);
46512
- if (y > maxY) maxY = y;
46513
- }
46514
- }
46515
- if (pts.length === 0) return () => yB - height * 0.42;
46516
- let m = 0;
46517
- if (pts.length >= 2) {
46518
- let sx = 0, sy = 0, sxx = 0, sxy = 0;
46519
- for (const [x, y] of pts) {
46520
- sx += x;
46521
- sy += y;
46522
- sxx += x * x;
46523
- sxy += x * y;
46524
- }
46525
- const den = pts.length * sxx - sx * sx;
46526
- if (den !== 0) m = (pts.length * sxy - sx * sy) / den;
46527
- }
46528
- m = Math.max(-0.35, Math.min(0.35, m));
46529
- let c = -Infinity;
46530
- for (const [x, y] of pts) {
46531
- const need = y - m * x + GAP;
46532
- if (need > c) c = need;
46533
- }
46534
- return (x) => m * x + c;
46489
+ const y = at(x0 + (xr - x0) * i / n);
46490
+ if (y > maxY) maxY = y;
46491
+ }
46492
+ return maxY;
46535
46493
  };
46536
46494
  const placeInset = (iso, proj, boxX, iwReq) => {
46537
46495
  const f = usLayer.get(iso);
@@ -46540,19 +46498,15 @@ function layoutMap(resolved, data, size, opts) {
46540
46498
  const iw = Math.min(iwReq, width - FIT_PAD - x0 - 2 * PAD);
46541
46499
  if (iw < 24) return boxX;
46542
46500
  const xr = x0 + iw + 2 * PAD;
46543
- const top = coastTop(x0, xr);
46544
- const yL = top(x0);
46545
- const yR = top(xr);
46501
+ const floor = coastFloor(x0, xr);
46502
+ const topGuess = floor > -Infinity ? floor + GAP : yB - height * 0.42;
46546
46503
  proj.fitWidth(iw, f);
46547
46504
  const bb = geoPath(proj).bounds(f);
46548
46505
  const sh = Number.isFinite(bb[0][0]) ? bb[1][1] - bb[0][1] : iw;
46549
46506
  const needH = sh + 2 * PAD;
46550
- let topFit = Math.max(yL, yR);
46507
+ let topFit = topGuess;
46551
46508
  const bottom = Math.min(topFit + needH, yB);
46552
46509
  if (bottom - topFit < needH) topFit = bottom - needH;
46553
- const lift = topFit - Math.max(yL, yR);
46554
- const topL = yL + lift;
46555
- const topR = yR + lift;
46556
46510
  proj.fitExtent(
46557
46511
  [
46558
46512
  [x0 + PAD, topFit + PAD],
@@ -46571,15 +46525,18 @@ function layoutMap(resolved, data, size, opts) {
46571
46525
  }
46572
46526
  insets.push({
46573
46527
  x: x0,
46574
- y: Math.min(topL, topR),
46528
+ y: topFit,
46575
46529
  w: xr - x0,
46576
- h: bottom - Math.min(topL, topR),
46530
+ h: bottom - topFit,
46577
46531
  points: [
46578
- [x0, topL],
46579
- [xr, topR],
46532
+ [x0, topFit],
46533
+ [xr, topFit],
46580
46534
  [xr, bottom],
46581
46535
  [x0, bottom]
46582
- ]
46536
+ ],
46537
+ // The FITTED inset projection (just fit to this box) — captured so the
46538
+ // geo-query can invert pixels inside the frame back to AK/HI coords.
46539
+ projection: proj
46583
46540
  });
46584
46541
  insetRegions.push({
46585
46542
  id: iso,
@@ -46749,13 +46706,40 @@ function layoutMap(resolved, data, size, opts) {
46749
46706
  id: "lake",
46750
46707
  d,
46751
46708
  fill: water,
46752
- stroke: "none",
46709
+ stroke: lakeStroke,
46753
46710
  lineNumber: -1,
46754
46711
  layer: "base"
46755
46712
  });
46756
46713
  }
46757
46714
  }
46758
- const riverColor = water;
46715
+ const relief = [];
46716
+ let reliefHatch = null;
46717
+ if (resolved.directives.relief === true && data.mountainRanges) {
46718
+ for (const [, f] of decodeLayer(data.mountainRanges)) {
46719
+ const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
46720
+ if (!viewF) continue;
46721
+ const area2 = path.area(viewF);
46722
+ if (!Number.isFinite(area2) || area2 < RELIEF_MIN_AREA) continue;
46723
+ const box = path.bounds(viewF);
46724
+ if (box[1][0] - box[0][0] < RELIEF_MIN_DIM || box[1][1] - box[0][1] < RELIEF_MIN_DIM)
46725
+ continue;
46726
+ const d = path(viewF) ?? "";
46727
+ if (!d) continue;
46728
+ relief.push({ d });
46729
+ }
46730
+ if (relief.length) {
46731
+ const darkTone = isDark ? palette.bg : palette.text;
46732
+ const lightTone = isDark ? palette.text : palette.bg;
46733
+ const landLum = relativeLuminance(neutralFill);
46734
+ const tone = Math.abs(landLum - relativeLuminance(darkTone)) > 0.04 ? darkTone : lightTone;
46735
+ reliefHatch = {
46736
+ color: mix(tone, neutralFill, RELIEF_HATCH_STRENGTH),
46737
+ spacing: RELIEF_HATCH_SPACING,
46738
+ width: RELIEF_HATCH_WIDTH
46739
+ };
46740
+ }
46741
+ }
46742
+ const riverColor = mix(water, regionStroke, 16);
46759
46743
  const rivers = [];
46760
46744
  if (data.rivers) {
46761
46745
  for (const [, f] of decodeLayer(data.rivers)) {
@@ -46776,6 +46760,9 @@ function layoutMap(resolved, data, size, opts) {
46776
46760
  return R_MIN + Math.max(0, Math.min(1, t)) * (R_MAX - R_MIN);
46777
46761
  };
46778
46762
  const poiFill = (p) => {
46763
+ const directHex = p.color ? resolveColor(p.color, palette) : null;
46764
+ if (directHex)
46765
+ return { fill: directHex, stroke: mix(directHex, palette.text, 18) };
46779
46766
  for (const group of resolved.tagGroups) {
46780
46767
  const val = p.tags[group.name.toLowerCase()];
46781
46768
  if (!val) continue;
@@ -47191,19 +47178,24 @@ function layoutMap(resolved, data, size, opts) {
47191
47178
  ...resolved.caption !== void 0 && { caption: resolved.caption },
47192
47179
  regions,
47193
47180
  rivers,
47181
+ relief,
47182
+ reliefHatch,
47194
47183
  legs,
47195
47184
  pois,
47196
47185
  labels,
47197
47186
  legend,
47198
47187
  insets,
47199
- insetRegions
47188
+ insetRegions,
47189
+ projection,
47190
+ stretch: stretchParams
47200
47191
  };
47201
47192
  }
47202
- var FIT_PAD, RAMP_FLOOR, R_DEFAULT, R_MIN, R_MAX, W_MIN, W_MAX, FONT, COLO_EPS, LAND_TINT_LIGHT, LAND_TINT_DARK, TAG_TINT_LIGHT, TAG_TINT_DARK, WATER_TINT, RIVER_WIDTH, FOREIGN_TINT_LIGHT, FOREIGN_TINT_DARK, MUTED_WATER_LIGHT, MUTED_WATER_DARK, MUTED_FOREIGN_LIGHT, MUTED_FOREIGN_DARK, MUTED_LAND_DARK, COLO_R, GOLDEN_ANGLE, FAN_STEP, ARC_CURVE_FRAC, usConusProjection, alaskaProjection, hawaiiProjection, INSET_STATES, US_NON_CONUS;
47193
+ var FIT_PAD, RAMP_FLOOR, R_DEFAULT, R_MIN, R_MAX, W_MIN, W_MAX, FONT, COLO_EPS, LAND_TINT_LIGHT, LAND_TINT_DARK, TAG_TINT_LIGHT, TAG_TINT_DARK, WATER_TINT_LIGHT, WATER_TINT_DARK, RIVER_WIDTH, RELIEF_MIN_AREA, RELIEF_MIN_DIM, RELIEF_HATCH_SPACING, RELIEF_HATCH_WIDTH, RELIEF_HATCH_STRENGTH, FOREIGN_TINT_LIGHT, FOREIGN_TINT_DARK, MUTED_FOREIGN_LIGHT, MUTED_FOREIGN_DARK, COLO_R, GOLDEN_ANGLE, FAN_STEP, ARC_CURVE_FRAC, usConusProjection, alaskaProjection, hawaiiProjection, INSET_STATES, US_NON_CONUS;
47203
47194
  var init_layout15 = __esm({
47204
47195
  "src/map/layout.ts"() {
47205
47196
  "use strict";
47206
47197
  init_color_utils();
47198
+ init_colors();
47207
47199
  init_label_layout();
47208
47200
  init_legend_constants();
47209
47201
  init_title_constants();
@@ -47216,19 +47208,22 @@ var init_layout15 = __esm({
47216
47208
  W_MAX = 8;
47217
47209
  FONT = 11;
47218
47210
  COLO_EPS = 1.5;
47219
- LAND_TINT_LIGHT = 58;
47220
- LAND_TINT_DARK = 75;
47211
+ LAND_TINT_LIGHT = 12;
47212
+ LAND_TINT_DARK = 24;
47221
47213
  TAG_TINT_LIGHT = 60;
47222
47214
  TAG_TINT_DARK = 68;
47223
- WATER_TINT = 55;
47215
+ WATER_TINT_LIGHT = 13;
47216
+ WATER_TINT_DARK = 14;
47224
47217
  RIVER_WIDTH = 1.3;
47218
+ RELIEF_MIN_AREA = 12;
47219
+ RELIEF_MIN_DIM = 2;
47220
+ RELIEF_HATCH_SPACING = 3;
47221
+ RELIEF_HATCH_WIDTH = 0.25;
47222
+ RELIEF_HATCH_STRENGTH = 32;
47225
47223
  FOREIGN_TINT_LIGHT = 30;
47226
47224
  FOREIGN_TINT_DARK = 62;
47227
- MUTED_WATER_LIGHT = 14;
47228
- MUTED_WATER_DARK = 10;
47229
47225
  MUTED_FOREIGN_LIGHT = 28;
47230
47226
  MUTED_FOREIGN_DARK = 16;
47231
- MUTED_LAND_DARK = 24;
47232
47227
  COLO_R = 9;
47233
47228
  GOLDEN_ANGLE = 2.399963229728653;
47234
47229
  FAN_STEP = 16;
@@ -47301,6 +47296,20 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47301
47296
  }
47302
47297
  };
47303
47298
  for (const r of layout.regions) drawRegion(gRegions, r, 0.5);
47299
+ if (layout.relief.length && layout.reliefHatch) {
47300
+ const h = layout.reliefHatch;
47301
+ const rangeClipId = "dgmo-relief-clip";
47302
+ const landClipId = "dgmo-relief-land";
47303
+ const rangeClip = defs.append("clipPath").attr("id", rangeClipId);
47304
+ for (const s of layout.relief) rangeClip.append("path").attr("d", s.d);
47305
+ const landClip = defs.append("clipPath").attr("id", landClipId);
47306
+ for (const r of layout.regions)
47307
+ if (r.id !== "lake") landClip.append("path").attr("d", r.d);
47308
+ const gRelief = svg.append("g").attr("clip-path", `url(#${landClipId})`).append("g").attr("class", "dgmo-map-relief").attr("clip-path", `url(#${rangeClipId})`).attr("stroke", h.color).attr("stroke-width", h.width).attr("vector-effect", "non-scaling-stroke");
47309
+ for (let y = h.spacing; y < height; y += h.spacing) {
47310
+ gRelief.append("line").attr("x1", 0).attr("y1", y).attr("x2", width).attr("y2", y);
47311
+ }
47312
+ }
47304
47313
  if (layout.rivers.length) {
47305
47314
  const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none");
47306
47315
  for (const r of layout.rivers) {
@@ -47425,7 +47434,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47425
47434
  }
47426
47435
  }
47427
47436
  if (layout.title) {
47428
- 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);
47437
+ svg.append("text").attr("class", "dgmo-map-title").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);
47429
47438
  }
47430
47439
  if (layout.subtitle) {
47431
47440
  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);
@@ -47457,6 +47466,120 @@ var init_renderer16 = __esm({
47457
47466
  }
47458
47467
  });
47459
47468
 
47469
+ // src/map/load-data.ts
47470
+ var load_data_exports = {};
47471
+ __export(load_data_exports, {
47472
+ loadMapData: () => loadMapData
47473
+ });
47474
+ async function loadNodeBuiltins() {
47475
+ const [{ readFile }, { fileURLToPath }, { dirname, resolve }] = await Promise.all([
47476
+ import("fs/promises"),
47477
+ import("url"),
47478
+ import("path")
47479
+ ]);
47480
+ return { readFile, fileURLToPath, dirname, resolve };
47481
+ }
47482
+ async function readJson(nb, dir, name) {
47483
+ return JSON.parse(await nb.readFile(nb.resolve(dir, name), "utf8"));
47484
+ }
47485
+ async function firstExistingDir(nb, baseDir) {
47486
+ for (const rel of CANDIDATE_DIRS) {
47487
+ const dir = nb.resolve(baseDir, rel);
47488
+ try {
47489
+ await nb.readFile(nb.resolve(dir, FILES.gazetteer), "utf8");
47490
+ return dir;
47491
+ } catch {
47492
+ }
47493
+ }
47494
+ throw new Error(
47495
+ `map data assets not found near ${baseDir} (looked in ${CANDIDATE_DIRS.join(", ")}). Run \`pnpm build:map-data\` and \`pnpm build\`.`
47496
+ );
47497
+ }
47498
+ function validate(data) {
47499
+ const topoOk = (t) => !!t && t.type === "Topology" && !!t.objects;
47500
+ if (!topoOk(data.worldCoarse) || !topoOk(data.worldDetail) || !topoOk(data.usStates) || !data.gazetteer || !Array.isArray(data.gazetteer.cities) || !data.gazetteer.byName) {
47501
+ throw new Error("map data assets are malformed (failed shape validation)");
47502
+ }
47503
+ return data;
47504
+ }
47505
+ function moduleBaseDir(nb) {
47506
+ try {
47507
+ const url = import.meta.url;
47508
+ if (url) return nb.dirname(nb.fileURLToPath(url));
47509
+ } catch {
47510
+ }
47511
+ if (typeof __dirname !== "undefined") return __dirname;
47512
+ return process.cwd();
47513
+ }
47514
+ function loadMapData() {
47515
+ cache ??= (async () => {
47516
+ const nb = await loadNodeBuiltins();
47517
+ const dir = await firstExistingDir(nb, moduleBaseDir(nb));
47518
+ const [
47519
+ worldCoarse,
47520
+ worldDetail,
47521
+ usStates,
47522
+ lakes,
47523
+ rivers,
47524
+ mountainRanges,
47525
+ naLand,
47526
+ naLakes,
47527
+ gazetteer
47528
+ ] = await Promise.all([
47529
+ readJson(nb, dir, FILES.worldCoarse),
47530
+ readJson(nb, dir, FILES.worldDetail),
47531
+ readJson(nb, dir, FILES.usStates),
47532
+ // Lakes/rivers/mountain/NA assets are optional — older bundles may predate them.
47533
+ readJson(nb, dir, FILES.lakes).catch(() => void 0),
47534
+ readJson(nb, dir, FILES.rivers).catch(() => void 0),
47535
+ readJson(nb, dir, FILES.mountainRanges).catch(
47536
+ () => void 0
47537
+ ),
47538
+ readJson(nb, dir, FILES.naLand).catch(() => void 0),
47539
+ readJson(nb, dir, FILES.naLakes).catch(() => void 0),
47540
+ readJson(nb, dir, FILES.gazetteer)
47541
+ ]);
47542
+ return validate({
47543
+ worldCoarse,
47544
+ worldDetail,
47545
+ usStates,
47546
+ gazetteer,
47547
+ ...lakes && { lakes },
47548
+ ...rivers && { rivers },
47549
+ ...mountainRanges && { mountainRanges },
47550
+ ...naLand && { naLand },
47551
+ ...naLakes && { naLakes }
47552
+ });
47553
+ })().catch((e) => {
47554
+ cache = void 0;
47555
+ throw e;
47556
+ });
47557
+ return cache;
47558
+ }
47559
+ var FILES, CANDIDATE_DIRS, cache;
47560
+ var init_load_data = __esm({
47561
+ "src/map/load-data.ts"() {
47562
+ "use strict";
47563
+ FILES = {
47564
+ worldCoarse: "world-coarse.json",
47565
+ worldDetail: "world-detail.json",
47566
+ usStates: "us-states.json",
47567
+ lakes: "lakes.json",
47568
+ rivers: "rivers.json",
47569
+ mountainRanges: "mountain-ranges.json",
47570
+ naLand: "na-land.json",
47571
+ naLakes: "na-lakes.json",
47572
+ gazetteer: "gazetteer.json"
47573
+ };
47574
+ CANDIDATE_DIRS = [
47575
+ "./data",
47576
+ "./map-data",
47577
+ "../map-data",
47578
+ "../src/map/data"
47579
+ ];
47580
+ }
47581
+ });
47582
+
47460
47583
  // src/pyramid/renderer.ts
47461
47584
  var renderer_exports17 = {};
47462
47585
  __export(renderer_exports17, {
@@ -55585,15 +55708,17 @@ async function renderForExport(content, theme, palette, viewState, options) {
55585
55708
  if (detectedType === "map") {
55586
55709
  const { parseMap: parseMap2 } = await Promise.resolve().then(() => (init_parser12(), parser_exports11));
55587
55710
  const { resolveMap: resolveMap2 } = await Promise.resolve().then(() => (init_resolver2(), resolver_exports));
55588
- const { loadMapData: loadMapData2 } = await Promise.resolve().then(() => (init_load_data(), load_data_exports));
55589
55711
  const { renderMapForExport: renderMapForExport2 } = await Promise.resolve().then(() => (init_renderer16(), renderer_exports16));
55590
55712
  const effectivePalette2 = await resolveExportPalette(theme, palette);
55591
55713
  const mapParsed = parseMap2(content);
55592
- let mapData;
55593
- try {
55594
- mapData = await loadMapData2();
55595
- } catch {
55596
- return "";
55714
+ let mapData = options?.mapData;
55715
+ if (!mapData) {
55716
+ const { loadMapData: loadMapData2 } = await Promise.resolve().then(() => (init_load_data(), load_data_exports));
55717
+ try {
55718
+ mapData = await loadMapData2();
55719
+ } catch {
55720
+ return "";
55721
+ }
55597
55722
  }
55598
55723
  const mapResolved = resolveMap2(mapParsed, mapData);
55599
55724
  const container2 = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
@@ -56635,6 +56760,7 @@ var DIRECTIVE_KEYWORDS = /* @__PURE__ */ new Set([
56635
56760
  "default-country",
56636
56761
  "default-state",
56637
56762
  "no-legend",
56763
+ "no-insets",
56638
56764
  "muted",
56639
56765
  "natural",
56640
56766
  "subtitle",
@@ -57304,7 +57430,7 @@ pre.dgmo, code.language-dgmo, pre > code.language-dgmo,
57304
57430
 
57305
57431
  // src/auto/index.ts
57306
57432
  init_safe_href();
57307
- var VERSION = "0.21.0";
57433
+ var VERSION = "0.21.1";
57308
57434
  var DEFAULTS = {
57309
57435
  theme: "auto",
57310
57436
  palette: "nord",