@diagrammo/dgmo 0.22.0 → 0.23.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 (43) hide show
  1. package/dist/advanced.cjs +238 -48
  2. package/dist/advanced.d.cts +17 -0
  3. package/dist/advanced.d.ts +17 -0
  4. package/dist/advanced.js +238 -48
  5. package/dist/auto.cjs +236 -42
  6. package/dist/auto.js +115 -115
  7. package/dist/auto.mjs +236 -42
  8. package/dist/cli.cjs +153 -153
  9. package/dist/editor.cjs +3 -0
  10. package/dist/editor.js +3 -0
  11. package/dist/highlight.cjs +3 -0
  12. package/dist/highlight.js +3 -0
  13. package/dist/index.cjs +232 -41
  14. package/dist/index.js +232 -41
  15. package/dist/internal.cjs +238 -48
  16. package/dist/internal.d.cts +17 -0
  17. package/dist/internal.d.ts +17 -0
  18. package/dist/internal.js +238 -48
  19. package/dist/map-data/PROVENANCE.json +1 -1
  20. package/dist/map-data/gazetteer.json +1 -1
  21. package/dist/map-data/mountain-ranges.json +1 -1
  22. package/dist/map-data/water-bodies.json +1 -1
  23. package/dist/map-data/world-coarse.json +1 -1
  24. package/dist/map-data/world-detail.json +1 -1
  25. package/docs/language-reference.md +35 -0
  26. package/gallery/fixtures/boxes-and-lines.dgmo +6 -4
  27. package/package.json +1 -1
  28. package/src/boxes-and-lines/parser.ts +39 -0
  29. package/src/boxes-and-lines/renderer.ts +171 -13
  30. package/src/boxes-and-lines/types.ts +9 -0
  31. package/src/completion.ts +4 -5
  32. package/src/d3.ts +12 -4
  33. package/src/editor/keywords.ts +3 -0
  34. package/src/map/data/PROVENANCE.json +1 -1
  35. package/src/map/data/README.md +6 -0
  36. package/src/map/data/gazetteer.json +1 -1
  37. package/src/map/data/mountain-ranges.json +1 -1
  38. package/src/map/data/water-bodies.json +1 -1
  39. package/src/map/data/world-coarse.json +1 -1
  40. package/src/map/data/world-detail.json +1 -1
  41. package/src/map/layout.ts +111 -18
  42. package/src/map/renderer.ts +95 -4
  43. package/src/utils/reserved-key-registry.ts +5 -3
