@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/editor.cjs CHANGED
@@ -147,6 +147,9 @@ var DIRECTIVE_KEYWORDS = /* @__PURE__ */ new Set([
147
147
  "hide",
148
148
  "mode",
149
149
  "direction",
150
+ // Boxes-and-lines
151
+ "box-metric",
152
+ "show-values",
150
153
  // ER
151
154
  "notation",
152
155
  // Class
package/dist/editor.js CHANGED
@@ -117,6 +117,9 @@ var DIRECTIVE_KEYWORDS = /* @__PURE__ */ new Set([
117
117
  "hide",
118
118
  "mode",
119
119
  "direction",
120
+ // Boxes-and-lines
121
+ "box-metric",
122
+ "show-values",
120
123
  // ER
121
124
  "notation",
122
125
  // Class
@@ -109,6 +109,9 @@ var DIRECTIVE_KEYWORDS = /* @__PURE__ */ new Set([
109
109
  "hide",
110
110
  "mode",
111
111
  "direction",
112
+ // Boxes-and-lines
113
+ "box-metric",
114
+ "show-values",
112
115
  // ER
113
116
  "notation",
114
117
  // Class
package/dist/highlight.js CHANGED
@@ -80,6 +80,9 @@ var DIRECTIVE_KEYWORDS = /* @__PURE__ */ new Set([
80
80
  "hide",
81
81
  "mode",
82
82
  "direction",
83
+ // Boxes-and-lines
84
+ "box-metric",
85
+ "show-values",
83
86
  // ER
84
87
  "notation",
85
88
  // Class
package/dist/index.cjs CHANGED
@@ -895,9 +895,7 @@ var init_reserved_key_registry = __esm({
895
895
  BOXES_AND_LINES_REGISTRY = staticRegistry([
896
896
  "color",
897
897
  "description",
898
- "width",
899
- "split",
900
- "fanout"
898
+ "value"
901
899
  ]);
902
900
  TIMELINE_REGISTRY = staticRegistry([
903
901
  "color",
@@ -16899,6 +16897,21 @@ function parseBoxesAndLines(content) {
16899
16897
  }
16900
16898
  continue;
16901
16899
  }
16900
+ if (!contentStarted) {
16901
+ const metricMatch = trimmed.match(/^box-metric\s+(.+)$/i);
16902
+ if (metricMatch) {
16903
+ const { label, colorName } = peelTrailingColorName(
16904
+ metricMatch[1].trim()
16905
+ );
16906
+ result.boxMetric = label;
16907
+ if (colorName !== void 0) result.boxMetricColor = colorName;
16908
+ continue;
16909
+ }
16910
+ if (/^show-values$/i.test(trimmed)) {
16911
+ result.showValues = true;
16912
+ continue;
16913
+ }
16914
+ }
16902
16915
  if (!contentStarted) {
16903
16916
  const optMatch = trimmed.match(OPTION_NOCOLON_RE);
16904
16917
  if (optMatch) {
@@ -17277,6 +17290,19 @@ function parseNodeLine(trimmed, lineNum, metaAliasMap, diagnostics, nameAliasMap
17277
17290
  description = [metadata["description"]];
17278
17291
  delete metadata["description"];
17279
17292
  }
17293
+ let value;
17294
+ if (metadata["value"] !== void 0) {
17295
+ const raw = metadata["value"];
17296
+ const num = Number(raw);
17297
+ if (Number.isFinite(num)) {
17298
+ value = num;
17299
+ } else {
17300
+ diagnostics.push(
17301
+ makeDgmoError(lineNum, `value must be a number (got "${raw}")`, "error")
17302
+ );
17303
+ }
17304
+ delete metadata["value"];
17305
+ }
17280
17306
  if (split.alias) {
17281
17307
  nameAliasMap?.set(normalizeName(split.alias), label);
17282
17308
  }
@@ -17285,7 +17311,8 @@ function parseNodeLine(trimmed, lineNum, metaAliasMap, diagnostics, nameAliasMap
17285
17311
  label,
17286
17312
  lineNumber: lineNum,
17287
17313
  metadata,
17288
- ...description !== void 0 && { description }
17314
+ ...description !== void 0 && { description },
17315
+ ...value !== void 0 && { value }
17289
17316
  };
17290
17317
  }
17291
17318
  function splitTargetAndMeta(rest, metaAliasMap) {
@@ -26405,7 +26432,18 @@ function fitLabelToHeader(label, nodeWidth, maxLines) {
26405
26432
  const truncated = label.length > maxChars ? label.slice(0, maxChars - 1) + "\u2026" : label;
26406
26433
  return { lines: [truncated], fontSize: MIN_NODE_FONT_SIZE };
26407
26434
  }
26408
- function nodeColors(node, tagGroups, activeGroupName, palette, isDark, solid) {
26435
+ function nodeColors(node, tagGroups, activeGroupName, palette, isDark, value, solid) {
26436
+ const neutralFill = mix(palette.bg, palette.text, isDark ? 90 : 95);
26437
+ if (value.active) {
26438
+ const fill3 = node.value !== void 0 ? value.fillForValue(node.value) : neutralFill;
26439
+ const stroke3 = value.hue;
26440
+ const text2 = contrastText(
26441
+ fill3,
26442
+ palette.textOnFillLight,
26443
+ palette.textOnFillDark
26444
+ );
26445
+ return { fill: fill3, stroke: stroke3, text: text2 };
26446
+ }
26409
26447
  const tagColor = resolveTagColor(
26410
26448
  node.metadata,
26411
26449
  [...tagGroups],
@@ -26514,25 +26552,65 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26514
26552
  const sGroupLabelZone = sctx.structural(GROUP_LABEL_ZONE);
26515
26553
  const sTitleFontSize = sctx.text(TITLE_FONT_SIZE);
26516
26554
  const sTitleY = sctx.structural(TITLE_Y);
26555
+ const nodeValues = parsed.nodes.filter((n) => n.value !== void 0).map((n) => n.value);
26556
+ const hasRamp = nodeValues.length > 0;
26557
+ const allNonNegative = hasRamp && nodeValues.every((v) => v >= 0);
26558
+ const rampMin = allNonNegative ? 0 : Math.min(...nodeValues);
26559
+ const rampMax = Math.max(...nodeValues);
26560
+ const rampHue = resolveColor(parsed.boxMetricColor ?? "", palette) ?? palette.primary;
26561
+ const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
26562
+ const fillForValue = (v) => {
26563
+ const t = rampMax > rampMin ? (v - rampMin) / (rampMax - rampMin) : 1;
26564
+ const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
26565
+ return mix(rampHue, rampBase, pct);
26566
+ };
26567
+ const VALUE_NAME = hasRamp ? parsed.boxMetric?.trim() || "Value" : null;
26568
+ const matchColorGroup = (v) => {
26569
+ const lv = v.trim().toLowerCase();
26570
+ if (lv === "none") return null;
26571
+ const tg = parsed.tagGroups.find((g) => g.name.toLowerCase() === lv);
26572
+ if (tg) return tg.name;
26573
+ if (lv === VALUE_NAME?.toLowerCase()) return VALUE_NAME;
26574
+ return v;
26575
+ };
26576
+ const override = activeTagGroup;
26577
+ let activeGroup;
26578
+ if (override !== void 0) {
26579
+ activeGroup = override === null ? null : matchColorGroup(override);
26580
+ } else if (parsed.options["active-tag"] !== void 0) {
26581
+ activeGroup = matchColorGroup(parsed.options["active-tag"]);
26582
+ } else {
26583
+ activeGroup = VALUE_NAME ?? (parsed.tagGroups.length > 0 ? parsed.tagGroups[0].name : null);
26584
+ }
26585
+ const activeIsValue = VALUE_NAME !== null && activeGroup === VALUE_NAME;
26586
+ const valueGroup = VALUE_NAME !== null ? {
26587
+ name: VALUE_NAME,
26588
+ entries: [],
26589
+ gradient: {
26590
+ min: rampMin,
26591
+ max: rampMax,
26592
+ hue: rampHue,
26593
+ base: rampBase
26594
+ }
26595
+ } : null;
26596
+ const legendGroups = [
26597
+ ...valueGroup ? [valueGroup] : [],
26598
+ ...parsed.tagGroups
26599
+ ];
26517
26600
  const reserveHasDescriptions = parsed.nodes.some(
26518
26601
  (n) => n.description && n.description.length > 0
26519
26602
  );
26520
- const willRenderLegend = parsed.tagGroups.length > 0 || reserveHasDescriptions && controlsHost !== "app";
26603
+ const willRenderLegend = legendGroups.length > 0 || reserveHasDescriptions && controlsHost !== "app";
26521
26604
  const sLegendHeight = willRenderLegend ? sctx.structural(
26522
26605
  getMaxLegendReservedHeight(
26523
26606
  {
26524
- groups: parsed.tagGroups,
26607
+ groups: legendGroups,
26525
26608
  position: { placement: "top-center", titleRelation: "below-title" },
26526
26609
  mode: exportMode ? "export" : "preview"
26527
26610
  },
26528
26611
  width
26529
26612
  )
26530
26613
  ) : 0;
26531
- const activeGroup = resolveActiveTagGroup(
26532
- parsed.tagGroups,
26533
- parsed.options["active-tag"],
26534
- activeTagGroup
26535
- );
26536
26614
  const hidden = hiddenTagValues ?? parsed.initialHiddenTagValues;
26537
26615
  const nodeMap = /* @__PURE__ */ new Map();
26538
26616
  for (const node of parsed.nodes) nodeMap.set(node.label, node);
@@ -26543,7 +26621,7 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26543
26621
  const hasAnyDescriptions = parsed.nodes.some(
26544
26622
  (n) => n.description && n.description.length > 0
26545
26623
  );
26546
- const needsLegend = parsed.tagGroups.length > 0 || hasAnyDescriptions && onToggleDescriptions;
26624
+ const needsLegend = legendGroups.length > 0 || hasAnyDescriptions && onToggleDescriptions;
26547
26625
  const legendH = needsLegend ? sLegendHeight + 8 : 0;
26548
26626
  const groupLabelsSet = new Set(layout.groups.map((g) => g.label));
26549
26627
  let labelZoneExtension = 0;
@@ -26749,12 +26827,16 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26749
26827
  activeGroup,
26750
26828
  palette,
26751
26829
  isDark,
26830
+ { active: activeIsValue, hue: rampHue, fillForValue },
26752
26831
  parsed.options["solid-fill"] === "on"
26753
26832
  );
26754
26833
  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);
26755
26834
  for (const [key, val] of Object.entries(node.metadata)) {
26756
26835
  nodeG.attr(`data-tag-${key.toLowerCase()}`, val.toLowerCase());
26757
26836
  }
26837
+ if (node.value !== void 0) {
26838
+ nodeG.attr("data-value", node.value);
26839
+ }
26758
26840
  if (onClickItem) {
26759
26841
  nodeG.on("click", (event) => {
26760
26842
  const target = event.target;
@@ -26838,11 +26920,27 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26838
26920
  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]);
26839
26921
  }
26840
26922
  }
26923
+ if (parsed.showValues && node.value !== void 0) {
26924
+ const valueText = String(node.value);
26925
+ const descShown = !!(desc && desc.length > 0 && !hideDescriptions);
26926
+ if (descShown) {
26927
+ const padX = 6;
26928
+ const padY = 5;
26929
+ const bw = valueText.length * VALUE_FONT_SIZE * CHAR_WIDTH_RATIO2 + 8;
26930
+ const bh = VALUE_FONT_SIZE + 4;
26931
+ const bx = ln.width / 2 - bw - 4;
26932
+ const by = -ln.height / 2 + 4;
26933
+ 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);
26934
+ 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);
26935
+ } else {
26936
+ 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);
26937
+ }
26938
+ }
26841
26939
  }
26842
26940
  const hasDescriptions = parsed.nodes.some(
26843
26941
  (n) => n.description && n.description.length > 0
26844
26942
  );
26845
- const hasLegend = parsed.tagGroups.length > 0 || hasDescriptions && controlsHost !== "app";
26943
+ const hasLegend = legendGroups.length > 0 || hasDescriptions && controlsHost !== "app";
26846
26944
  if (hasLegend) {
26847
26945
  let controlsGroup;
26848
26946
  if (hasDescriptions && (onToggleDescriptions || controlsHost === "app")) {
@@ -26860,7 +26958,7 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26860
26958
  };
26861
26959
  }
26862
26960
  const legendConfig = {
26863
- groups: parsed.tagGroups,
26961
+ groups: legendGroups,
26864
26962
  position: { placement: "top-center", titleRelation: "below-title" },
26865
26963
  mode: exportMode ? "export" : "preview",
26866
26964
  // Keep inactive sibling tag groups visible as collapsed pills so the user
@@ -26915,7 +27013,7 @@ function renderBoxesAndLinesForExport(container, parsed, layout, palette, isDark
26915
27013
  }
26916
27014
  });
26917
27015
  }
26918
- var d3Selection6, d3Shape4, 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;
27016
+ var d3Selection6, d3Shape4, 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;
26919
27017
  var init_renderer6 = __esm({
26920
27018
  "src/boxes-and-lines/renderer.ts"() {
26921
27019
  "use strict";
@@ -26926,6 +27024,7 @@ var init_renderer6 = __esm({
26926
27024
  init_legend_layout();
26927
27025
  init_title_constants();
26928
27026
  init_color_utils();
27027
+ init_colors();
26929
27028
  init_tag_groups();
26930
27029
  init_inline_markdown();
26931
27030
  init_wrapped_desc();
@@ -26948,6 +27047,8 @@ var init_renderer6 = __esm({
26948
27047
  GROUP_RX = 8;
26949
27048
  GROUP_LABEL_FONT_SIZE = 14;
26950
27049
  GROUP_LABEL_ZONE = 32;
27050
+ RAMP_FLOOR = 15;
27051
+ VALUE_FONT_SIZE = 11;
26951
27052
  lineGeneratorLR = d3Shape4.line().x((d) => d.x).y((d) => d.y).curve(d3Shape4.curveBasis);
26952
27053
  lineGeneratorTB = d3Shape4.line().x((d) => d.x).y((d) => d.y).curve(d3Shape4.curveBasis);
26953
27054
  }
@@ -47279,6 +47380,38 @@ function parsePathRings(d) {
47279
47380
  if (cur.length) rings.push(cur);
47280
47381
  return rings;
47281
47382
  }
47383
+ function dropAntimeridianWrapSlivers(d, width, height) {
47384
+ const rings = parsePathRings(d);
47385
+ if (rings.length <= 1) return d;
47386
+ const eps = 0.75;
47387
+ const minArea = 3e-3 * width * height;
47388
+ const ringArea = (r) => {
47389
+ let s = 0;
47390
+ for (let i = 0; i < r.length; i++) {
47391
+ const a = r[i];
47392
+ const b = r[(i + 1) % r.length];
47393
+ s += a[0] * b[1] - b[0] * a[1];
47394
+ }
47395
+ return Math.abs(s) / 2;
47396
+ };
47397
+ const areas = rings.map(ringArea);
47398
+ const maxArea = Math.max(...areas);
47399
+ 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;
47400
+ let dropped = false;
47401
+ const kept = rings.filter((r, idx) => {
47402
+ if (areas[idx] >= maxArea || areas[idx] >= minArea) return true;
47403
+ const touches = r.some((p, i) => onVEdge(p, r[(i + 1) % r.length]));
47404
+ if (touches) {
47405
+ dropped = true;
47406
+ return false;
47407
+ }
47408
+ return true;
47409
+ });
47410
+ if (!dropped) return d;
47411
+ return kept.map(
47412
+ (r) => r.map((p, i) => (i ? "L" : "M") + p[0] + "," + p[1]).join("") + "Z"
47413
+ ).join("");
47414
+ }
47282
47415
  function layoutMap(resolved, data, size, opts) {
47283
47416
  const { palette, isDark } = opts;
47284
47417
  const { width, height } = size;
@@ -47362,7 +47495,7 @@ function layoutMap(resolved, data, size, opts) {
47362
47495
  const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
47363
47496
  const fillForValue = (s) => {
47364
47497
  const t = rampMax > rampMin ? (s - rampMin) / (rampMax - rampMin) : 1;
47365
- const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
47498
+ const pct = RAMP_FLOOR2 + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR2);
47366
47499
  return mix(rampHue, rampBase, pct);
47367
47500
  };
47368
47501
  const tagFill = (tags, groupName) => {
@@ -47421,10 +47554,11 @@ function layoutMap(resolved, data, size, opts) {
47421
47554
  const by0 = cb[0][1];
47422
47555
  const cw = cb[1][0] - bx0;
47423
47556
  const ch = cb[1][1] - by0;
47424
- const ox = fitBox[0][0];
47425
- const oy = fitBox[0][1];
47426
- const sx = cw > 0 ? (fitBox[1][0] - ox) / cw : 1;
47427
- const sy = ch > 0 ? (fitBox[1][1] - oy) / ch : 1;
47557
+ const topReserve = resolved.title && resolved.pois.length > 0 ? topPad : 0;
47558
+ const ox = 0;
47559
+ const oy = topReserve;
47560
+ const sx = cw > 0 ? width / cw : 1;
47561
+ const sy = ch > 0 ? (height - topReserve) / ch : 1;
47428
47562
  stretchParams = { sx, sy, ox, oy, bx0, by0 };
47429
47563
  const stretch = (x, y) => [
47430
47564
  ox + (x - bx0) * sx,
@@ -47697,7 +47831,8 @@ function layoutMap(resolved, data, size, opts) {
47697
47831
  const r = regionById.get(iso);
47698
47832
  const viewF = shouldCull ? cullFeatureToView(f) : dropFrameFillers(f);
47699
47833
  if (!viewF) continue;
47700
- const d = path(viewF) ?? "";
47834
+ const raw = path(viewF) ?? "";
47835
+ const d = fitIsGlobal ? dropAntimeridianWrapSlivers(raw, width, height) : raw;
47701
47836
  if (!d) continue;
47702
47837
  const isThisLayer = r?.layer === layerKind;
47703
47838
  const isForeign = layerKind === "country" && usContext && iso !== "US";
@@ -47714,6 +47849,9 @@ function layoutMap(resolved, data, size, opts) {
47714
47849
  } else {
47715
47850
  label = f.properties?.name;
47716
47851
  }
47852
+ const labelAnchor = WORLD_LABEL_ANCHORS[iso];
47853
+ const c = labelAnchor ? project(labelAnchor[0], labelAnchor[1]) : path.centroid(viewF);
47854
+ const hasCentroid = c != null && Number.isFinite(c[0]) && Number.isFinite(c[1]);
47717
47855
  regions.push({
47718
47856
  id: iso,
47719
47857
  d,
@@ -47722,6 +47860,7 @@ function layoutMap(resolved, data, size, opts) {
47722
47860
  lineNumber,
47723
47861
  layer,
47724
47862
  ...label !== void 0 && { label },
47863
+ ...hasCentroid && { labelX: c[0], labelY: c[1] },
47725
47864
  ...isThisLayer && r.value !== void 0 && { value: r.value },
47726
47865
  ...isThisLayer && Object.keys(r.tags).length > 0 && { tags: r.tags }
47727
47866
  });
@@ -48162,10 +48301,6 @@ function layoutMap(resolved, data, size, opts) {
48162
48301
  lineNumber
48163
48302
  });
48164
48303
  };
48165
- const WORLD_LABEL_ANCHORS = {
48166
- US: [-98.5, 39.5]
48167
- // CONUS geographic centre (near Lebanon, Kansas)
48168
- };
48169
48304
  const REGION_LABEL_GAP = 2;
48170
48305
  const regionLabelRect = (cx, cy, text) => {
48171
48306
  const w = measureLegendText(text, FONT2) + 2 * REGION_LABEL_GAP;
@@ -48531,7 +48666,7 @@ function layoutMap(resolved, data, size, opts) {
48531
48666
  diagnostics: []
48532
48667
  };
48533
48668
  }
48534
- var import_d3_geo2, import_topojson_client2, 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;
48669
+ var import_d3_geo2, import_topojson_client2, 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;
48535
48670
  var init_layout15 = __esm({
48536
48671
  "src/map/layout.ts"() {
48537
48672
  "use strict";
@@ -48546,13 +48681,17 @@ var init_layout15 = __esm({
48546
48681
  init_title_constants();
48547
48682
  init_context_labels();
48548
48683
  FIT_PAD = 24;
48549
- RAMP_FLOOR = 15;
48684
+ RAMP_FLOOR2 = 15;
48550
48685
  R_DEFAULT = 6;
48551
48686
  R_MIN = 4;
48552
48687
  R_MAX = 22;
48553
48688
  W_MIN = 1.25;
48554
48689
  W_MAX = 8;
48555
48690
  FONT2 = 11;
48691
+ WORLD_LABEL_ANCHORS = {
48692
+ US: [-98.5, 39.5]
48693
+ // CONUS geographic centre (near Lebanon, Kansas)
48694
+ };
48556
48695
  MAX_CLUSTER_EXTENT_FACTOR = 0.18;
48557
48696
  MAX_COLUMN_ROWS = 7;
48558
48697
  REGION_LABEL_HALO_RATIO = 4.5;
@@ -48566,9 +48705,9 @@ var init_layout15 = __esm({
48566
48705
  COMPACT_WIDTH_PX = 480;
48567
48706
  RELIEF_MIN_AREA = 12;
48568
48707
  RELIEF_MIN_DIM = 2;
48569
- RELIEF_HATCH_SPACING = 2;
48570
- RELIEF_HATCH_WIDTH = 0.15;
48571
- RELIEF_HATCH_STRENGTH = 32;
48708
+ RELIEF_HATCH_SPACING = 1.5;
48709
+ RELIEF_HATCH_WIDTH = 0.2;
48710
+ RELIEF_HATCH_STRENGTH = 26;
48572
48711
  COASTLINE_RING_COUNT = 5;
48573
48712
  COASTLINE_D0 = 16e-4;
48574
48713
  COASTLINE_STEP = 28e-4;
@@ -48646,7 +48785,47 @@ function ringToPath(ring) {
48646
48785
  d += (i ? "L" : "M") + ring[i][0] + "," + ring[i][1];
48647
48786
  return d + "Z";
48648
48787
  }
48649
- function coastlineOuterRings(regions, minExtent) {
48788
+ function polylineToPath(pts) {
48789
+ let d = "";
48790
+ for (let i = 0; i < pts.length; i++)
48791
+ d += (i ? "L" : "M") + pts[i][0] + "," + pts[i][1];
48792
+ return d;
48793
+ }
48794
+ function ringToCoastPaths(ring, frame) {
48795
+ if (!frame) return [ringToPath(ring)];
48796
+ const n = ring.length;
48797
+ const eps = 0.75;
48798
+ const onL = (x) => Math.abs(x) <= eps;
48799
+ const onR = (x) => Math.abs(x - frame.w) <= eps;
48800
+ const onT = (y) => Math.abs(y) <= eps;
48801
+ const onB = (y) => Math.abs(y - frame.h) <= eps;
48802
+ 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]);
48803
+ let firstBreak = -1;
48804
+ for (let i = 0; i < n; i++)
48805
+ if (isFrameEdge(ring[i], ring[(i + 1) % n])) {
48806
+ firstBreak = i;
48807
+ break;
48808
+ }
48809
+ if (firstBreak === -1) return [ringToPath(ring)];
48810
+ const paths = [];
48811
+ let cur = [];
48812
+ const start = (firstBreak + 1) % n;
48813
+ for (let k = 0; k < n; k++) {
48814
+ const i = (start + k) % n;
48815
+ const a = ring[i];
48816
+ const b = ring[(i + 1) % n];
48817
+ if (isFrameEdge(a, b)) {
48818
+ if (cur.length >= 2) paths.push(polylineToPath(cur));
48819
+ cur = [];
48820
+ continue;
48821
+ }
48822
+ if (cur.length === 0) cur.push(a);
48823
+ cur.push(b);
48824
+ }
48825
+ if (cur.length >= 2) paths.push(polylineToPath(cur));
48826
+ return paths;
48827
+ }
48828
+ function coastlineOuterRings(regions, minExtent, frame) {
48650
48829
  const paths = [];
48651
48830
  for (const r of regions) {
48652
48831
  const rings = parsePathRings(r.d);
@@ -48669,7 +48848,7 @@ function coastlineOuterRings(regions, minExtent) {
48669
48848
  for (let j = 0; j < rings.length; j++)
48670
48849
  if (j !== i && pointInRing2(fx, fy, rings[j])) depth++;
48671
48850
  if (depth % 2 === 1) continue;
48672
- paths.push(ringToPath(ring));
48851
+ paths.push(...ringToCoastPaths(ring, frame));
48673
48852
  }
48674
48853
  }
48675
48854
  return paths;
@@ -48713,6 +48892,10 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
48713
48892
  const drawRegion = (g, r, strokeWidth) => {
48714
48893
  const p = g.append("path").attr("d", r.d).attr("fill", r.fill).attr("stroke", r.stroke).attr("stroke-width", strokeWidth);
48715
48894
  if (r.label) p.attr("data-region-name", r.label);
48895
+ if (r.id && r.id !== "lake") p.attr("data-iso", r.id);
48896
+ if (r.labelX !== void 0 && r.labelY !== void 0) {
48897
+ p.attr("data-label-x", r.labelX).attr("data-label-y", r.labelY);
48898
+ }
48716
48899
  if (r.layer !== "base") {
48717
48900
  p.classed("dgmo-map-region", true).attr("data-region", r.id);
48718
48901
  if (r.value !== void 0) p.attr("data-value", r.value);
@@ -48742,7 +48925,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
48742
48925
  const landClip = defs.append("clipPath").attr("id", landClipId);
48743
48926
  for (const r of layout.regions)
48744
48927
  if (r.id !== "lake") landClip.append("path").attr("d", r.d);
48745
- 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");
48928
+ 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");
48746
48929
  for (let y = h.spacing; y < height; y += h.spacing) {
48747
48930
  gRelief.append("line").attr("x1", 0).attr("y1", y).attr("x2", width).attr("y2", y);
48748
48931
  }
@@ -48763,10 +48946,16 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
48763
48946
  mask.append("path").attr("d", d).attr("fill", "black").attr("stroke", "black").attr("stroke-width", 2 * reach).attr("stroke-linejoin", "round");
48764
48947
  }
48765
48948
  }
48766
- const gWater = svg.append("g").attr("class", "dgmo-map-water-lines").attr("fill", "none").attr("mask", `url(#${maskId})`);
48949
+ const gWater = svg.append("g").attr("class", "dgmo-map-water-lines").attr("fill", "none").style("pointer-events", "none").attr("mask", `url(#${maskId})`);
48767
48950
  appendWaterLines(
48768
48951
  gWater,
48769
- coastlineOuterRings(layout.regions, cs.minExtent),
48952
+ // Pass the canvas frame so edges collinear with it (the antimeridian on a
48953
+ // world map, regional clipExtent cuts) don't get ringed as fake coast —
48954
+ // land runs cleanly to the render-area edge.
48955
+ coastlineOuterRings(layout.regions, cs.minExtent, {
48956
+ w: width,
48957
+ h: height
48958
+ }),
48770
48959
  cs,
48771
48960
  layout.background
48772
48961
  );
@@ -48780,7 +48969,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
48780
48969
  gWater.append("path").attr("d", ds.join(" ")).attr("stroke", stroke2).attr("stroke-width", 0.5).attr("stroke-linejoin", "round");
48781
48970
  }
48782
48971
  if (layout.rivers.length) {
48783
- const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none");
48972
+ const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none").style("pointer-events", "none");
48784
48973
  for (const r of layout.rivers) {
48785
48974
  gRivers.append("path").attr("d", r.d).attr("stroke", r.color).attr("stroke-width", r.width).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
48786
48975
  }
@@ -48821,7 +49010,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
48821
49010
  const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
48822
49011
  clip.append("path").attr("d", d);
48823
49012
  }
48824
- 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})`);
49013
+ 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})`);
48825
49014
  appendWaterLines(
48826
49015
  gInsetWater,
48827
49016
  coastlineOuterRings(layout.insetRegions, cs.minExtent),
@@ -54839,10 +55028,12 @@ function renderTimelineHorizontalTimeSort(container, parsed, palette, isDark, se
54839
55028
  const markerLabelY = markerReserve ? -(topScaleH + MARKER_ROW_H / 2) : 0;
54840
55029
  const eraLabelY = eraReserve ? -(topScaleH + markerReserve + ERA_ROW_H / 2) : 0;
54841
55030
  const innerWidth = width - margin.left - margin.right;
54842
- const innerHeight = height - margin.top - margin.bottom;
54843
- const rowH = Math.min(ctx.structural(28), innerHeight / sorted.length);
55031
+ const availInnerHeight = height - margin.top - margin.bottom;
55032
+ const rowH = Math.min(ctx.structural(28), availInnerHeight / sorted.length);
55033
+ const innerHeight = rowH * sorted.length;
55034
+ const usedHeight = margin.top + innerHeight + margin.bottom;
54844
55035
  const xScale = d3Scale2.scaleLinear().domain([minDate - datePadding, maxDate + datePadding]).range([0, innerWidth]);
54845
- 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);
55036
+ 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);
54846
55037
  if (ctx.isBelowFloor) {
54847
55038
  svg.attr("width", "100%");
54848
55039
  }