package/dist/index.js CHANGED
@@ -893,9 +893,7 @@ var init_reserved_key_registry = __esm({
893
893
  BOXES_AND_LINES_REGISTRY = staticRegistry([
894
894
  "color",
895
895
  "description",
896
- "width",
897
- "split",
898
- "fanout"
896
+ "value"
899
897
  ]);
900
898
  TIMELINE_REGISTRY = staticRegistry([
901
899
  "color",
@@ -16915,6 +16913,21 @@ function parseBoxesAndLines(content) {
16915
16913
  }
16916
16914
  continue;
16917
16915
  }
16916
+ if (!contentStarted) {
16917
+ const metricMatch = trimmed.match(/^box-metric\s+(.+)$/i);
16918
+ if (metricMatch) {
16919
+ const { label, colorName } = peelTrailingColorName(
16920
+ metricMatch[1].trim()
16921
+ );
16922
+ result.boxMetric = label;
16923
+ if (colorName !== void 0) result.boxMetricColor = colorName;
16924
+ continue;
16925
+ }
16926
+ if (/^show-values$/i.test(trimmed)) {
16927
+ result.showValues = true;
16928
+ continue;
16929
+ }
16930
+ }
16918
16931
  if (!contentStarted) {
16919
16932
  const optMatch = trimmed.match(OPTION_NOCOLON_RE);
16920
16933
  if (optMatch) {
@@ -17293,6 +17306,19 @@ function parseNodeLine(trimmed, lineNum, metaAliasMap, diagnostics, nameAliasMap
17293
17306
  description = [metadata["description"]];
17294
17307
  delete metadata["description"];
17295
17308
  }
17309
+ let value;
17310
+ if (metadata["value"] !== void 0) {
17311
+ const raw = metadata["value"];
17312
+ const num = Number(raw);
17313
+ if (Number.isFinite(num)) {
17314
+ value = num;
17315
+ } else {
17316
+ diagnostics.push(
17317
+ makeDgmoError(lineNum, `value must be a number (got "${raw}")`, "error")
17318
+ );
17319
+ }
17320
+ delete metadata["value"];
17321
+ }
17296
17322
  if (split.alias) {
17297
17323
  nameAliasMap?.set(normalizeName(split.alias), label);
17298
17324
  }
@@ -17301,7 +17327,8 @@ function parseNodeLine(trimmed, lineNum, metaAliasMap, diagnostics, nameAliasMap
17301
17327
  label,
17302
17328
  lineNumber: lineNum,
17303
17329
  metadata,
17304
- ...description !== void 0 && { description }
17330
+ ...description !== void 0 && { description },
17331
+ ...value !== void 0 && { value }
17305
17332
  };
17306
17333
  }
17307
17334
  function splitTargetAndMeta(rest, metaAliasMap) {
@@ -26423,7 +26450,18 @@ function fitLabelToHeader(label, nodeWidth, maxLines) {
26423
26450
  const truncated = label.length > maxChars ? label.slice(0, maxChars - 1) + "\u2026" : label;
26424
26451
  return { lines: [truncated], fontSize: MIN_NODE_FONT_SIZE };
26425
26452
  }
26426
- function nodeColors(node, tagGroups, activeGroupName, palette, isDark, solid) {
26453
+ function nodeColors(node, tagGroups, activeGroupName, palette, isDark, value, solid) {
26454
+ const neutralFill = mix(palette.bg, palette.text, isDark ? 90 : 95);
26455
+ if (value.active) {
26456
+ const fill3 = node.value !== void 0 ? value.fillForValue(node.value) : neutralFill;
26457
+ const stroke3 = value.hue;
26458
+ const text2 = contrastText(
26459
+ fill3,
26460
+ palette.textOnFillLight,
26461
+ palette.textOnFillDark
26462
+ );
26463
+ return { fill: fill3, stroke: stroke3, text: text2 };
26464
+ }
26427
26465
  const tagColor = resolveTagColor(
26428
26466
  node.metadata,
26429
26467
  [...tagGroups],
@@ -26532,25 +26570,65 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26532
26570
  const sGroupLabelZone = sctx.structural(GROUP_LABEL_ZONE);
26533
26571
  const sTitleFontSize = sctx.text(TITLE_FONT_SIZE);
26534
26572
  const sTitleY = sctx.structural(TITLE_Y);
26573
+ const nodeValues = parsed.nodes.filter((n) => n.value !== void 0).map((n) => n.value);
26574
+ const hasRamp = nodeValues.length > 0;
26575
+ const allNonNegative = hasRamp && nodeValues.every((v) => v >= 0);
26576
+ const rampMin = allNonNegative ? 0 : Math.min(...nodeValues);
26577
+ const rampMax = Math.max(...nodeValues);
26578
+ const rampHue = resolveColor(parsed.boxMetricColor ?? "", palette) ?? palette.primary;
26579
+ const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
26580
+ const fillForValue = (v) => {
26581
+ const t = rampMax > rampMin ? (v - rampMin) / (rampMax - rampMin) : 1;
26582
+ const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
26583
+ return mix(rampHue, rampBase, pct);
26584
+ };
26585
+ const VALUE_NAME = hasRamp ? parsed.boxMetric?.trim() || "Value" : null;
26586
+ const matchColorGroup = (v) => {
26587
+ const lv = v.trim().toLowerCase();
26588
+ if (lv === "none") return null;
26589
+ const tg = parsed.tagGroups.find((g) => g.name.toLowerCase() === lv);
26590
+ if (tg) return tg.name;
26591
+ if (lv === VALUE_NAME?.toLowerCase()) return VALUE_NAME;
26592
+ return v;
26593
+ };
26594
+ const override = activeTagGroup;
26595
+ let activeGroup;
26596
+ if (override !== void 0) {
26597
+ activeGroup = override === null ? null : matchColorGroup(override);
26598
+ } else if (parsed.options["active-tag"] !== void 0) {
26599
+ activeGroup = matchColorGroup(parsed.options["active-tag"]);
26600
+ } else {
26601
+ activeGroup = VALUE_NAME ?? (parsed.tagGroups.length > 0 ? parsed.tagGroups[0].name : null);
26602
+ }
26603
+ const activeIsValue = VALUE_NAME !== null && activeGroup === VALUE_NAME;
26604
+ const valueGroup = VALUE_NAME !== null ? {
26605
+ name: VALUE_NAME,
26606
+ entries: [],
26607
+ gradient: {
26608
+ min: rampMin,
26609
+ max: rampMax,
26610
+ hue: rampHue,
26611
+ base: rampBase
26612
+ }
26613
+ } : null;
26614
+ const legendGroups = [
26615
+ ...valueGroup ? [valueGroup] : [],
26616
+ ...parsed.tagGroups
26617
+ ];
26535
26618
  const reserveHasDescriptions = parsed.nodes.some(
26536
26619
  (n) => n.description && n.description.length > 0
26537
26620
  );
26538
- const willRenderLegend = parsed.tagGroups.length > 0 || reserveHasDescriptions && controlsHost !== "app";
26621
+ const willRenderLegend = legendGroups.length > 0 || reserveHasDescriptions && controlsHost !== "app";
26539
26622
  const sLegendHeight = willRenderLegend ? sctx.structural(
26540
26623
  getMaxLegendReservedHeight(
26541
26624
  {
26542
- groups: parsed.tagGroups,
26625
+ groups: legendGroups,
26543
26626
  position: { placement: "top-center", titleRelation: "below-title" },
26544
26627
  mode: exportMode ? "export" : "preview"
26545
26628
  },
26546
26629
  width
26547
26630
  )
26548
26631
  ) : 0;
26549
- const activeGroup = resolveActiveTagGroup(
26550
- parsed.tagGroups,
26551
- parsed.options["active-tag"],
26552
- activeTagGroup
26553
- );
26554
26632
  const hidden = hiddenTagValues ?? parsed.initialHiddenTagValues;
26555
26633
  const nodeMap = /* @__PURE__ */ new Map();
26556
26634
  for (const node of parsed.nodes) nodeMap.set(node.label, node);
@@ -26561,7 +26639,7 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26561
26639
  const hasAnyDescriptions = parsed.nodes.some(
26562
26640
  (n) => n.description && n.description.length > 0
26563
26641
  );
26564
- const needsLegend = parsed.tagGroups.length > 0 || hasAnyDescriptions && onToggleDescriptions;
26642
+ const needsLegend = legendGroups.length > 0 || hasAnyDescriptions && onToggleDescriptions;
26565
26643
  const legendH = needsLegend ? sLegendHeight + 8 : 0;
26566
26644
  const groupLabelsSet = new Set(layout.groups.map((g) => g.label));
26567
26645
  let labelZoneExtension = 0;
@@ -26767,12 +26845,16 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26767
26845
  activeGroup,
26768
26846
  palette,
26769
26847
  isDark,
26848
+ { active: activeIsValue, hue: rampHue, fillForValue },
26770
26849
  parsed.options["solid-fill"] === "on"
26771
26850
  );
26772
26851
  const nodeG = diagramG.append("g").attr("class", "bl-node").attr("transform", `translate(${ln.x},${ln.y})`).attr("data-line-number", node.lineNumber).attr("data-node-id", node.label).style("cursor", onClickItem ? "pointer" : "default").style("--bl-node-stroke", colors.stroke);
26773
26852
  for (const [key, val] of Object.entries(node.metadata)) {
26774
26853
  nodeG.attr(`data-tag-${key.toLowerCase()}`, val.toLowerCase());
26775
26854
  }
26855
+ if (node.value !== void 0) {
26856
+ nodeG.attr("data-value", node.value);
26857
+ }
26776
26858
  if (onClickItem) {
26777
26859
  nodeG.on("click", (event) => {
26778
26860
  const target = event.target;
@@ -26856,11 +26938,27 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26856
26938
  nodeG.append("text").attr("x", 0).attr("y", -totalH / 2 + lineH / 2 + li * lineH).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", fitted.fontSize).attr("font-weight", "600").attr("fill", colors.text).text(fitted.lines[li]);
26857
26939
  }
26858
26940
  }
26941
+ if (parsed.showValues && node.value !== void 0) {
26942
+ const valueText = String(node.value);
26943
+ const descShown = !!(desc && desc.length > 0 && !hideDescriptions);
26944
+ if (descShown) {
26945
+ const padX = 6;
26946
+ const padY = 5;
26947
+ const bw = valueText.length * VALUE_FONT_SIZE * CHAR_WIDTH_RATIO2 + 8;
26948
+ const bh = VALUE_FONT_SIZE + 4;
26949
+ const bx = ln.width / 2 - bw - 4;
26950
+ const by = -ln.height / 2 + 4;
26951
+ nodeG.append("rect").attr("x", bx).attr("y", by).attr("width", bw).attr("height", bh).attr("rx", 3).attr("fill", palette.bg).attr("opacity", 0.85);
26952
+ nodeG.append("text").attr("class", "bl-node-value").attr("x", bx + bw - padX).attr("y", by + padY).attr("text-anchor", "end").attr("dominant-baseline", "central").attr("font-size", VALUE_FONT_SIZE).attr("font-weight", "600").attr("fill", palette.textMuted).text(valueText);
26953
+ } else {
26954
+ nodeG.append("text").attr("class", "bl-node-value").attr("x", 0).attr("y", ln.height / 2 - VALUE_FONT_SIZE).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", VALUE_FONT_SIZE).attr("font-weight", "600").attr("fill", colors.text).attr("opacity", 0.8).text(valueText);
26955
+ }
26956
+ }
26859
26957
  }
26860
26958
  const hasDescriptions = parsed.nodes.some(
26861
26959
  (n) => n.description && n.description.length > 0
26862
26960
  );
26863
- const hasLegend = parsed.tagGroups.length > 0 || hasDescriptions && controlsHost !== "app";
26961
+ const hasLegend = legendGroups.length > 0 || hasDescriptions && controlsHost !== "app";
26864
26962
  if (hasLegend) {
26865
26963
  let controlsGroup;
26866
26964
  if (hasDescriptions && (onToggleDescriptions || controlsHost === "app")) {
@@ -26878,7 +26976,7 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26878
26976
  };
26879
26977
  }
26880
26978
  const legendConfig = {
26881
- groups: parsed.tagGroups,
26979
+ groups: legendGroups,
26882
26980
  position: { placement: "top-center", titleRelation: "below-title" },
26883
26981
  mode: exportMode ? "export" : "preview",
26884
26982
  // Keep inactive sibling tag groups visible as collapsed pills so the user
@@ -26933,7 +27031,7 @@ function renderBoxesAndLinesForExport(container, parsed, layout, palette, isDark
26933
27031
  }
26934
27032
  });
26935
27033
  }
26936
- var DIAGRAM_PADDING6, NODE_FONT_SIZE, MIN_NODE_FONT_SIZE, EDGE_LABEL_FONT_SIZE4, EDGE_STROKE_WIDTH5, NODE_STROKE_WIDTH5, NODE_RX, COLLAPSE_BAR_HEIGHT3, ARROWHEAD_W2, ARROWHEAD_H2, DESC_FONT_SIZE, DESC_LINE_HEIGHT, MAX_DESC_LINES, CHAR_WIDTH_RATIO2, NODE_TEXT_PADDING, GROUP_RX, GROUP_LABEL_FONT_SIZE, GROUP_LABEL_ZONE, lineGeneratorLR, lineGeneratorTB;
27034
+ var DIAGRAM_PADDING6, NODE_FONT_SIZE, MIN_NODE_FONT_SIZE, EDGE_LABEL_FONT_SIZE4, EDGE_STROKE_WIDTH5, NODE_STROKE_WIDTH5, NODE_RX, COLLAPSE_BAR_HEIGHT3, ARROWHEAD_W2, ARROWHEAD_H2, DESC_FONT_SIZE, DESC_LINE_HEIGHT, MAX_DESC_LINES, CHAR_WIDTH_RATIO2, NODE_TEXT_PADDING, GROUP_RX, GROUP_LABEL_FONT_SIZE, GROUP_LABEL_ZONE, RAMP_FLOOR, VALUE_FONT_SIZE, lineGeneratorLR, lineGeneratorTB;
26937
27035
  var init_renderer6 = __esm({
26938
27036
  "src/boxes-and-lines/renderer.ts"() {
26939
27037
  "use strict";
@@ -26942,6 +27040,7 @@ var init_renderer6 = __esm({
26942
27040
  init_legend_layout();
26943
27041
  init_title_constants();
26944
27042
  init_color_utils();
27043
+ init_colors();
26945
27044
  init_tag_groups();
26946
27045
  init_inline_markdown();
26947
27046
  init_wrapped_desc();
@@ -26964,6 +27063,8 @@ var init_renderer6 = __esm({
26964
27063
  GROUP_RX = 8;
26965
27064
  GROUP_LABEL_FONT_SIZE = 14;
26966
27065
  GROUP_LABEL_ZONE = 32;
27066
+ RAMP_FLOOR = 15;
27067
+ VALUE_FONT_SIZE = 11;
26967
27068
  lineGeneratorLR = d3Shape4.line().x((d) => d.x).y((d) => d.y).curve(d3Shape4.curveBasis);
26968
27069
  lineGeneratorTB = d3Shape4.line().x((d) => d.x).y((d) => d.y).curve(d3Shape4.curveBasis);
26969
27070
  }
@@ -47306,6 +47407,38 @@ function parsePathRings(d) {
47306
47407
  if (cur.length) rings.push(cur);
47307
47408
  return rings;
47308
47409
  }
47410
+ function dropAntimeridianWrapSlivers(d, width, height) {
47411
+ const rings = parsePathRings(d);
47412
+ if (rings.length <= 1) return d;
47413
+ const eps = 0.75;
47414
+ const minArea = 3e-3 * width * height;
47415
+ const ringArea = (r) => {
47416
+ let s = 0;
47417
+ for (let i = 0; i < r.length; i++) {
47418
+ const a = r[i];
47419
+ const b = r[(i + 1) % r.length];
47420
+ s += a[0] * b[1] - b[0] * a[1];
47421
+ }
47422
+ return Math.abs(s) / 2;
47423
+ };
47424
+ const areas = rings.map(ringArea);
47425
+ const maxArea = Math.max(...areas);
47426
+ const onVEdge = (a, b) => Math.abs(a[0]) <= eps && Math.abs(b[0]) <= eps || Math.abs(a[0] - width) <= eps && Math.abs(b[0] - width) <= eps;
47427
+ let dropped = false;
47428
+ const kept = rings.filter((r, idx) => {
47429
+ if (areas[idx] >= maxArea || areas[idx] >= minArea) return true;
47430
+ const touches = r.some((p, i) => onVEdge(p, r[(i + 1) % r.length]));
47431
+ if (touches) {
47432
+ dropped = true;
47433
+ return false;
47434
+ }
47435
+ return true;
47436
+ });
47437
+ if (!dropped) return d;
47438
+ return kept.map(
47439
+ (r) => r.map((p, i) => (i ? "L" : "M") + p[0] + "," + p[1]).join("") + "Z"
47440
+ ).join("");
47441
+ }
47309
47442
  function layoutMap(resolved, data, size, opts) {
47310
47443
  const { palette, isDark } = opts;
47311
47444
  const { width, height } = size;
@@ -47389,7 +47522,7 @@ function layoutMap(resolved, data, size, opts) {
47389
47522
  const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
47390
47523
  const fillForValue = (s) => {
47391
47524
  const t = rampMax > rampMin ? (s - rampMin) / (rampMax - rampMin) : 1;
47392
- const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
47525
+ const pct = RAMP_FLOOR2 + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR2);
47393
47526
  return mix(rampHue, rampBase, pct);
47394
47527
  };
47395
47528
  const tagFill = (tags, groupName) => {
@@ -47448,10 +47581,11 @@ function layoutMap(resolved, data, size, opts) {
47448
47581
  const by0 = cb[0][1];
47449
47582
  const cw = cb[1][0] - bx0;
47450
47583
  const ch = cb[1][1] - by0;
47451
- const ox = fitBox[0][0];
47452
- const oy = fitBox[0][1];
47453
- const sx = cw > 0 ? (fitBox[1][0] - ox) / cw : 1;
47454
- const sy = ch > 0 ? (fitBox[1][1] - oy) / ch : 1;
47584
+ const topReserve = resolved.title && resolved.pois.length > 0 ? topPad : 0;
47585
+ const ox = 0;
47586
+ const oy = topReserve;
47587
+ const sx = cw > 0 ? width / cw : 1;
47588
+ const sy = ch > 0 ? (height - topReserve) / ch : 1;
47455
47589
  stretchParams = { sx, sy, ox, oy, bx0, by0 };
47456
47590
  const stretch = (x, y) => [
47457
47591
  ox + (x - bx0) * sx,
@@ -47724,7 +47858,8 @@ function layoutMap(resolved, data, size, opts) {
47724
47858
  const r = regionById.get(iso);
47725
47859
  const viewF = shouldCull ? cullFeatureToView(f) : dropFrameFillers(f);
47726
47860
  if (!viewF) continue;
47727
- const d = path(viewF) ?? "";
47861
+ const raw = path(viewF) ?? "";
47862
+ const d = fitIsGlobal ? dropAntimeridianWrapSlivers(raw, width, height) : raw;
47728
47863
  if (!d) continue;
47729
47864
  const isThisLayer = r?.layer === layerKind;
47730
47865
  const isForeign = layerKind === "country" && usContext && iso !== "US";
@@ -47741,6 +47876,9 @@ function layoutMap(resolved, data, size, opts) {
47741
47876
  } else {
47742
47877
  label = f.properties?.name;
47743
47878
  }
47879
+ const labelAnchor = WORLD_LABEL_ANCHORS[iso];
47880
+ const c = labelAnchor ? project(labelAnchor[0], labelAnchor[1]) : path.centroid(viewF);
47881
+ const hasCentroid = c != null && Number.isFinite(c[0]) && Number.isFinite(c[1]);
47744
47882
  regions.push({
47745
47883
  id: iso,
47746
47884
  d,
@@ -47749,6 +47887,7 @@ function layoutMap(resolved, data, size, opts) {
47749
47887
  lineNumber,
47750
47888
  layer,
47751
47889
  ...label !== void 0 && { label },
47890
+ ...hasCentroid && { labelX: c[0], labelY: c[1] },
47752
47891
  ...isThisLayer && r.value !== void 0 && { value: r.value },
47753
47892
  ...isThisLayer && Object.keys(r.tags).length > 0 && { tags: r.tags }
47754
47893
  });
@@ -48189,10 +48328,6 @@ function layoutMap(resolved, data, size, opts) {
48189
48328
  lineNumber
48190
48329
  });
48191
48330
  };
48192
- const WORLD_LABEL_ANCHORS = {
48193
- US: [-98.5, 39.5]
48194
- // CONUS geographic centre (near Lebanon, Kansas)
48195
- };
48196
48331
  const REGION_LABEL_GAP = 2;
48197
48332
  const regionLabelRect = (cx, cy, text) => {
48198
48333
  const w = measureLegendText(text, FONT2) + 2 * REGION_LABEL_GAP;
@@ -48558,7 +48693,7 @@ function layoutMap(resolved, data, size, opts) {
48558
48693
  diagnostics: []
48559
48694
  };
48560
48695
  }
48561
- var FIT_PAD, RAMP_FLOOR, R_DEFAULT, R_MIN, R_MAX, W_MIN, W_MAX, FONT2, MAX_CLUSTER_EXTENT_FACTOR, MAX_COLUMN_ROWS, REGION_LABEL_HALO_RATIO, LAND_TINT_LIGHT, LAND_TINT_DARK, TAG_TINT_LIGHT, TAG_TINT_DARK, WATER_TINT_LIGHT, WATER_TINT_DARK, RIVER_WIDTH, COMPACT_WIDTH_PX, RELIEF_MIN_AREA, RELIEF_MIN_DIM, RELIEF_HATCH_SPACING, RELIEF_HATCH_WIDTH, RELIEF_HATCH_STRENGTH, COASTLINE_RING_COUNT, COASTLINE_D0, COASTLINE_STEP, COASTLINE_THICKNESS, COASTLINE_OPACITY_NEAR, COASTLINE_OPACITY_FAR, COASTLINE_MIN_EXTENT, COASTLINE_MIN_EXTENT_GLOBAL, COASTLINE_STROKE_MIX, FOREIGN_TINT_LIGHT, FOREIGN_TINT_DARK, MUTED_FOREIGN_LIGHT, MUTED_FOREIGN_DARK, COLO_R, GOLDEN_ANGLE, STACK_OVERLAP, STACK_RING_MAX, STACK_RING_GAP, FAN_STEP, ARC_CURVE_FRAC, decodeCache, usConusProjection, alaskaProjection, hawaiiProjection, INSET_STATES, inAlaska, inHawaii, FOREIGN_BORDER, US_NON_CONUS;
48696
+ var FIT_PAD, RAMP_FLOOR2, R_DEFAULT, R_MIN, R_MAX, W_MIN, W_MAX, FONT2, WORLD_LABEL_ANCHORS, MAX_CLUSTER_EXTENT_FACTOR, MAX_COLUMN_ROWS, REGION_LABEL_HALO_RATIO, LAND_TINT_LIGHT, LAND_TINT_DARK, TAG_TINT_LIGHT, TAG_TINT_DARK, WATER_TINT_LIGHT, WATER_TINT_DARK, RIVER_WIDTH, COMPACT_WIDTH_PX, RELIEF_MIN_AREA, RELIEF_MIN_DIM, RELIEF_HATCH_SPACING, RELIEF_HATCH_WIDTH, RELIEF_HATCH_STRENGTH, COASTLINE_RING_COUNT, COASTLINE_D0, COASTLINE_STEP, COASTLINE_THICKNESS, COASTLINE_OPACITY_NEAR, COASTLINE_OPACITY_FAR, COASTLINE_MIN_EXTENT, COASTLINE_MIN_EXTENT_GLOBAL, COASTLINE_STROKE_MIX, FOREIGN_TINT_LIGHT, FOREIGN_TINT_DARK, MUTED_FOREIGN_LIGHT, MUTED_FOREIGN_DARK, COLO_R, GOLDEN_ANGLE, STACK_OVERLAP, STACK_RING_MAX, STACK_RING_GAP, FAN_STEP, ARC_CURVE_FRAC, decodeCache, usConusProjection, alaskaProjection, hawaiiProjection, INSET_STATES, inAlaska, inHawaii, FOREIGN_BORDER, US_NON_CONUS;
48562
48697
  var init_layout15 = __esm({
48563
48698
  "src/map/layout.ts"() {
48564
48699
  "use strict";
@@ -48571,13 +48706,17 @@ var init_layout15 = __esm({
48571
48706
  init_title_constants();
48572
48707
  init_context_labels();
48573
48708
  FIT_PAD = 24;
48574
- RAMP_FLOOR = 15;
48709
+ RAMP_FLOOR2 = 15;
48575
48710
  R_DEFAULT = 6;
48576
48711
  R_MIN = 4;
48577
48712
  R_MAX = 22;
48578
48713
  W_MIN = 1.25;
48579
48714
  W_MAX = 8;
48580
48715
  FONT2 = 11;
48716
+ WORLD_LABEL_ANCHORS = {
48717
+ US: [-98.5, 39.5]
48718
+ // CONUS geographic centre (near Lebanon, Kansas)
48719
+ };
48581
48720
  MAX_CLUSTER_EXTENT_FACTOR = 0.18;
48582
48721
  MAX_COLUMN_ROWS = 7;
48583
48722
  REGION_LABEL_HALO_RATIO = 4.5;
@@ -48591,9 +48730,9 @@ var init_layout15 = __esm({
48591
48730
  COMPACT_WIDTH_PX = 480;
48592
48731
  RELIEF_MIN_AREA = 12;
48593
48732
  RELIEF_MIN_DIM = 2;
48594
- RELIEF_HATCH_SPACING = 2;
48595
- RELIEF_HATCH_WIDTH = 0.15;
48596
- RELIEF_HATCH_STRENGTH = 32;
48733
+ RELIEF_HATCH_SPACING = 1.5;
48734
+ RELIEF_HATCH_WIDTH = 0.2;
48735
+ RELIEF_HATCH_STRENGTH = 26;
48597
48736
  COASTLINE_RING_COUNT = 5;
48598
48737
  COASTLINE_D0 = 16e-4;
48599
48738
  COASTLINE_STEP = 28e-4;
@@ -48672,7 +48811,47 @@ function ringToPath(ring) {
48672
48811
  d += (i ? "L" : "M") + ring[i][0] + "," + ring[i][1];
48673
48812
  return d + "Z";
48674
48813
  }
48675
- function coastlineOuterRings(regions, minExtent) {
48814
+ function polylineToPath(pts) {
48815
+ let d = "";
48816
+ for (let i = 0; i < pts.length; i++)
48817
+ d += (i ? "L" : "M") + pts[i][0] + "," + pts[i][1];
48818
+ return d;
48819
+ }
48820
+ function ringToCoastPaths(ring, frame) {
48821
+ if (!frame) return [ringToPath(ring)];
48822
+ const n = ring.length;
48823
+ const eps = 0.75;
48824
+ const onL = (x) => Math.abs(x) <= eps;
48825
+ const onR = (x) => Math.abs(x - frame.w) <= eps;
48826
+ const onT = (y) => Math.abs(y) <= eps;
48827
+ const onB = (y) => Math.abs(y - frame.h) <= eps;
48828
+ const isFrameEdge = (a, b) => onL(a[0]) && onL(b[0]) || onR(a[0]) && onR(b[0]) || onT(a[1]) && onT(b[1]) || onB(a[1]) && onB(b[1]);
48829
+ let firstBreak = -1;
48830
+ for (let i = 0; i < n; i++)
48831
+ if (isFrameEdge(ring[i], ring[(i + 1) % n])) {
48832
+ firstBreak = i;
48833
+ break;
48834
+ }
48835
+ if (firstBreak === -1) return [ringToPath(ring)];
48836
+ const paths = [];
48837
+ let cur = [];
48838
+ const start = (firstBreak + 1) % n;
48839
+ for (let k = 0; k < n; k++) {
48840
+ const i = (start + k) % n;
48841
+ const a = ring[i];
48842
+ const b = ring[(i + 1) % n];
48843
+ if (isFrameEdge(a, b)) {
48844
+ if (cur.length >= 2) paths.push(polylineToPath(cur));
48845
+ cur = [];
48846
+ continue;
48847
+ }
48848
+ if (cur.length === 0) cur.push(a);
48849
+ cur.push(b);
48850
+ }
48851
+ if (cur.length >= 2) paths.push(polylineToPath(cur));
48852
+ return paths;
48853
+ }
48854
+ function coastlineOuterRings(regions, minExtent, frame) {
48676
48855
  const paths = [];
48677
48856
  for (const r of regions) {
48678
48857
  const rings = parsePathRings(r.d);
@@ -48695,7 +48874,7 @@ function coastlineOuterRings(regions, minExtent) {
48695
48874
  for (let j = 0; j < rings.length; j++)
48696
48875
  if (j !== i && pointInRing2(fx, fy, rings[j])) depth++;
48697
48876
  if (depth % 2 === 1) continue;
48698
- paths.push(ringToPath(ring));
48877
+ paths.push(...ringToCoastPaths(ring, frame));
48699
48878
  }
48700
48879
  }
48701
48880
  return paths;
@@ -48739,6 +48918,10 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
48739
48918
  const drawRegion = (g, r, strokeWidth) => {
48740
48919
  const p = g.append("path").attr("d", r.d).attr("fill", r.fill).attr("stroke", r.stroke).attr("stroke-width", strokeWidth);
48741
48920
  if (r.label) p.attr("data-region-name", r.label);
48921
+ if (r.id && r.id !== "lake") p.attr("data-iso", r.id);
48922
+ if (r.labelX !== void 0 && r.labelY !== void 0) {
48923
+ p.attr("data-label-x", r.labelX).attr("data-label-y", r.labelY);
48924
+ }
48742
48925
  if (r.layer !== "base") {
48743
48926
  p.classed("dgmo-map-region", true).attr("data-region", r.id);
48744
48927
  if (r.value !== void 0) p.attr("data-value", r.value);
@@ -48768,7 +48951,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
48768
48951
  const landClip = defs.append("clipPath").attr("id", landClipId);
48769
48952
  for (const r of layout.regions)
48770
48953
  if (r.id !== "lake") landClip.append("path").attr("d", r.d);
48771
- 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");
48954
+ const gRelief = svg.append("g").attr("clip-path", `url(#${landClipId})`).style("pointer-events", "none").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");
48772
48955
  for (let y = h.spacing; y < height; y += h.spacing) {
48773
48956
  gRelief.append("line").attr("x1", 0).attr("y1", y).attr("x2", width).attr("y2", y);
48774
48957
  }
@@ -48789,10 +48972,16 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
48789
48972
  mask.append("path").attr("d", d).attr("fill", "black").attr("stroke", "black").attr("stroke-width", 2 * reach).attr("stroke-linejoin", "round");
48790
48973
  }
48791
48974
  }
48792
- const gWater = svg.append("g").attr("class", "dgmo-map-water-lines").attr("fill", "none").attr("mask", `url(#${maskId})`);
48975
+ const gWater = svg.append("g").attr("class", "dgmo-map-water-lines").attr("fill", "none").style("pointer-events", "none").attr("mask", `url(#${maskId})`);
48793
48976
  appendWaterLines(
48794
48977
  gWater,
48795
- coastlineOuterRings(layout.regions, cs.minExtent),
48978
+ // Pass the canvas frame so edges collinear with it (the antimeridian on a
48979
+ // world map, regional clipExtent cuts) don't get ringed as fake coast —
48980
+ // land runs cleanly to the render-area edge.
48981
+ coastlineOuterRings(layout.regions, cs.minExtent, {
48982
+ w: width,
48983
+ h: height
48984
+ }),
48796
48985
  cs,
48797
48986
  layout.background
48798
48987
  );
@@ -48806,7 +48995,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
48806
48995
  gWater.append("path").attr("d", ds.join(" ")).attr("stroke", stroke2).attr("stroke-width", 0.5).attr("stroke-linejoin", "round");
48807
48996
  }
48808
48997
  if (layout.rivers.length) {
48809
- const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none");
48998
+ const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none").style("pointer-events", "none");
48810
48999
  for (const r of layout.rivers) {
48811
49000
  gRivers.append("path").attr("d", r.d).attr("stroke", r.color).attr("stroke-width", r.width).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
48812
49001
  }
@@ -48847,7 +49036,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
48847
49036
  const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
48848
49037
  clip.append("path").attr("d", d);
48849
49038
  }
48850
- const gInsetWater = insetG.append("g").attr("clip-path", `url(#${clipId})`).append("g").attr("class", "dgmo-map-inset-water-lines").attr("fill", "none").attr("mask", `url(#${maskId})`);
49039
+ const gInsetWater = insetG.append("g").attr("clip-path", `url(#${clipId})`).append("g").attr("class", "dgmo-map-inset-water-lines").attr("fill", "none").style("pointer-events", "none").attr("mask", `url(#${maskId})`);
48851
49040
  appendWaterLines(
48852
49041
  gInsetWater,
48853
49042
  coastlineOuterRings(layout.insetRegions, cs.minExtent),
@@ -54868,10 +55057,12 @@ function renderTimelineHorizontalTimeSort(container, parsed, palette, isDark, se
54868
55057
  const markerLabelY = markerReserve ? -(topScaleH + MARKER_ROW_H / 2) : 0;
54869
55058
  const eraLabelY = eraReserve ? -(topScaleH + markerReserve + ERA_ROW_H / 2) : 0;
54870
55059
  const innerWidth = width - margin.left - margin.right;
54871
- const innerHeight = height - margin.top - margin.bottom;
54872
- const rowH = Math.min(ctx.structural(28), innerHeight / sorted.length);
55060
+ const availInnerHeight = height - margin.top - margin.bottom;
55061
+ const rowH = Math.min(ctx.structural(28), availInnerHeight / sorted.length);
55062
+ const innerHeight = rowH * sorted.length;
55063
+ const usedHeight = margin.top + innerHeight + margin.bottom;
54873
55064
  const xScale = d3Scale2.scaleLinear().domain([minDate - datePadding, maxDate + datePadding]).range([0, innerWidth]);
54874
- const svg = d3Selection23.select(container).append("svg").attr("width", width).attr("height", height).attr("viewBox", `0 0 ${width} ${height}`).attr("preserveAspectRatio", "xMidYMin meet").style("background", bgColor);
55065
+ const svg = d3Selection23.select(container).append("svg").attr("width", width).attr("height", usedHeight).attr("viewBox", `0 0 ${width} ${usedHeight}`).attr("preserveAspectRatio", "xMidYMin meet").style("background", bgColor);
54875
55066
  if (ctx.isBelowFloor) {
54876
55067
  svg.attr("width", "100%");
54877
55068
  }