@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/auto.cjs CHANGED
@@ -820,9 +820,7 @@ var init_reserved_key_registry = __esm({
820
820
  BOXES_AND_LINES_REGISTRY = staticRegistry([
821
821
  "color",
822
822
  "description",
823
- "width",
824
- "split",
825
- "fanout"
823
+ "value"
826
824
  ]);
827
825
  TIMELINE_REGISTRY = staticRegistry([
828
826
  "color",
@@ -16824,6 +16822,21 @@ function parseBoxesAndLines(content) {
16824
16822
  }
16825
16823
  continue;
16826
16824
  }
16825
+ if (!contentStarted) {
16826
+ const metricMatch = trimmed.match(/^box-metric\s+(.+)$/i);
16827
+ if (metricMatch) {
16828
+ const { label, colorName } = peelTrailingColorName(
16829
+ metricMatch[1].trim()
16830
+ );
16831
+ result.boxMetric = label;
16832
+ if (colorName !== void 0) result.boxMetricColor = colorName;
16833
+ continue;
16834
+ }
16835
+ if (/^show-values$/i.test(trimmed)) {
16836
+ result.showValues = true;
16837
+ continue;
16838
+ }
16839
+ }
16827
16840
  if (!contentStarted) {
16828
16841
  const optMatch = trimmed.match(OPTION_NOCOLON_RE);
16829
16842
  if (optMatch) {
@@ -17202,6 +17215,19 @@ function parseNodeLine(trimmed, lineNum, metaAliasMap, diagnostics, nameAliasMap
17202
17215
  description = [metadata["description"]];
17203
17216
  delete metadata["description"];
17204
17217
  }
17218
+ let value;
17219
+ if (metadata["value"] !== void 0) {
17220
+ const raw = metadata["value"];
17221
+ const num = Number(raw);
17222
+ if (Number.isFinite(num)) {
17223
+ value = num;
17224
+ } else {
17225
+ diagnostics.push(
17226
+ makeDgmoError(lineNum, `value must be a number (got "${raw}")`, "error")
17227
+ );
17228
+ }
17229
+ delete metadata["value"];
17230
+ }
17205
17231
  if (split.alias) {
17206
17232
  nameAliasMap?.set(normalizeName(split.alias), label);
17207
17233
  }
@@ -17210,7 +17236,8 @@ function parseNodeLine(trimmed, lineNum, metaAliasMap, diagnostics, nameAliasMap
17210
17236
  label,
17211
17237
  lineNumber: lineNum,
17212
17238
  metadata,
17213
- ...description !== void 0 && { description }
17239
+ ...description !== void 0 && { description },
17240
+ ...value !== void 0 && { value }
17214
17241
  };
17215
17242
  }
17216
17243
  function splitTargetAndMeta(rest, metaAliasMap) {
@@ -26330,7 +26357,18 @@ function fitLabelToHeader(label, nodeWidth, maxLines) {
26330
26357
  const truncated = label.length > maxChars ? label.slice(0, maxChars - 1) + "\u2026" : label;
26331
26358
  return { lines: [truncated], fontSize: MIN_NODE_FONT_SIZE };
26332
26359
  }
26333
- function nodeColors(node, tagGroups, activeGroupName, palette, isDark, solid) {
26360
+ function nodeColors(node, tagGroups, activeGroupName, palette, isDark, value, solid) {
26361
+ const neutralFill = mix(palette.bg, palette.text, isDark ? 90 : 95);
26362
+ if (value.active) {
26363
+ const fill3 = node.value !== void 0 ? value.fillForValue(node.value) : neutralFill;
26364
+ const stroke3 = value.hue;
26365
+ const text2 = contrastText(
26366
+ fill3,
26367
+ palette.textOnFillLight,
26368
+ palette.textOnFillDark
26369
+ );
26370
+ return { fill: fill3, stroke: stroke3, text: text2 };
26371
+ }
26334
26372
  const tagColor = resolveTagColor(
26335
26373
  node.metadata,
26336
26374
  [...tagGroups],
@@ -26439,25 +26477,65 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26439
26477
  const sGroupLabelZone = sctx.structural(GROUP_LABEL_ZONE);
26440
26478
  const sTitleFontSize = sctx.text(TITLE_FONT_SIZE);
26441
26479
  const sTitleY = sctx.structural(TITLE_Y);
26480
+ const nodeValues = parsed.nodes.filter((n) => n.value !== void 0).map((n) => n.value);
26481
+ const hasRamp = nodeValues.length > 0;
26482
+ const allNonNegative = hasRamp && nodeValues.every((v) => v >= 0);
26483
+ const rampMin = allNonNegative ? 0 : Math.min(...nodeValues);
26484
+ const rampMax = Math.max(...nodeValues);
26485
+ const rampHue = resolveColor(parsed.boxMetricColor ?? "", palette) ?? palette.primary;
26486
+ const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
26487
+ const fillForValue = (v) => {
26488
+ const t = rampMax > rampMin ? (v - rampMin) / (rampMax - rampMin) : 1;
26489
+ const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
26490
+ return mix(rampHue, rampBase, pct);
26491
+ };
26492
+ const VALUE_NAME = hasRamp ? parsed.boxMetric?.trim() || "Value" : null;
26493
+ const matchColorGroup = (v) => {
26494
+ const lv = v.trim().toLowerCase();
26495
+ if (lv === "none") return null;
26496
+ const tg = parsed.tagGroups.find((g) => g.name.toLowerCase() === lv);
26497
+ if (tg) return tg.name;
26498
+ if (lv === VALUE_NAME?.toLowerCase()) return VALUE_NAME;
26499
+ return v;
26500
+ };
26501
+ const override = activeTagGroup;
26502
+ let activeGroup;
26503
+ if (override !== void 0) {
26504
+ activeGroup = override === null ? null : matchColorGroup(override);
26505
+ } else if (parsed.options["active-tag"] !== void 0) {
26506
+ activeGroup = matchColorGroup(parsed.options["active-tag"]);
26507
+ } else {
26508
+ activeGroup = VALUE_NAME ?? (parsed.tagGroups.length > 0 ? parsed.tagGroups[0].name : null);
26509
+ }
26510
+ const activeIsValue = VALUE_NAME !== null && activeGroup === VALUE_NAME;
26511
+ const valueGroup = VALUE_NAME !== null ? {
26512
+ name: VALUE_NAME,
26513
+ entries: [],
26514
+ gradient: {
26515
+ min: rampMin,
26516
+ max: rampMax,
26517
+ hue: rampHue,
26518
+ base: rampBase
26519
+ }
26520
+ } : null;
26521
+ const legendGroups = [
26522
+ ...valueGroup ? [valueGroup] : [],
26523
+ ...parsed.tagGroups
26524
+ ];
26442
26525
  const reserveHasDescriptions = parsed.nodes.some(
26443
26526
  (n) => n.description && n.description.length > 0
26444
26527
  );
26445
- const willRenderLegend = parsed.tagGroups.length > 0 || reserveHasDescriptions && controlsHost !== "app";
26528
+ const willRenderLegend = legendGroups.length > 0 || reserveHasDescriptions && controlsHost !== "app";
26446
26529
  const sLegendHeight = willRenderLegend ? sctx.structural(
26447
26530
  getMaxLegendReservedHeight(
26448
26531
  {
26449
- groups: parsed.tagGroups,
26532
+ groups: legendGroups,
26450
26533
  position: { placement: "top-center", titleRelation: "below-title" },
26451
26534
  mode: exportMode ? "export" : "preview"
26452
26535
  },
26453
26536
  width
26454
26537
  )
26455
26538
  ) : 0;
26456
- const activeGroup = resolveActiveTagGroup(
26457
- parsed.tagGroups,
26458
- parsed.options["active-tag"],
26459
- activeTagGroup
26460
- );
26461
26539
  const hidden = hiddenTagValues ?? parsed.initialHiddenTagValues;
26462
26540
  const nodeMap = /* @__PURE__ */ new Map();
26463
26541
  for (const node of parsed.nodes) nodeMap.set(node.label, node);
@@ -26468,7 +26546,7 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26468
26546
  const hasAnyDescriptions = parsed.nodes.some(
26469
26547
  (n) => n.description && n.description.length > 0
26470
26548
  );
26471
- const needsLegend = parsed.tagGroups.length > 0 || hasAnyDescriptions && onToggleDescriptions;
26549
+ const needsLegend = legendGroups.length > 0 || hasAnyDescriptions && onToggleDescriptions;
26472
26550
  const legendH = needsLegend ? sLegendHeight + 8 : 0;
26473
26551
  const groupLabelsSet = new Set(layout.groups.map((g) => g.label));
26474
26552
  let labelZoneExtension = 0;
@@ -26674,12 +26752,16 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26674
26752
  activeGroup,
26675
26753
  palette,
26676
26754
  isDark,
26755
+ { active: activeIsValue, hue: rampHue, fillForValue },
26677
26756
  parsed.options["solid-fill"] === "on"
26678
26757
  );
26679
26758
  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);
26680
26759
  for (const [key, val] of Object.entries(node.metadata)) {
26681
26760
  nodeG.attr(`data-tag-${key.toLowerCase()}`, val.toLowerCase());
26682
26761
  }
26762
+ if (node.value !== void 0) {
26763
+ nodeG.attr("data-value", node.value);
26764
+ }
26683
26765
  if (onClickItem) {
26684
26766
  nodeG.on("click", (event) => {
26685
26767
  const target = event.target;
@@ -26763,11 +26845,27 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26763
26845
  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]);
26764
26846
  }
26765
26847
  }
26848
+ if (parsed.showValues && node.value !== void 0) {
26849
+ const valueText = String(node.value);
26850
+ const descShown = !!(desc && desc.length > 0 && !hideDescriptions);
26851
+ if (descShown) {
26852
+ const padX = 6;
26853
+ const padY = 5;
26854
+ const bw = valueText.length * VALUE_FONT_SIZE * CHAR_WIDTH_RATIO2 + 8;
26855
+ const bh = VALUE_FONT_SIZE + 4;
26856
+ const bx = ln.width / 2 - bw - 4;
26857
+ const by = -ln.height / 2 + 4;
26858
+ 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);
26859
+ 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);
26860
+ } else {
26861
+ 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);
26862
+ }
26863
+ }
26766
26864
  }
26767
26865
  const hasDescriptions = parsed.nodes.some(
26768
26866
  (n) => n.description && n.description.length > 0
26769
26867
  );
26770
- const hasLegend = parsed.tagGroups.length > 0 || hasDescriptions && controlsHost !== "app";
26868
+ const hasLegend = legendGroups.length > 0 || hasDescriptions && controlsHost !== "app";
26771
26869
  if (hasLegend) {
26772
26870
  let controlsGroup;
26773
26871
  if (hasDescriptions && (onToggleDescriptions || controlsHost === "app")) {
@@ -26785,7 +26883,7 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26785
26883
  };
26786
26884
  }
26787
26885
  const legendConfig = {
26788
- groups: parsed.tagGroups,
26886
+ groups: legendGroups,
26789
26887
  position: { placement: "top-center", titleRelation: "below-title" },
26790
26888
  mode: exportMode ? "export" : "preview",
26791
26889
  // Keep inactive sibling tag groups visible as collapsed pills so the user
@@ -26840,7 +26938,7 @@ function renderBoxesAndLinesForExport(container, parsed, layout, palette, isDark
26840
26938
  }
26841
26939
  });
26842
26940
  }
26843
- 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;
26941
+ 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;
26844
26942
  var init_renderer6 = __esm({
26845
26943
  "src/boxes-and-lines/renderer.ts"() {
26846
26944
  "use strict";
@@ -26851,6 +26949,7 @@ var init_renderer6 = __esm({
26851
26949
  init_legend_layout();
26852
26950
  init_title_constants();
26853
26951
  init_color_utils();
26952
+ init_colors();
26854
26953
  init_tag_groups();
26855
26954
  init_inline_markdown();
26856
26955
  init_wrapped_desc();
@@ -26873,6 +26972,8 @@ var init_renderer6 = __esm({
26873
26972
  GROUP_RX = 8;
26874
26973
  GROUP_LABEL_FONT_SIZE = 14;
26875
26974
  GROUP_LABEL_ZONE = 32;
26975
+ RAMP_FLOOR = 15;
26976
+ VALUE_FONT_SIZE = 11;
26876
26977
  lineGeneratorLR = d3Shape4.line().x((d) => d.x).y((d) => d.y).curve(d3Shape4.curveBasis);
26877
26978
  lineGeneratorTB = d3Shape4.line().x((d) => d.x).y((d) => d.y).curve(d3Shape4.curveBasis);
26878
26979
  }
@@ -47204,6 +47305,38 @@ function parsePathRings(d) {
47204
47305
  if (cur.length) rings.push(cur);
47205
47306
  return rings;
47206
47307
  }
47308
+ function dropAntimeridianWrapSlivers(d, width, height) {
47309
+ const rings = parsePathRings(d);
47310
+ if (rings.length <= 1) return d;
47311
+ const eps = 0.75;
47312
+ const minArea = 3e-3 * width * height;
47313
+ const ringArea = (r) => {
47314
+ let s = 0;
47315
+ for (let i = 0; i < r.length; i++) {
47316
+ const a = r[i];
47317
+ const b = r[(i + 1) % r.length];
47318
+ s += a[0] * b[1] - b[0] * a[1];
47319
+ }
47320
+ return Math.abs(s) / 2;
47321
+ };
47322
+ const areas = rings.map(ringArea);
47323
+ const maxArea = Math.max(...areas);
47324
+ 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;
47325
+ let dropped = false;
47326
+ const kept = rings.filter((r, idx) => {
47327
+ if (areas[idx] >= maxArea || areas[idx] >= minArea) return true;
47328
+ const touches = r.some((p, i) => onVEdge(p, r[(i + 1) % r.length]));
47329
+ if (touches) {
47330
+ dropped = true;
47331
+ return false;
47332
+ }
47333
+ return true;
47334
+ });
47335
+ if (!dropped) return d;
47336
+ return kept.map(
47337
+ (r) => r.map((p, i) => (i ? "L" : "M") + p[0] + "," + p[1]).join("") + "Z"
47338
+ ).join("");
47339
+ }
47207
47340
  function layoutMap(resolved, data, size, opts) {
47208
47341
  const { palette, isDark } = opts;
47209
47342
  const { width, height } = size;
@@ -47287,7 +47420,7 @@ function layoutMap(resolved, data, size, opts) {
47287
47420
  const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
47288
47421
  const fillForValue = (s) => {
47289
47422
  const t = rampMax > rampMin ? (s - rampMin) / (rampMax - rampMin) : 1;
47290
- const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
47423
+ const pct = RAMP_FLOOR2 + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR2);
47291
47424
  return mix(rampHue, rampBase, pct);
47292
47425
  };
47293
47426
  const tagFill = (tags, groupName) => {
@@ -47346,10 +47479,11 @@ function layoutMap(resolved, data, size, opts) {
47346
47479
  const by0 = cb[0][1];
47347
47480
  const cw = cb[1][0] - bx0;
47348
47481
  const ch = cb[1][1] - by0;
47349
- const ox = fitBox[0][0];
47350
- const oy = fitBox[0][1];
47351
- const sx = cw > 0 ? (fitBox[1][0] - ox) / cw : 1;
47352
- const sy = ch > 0 ? (fitBox[1][1] - oy) / ch : 1;
47482
+ const topReserve = resolved.title && resolved.pois.length > 0 ? topPad : 0;
47483
+ const ox = 0;
47484
+ const oy = topReserve;
47485
+ const sx = cw > 0 ? width / cw : 1;
47486
+ const sy = ch > 0 ? (height - topReserve) / ch : 1;
47353
47487
  stretchParams = { sx, sy, ox, oy, bx0, by0 };
47354
47488
  const stretch = (x, y) => [
47355
47489
  ox + (x - bx0) * sx,
@@ -47622,7 +47756,8 @@ function layoutMap(resolved, data, size, opts) {
47622
47756
  const r = regionById.get(iso);
47623
47757
  const viewF = shouldCull ? cullFeatureToView(f) : dropFrameFillers(f);
47624
47758
  if (!viewF) continue;
47625
- const d = path(viewF) ?? "";
47759
+ const raw = path(viewF) ?? "";
47760
+ const d = fitIsGlobal ? dropAntimeridianWrapSlivers(raw, width, height) : raw;
47626
47761
  if (!d) continue;
47627
47762
  const isThisLayer = r?.layer === layerKind;
47628
47763
  const isForeign = layerKind === "country" && usContext && iso !== "US";
@@ -47639,6 +47774,9 @@ function layoutMap(resolved, data, size, opts) {
47639
47774
  } else {
47640
47775
  label = f.properties?.name;
47641
47776
  }
47777
+ const labelAnchor = WORLD_LABEL_ANCHORS[iso];
47778
+ const c = labelAnchor ? project(labelAnchor[0], labelAnchor[1]) : path.centroid(viewF);
47779
+ const hasCentroid = c != null && Number.isFinite(c[0]) && Number.isFinite(c[1]);
47642
47780
  regions.push({
47643
47781
  id: iso,
47644
47782
  d,
@@ -47647,6 +47785,7 @@ function layoutMap(resolved, data, size, opts) {
47647
47785
  lineNumber,
47648
47786
  layer,
47649
47787
  ...label !== void 0 && { label },
47788
+ ...hasCentroid && { labelX: c[0], labelY: c[1] },
47650
47789
  ...isThisLayer && r.value !== void 0 && { value: r.value },
47651
47790
  ...isThisLayer && Object.keys(r.tags).length > 0 && { tags: r.tags }
47652
47791
  });
@@ -48087,10 +48226,6 @@ function layoutMap(resolved, data, size, opts) {
48087
48226
  lineNumber
48088
48227
  });
48089
48228
  };
48090
- const WORLD_LABEL_ANCHORS = {
48091
- US: [-98.5, 39.5]
48092
- // CONUS geographic centre (near Lebanon, Kansas)
48093
- };
48094
48229
  const REGION_LABEL_GAP = 2;
48095
48230
  const regionLabelRect = (cx, cy, text) => {
48096
48231
  const w = measureLegendText(text, FONT2) + 2 * REGION_LABEL_GAP;
@@ -48456,7 +48591,7 @@ function layoutMap(resolved, data, size, opts) {
48456
48591
  diagnostics: []
48457
48592
  };
48458
48593
  }
48459
- 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;
48594
+ 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;
48460
48595
  var init_layout15 = __esm({
48461
48596
  "src/map/layout.ts"() {
48462
48597
  "use strict";
@@ -48471,13 +48606,17 @@ var init_layout15 = __esm({
48471
48606
  init_title_constants();
48472
48607
  init_context_labels();
48473
48608
  FIT_PAD = 24;
48474
- RAMP_FLOOR = 15;
48609
+ RAMP_FLOOR2 = 15;
48475
48610
  R_DEFAULT = 6;
48476
48611
  R_MIN = 4;
48477
48612
  R_MAX = 22;
48478
48613
  W_MIN = 1.25;
48479
48614
  W_MAX = 8;
48480
48615
  FONT2 = 11;
48616
+ WORLD_LABEL_ANCHORS = {
48617
+ US: [-98.5, 39.5]
48618
+ // CONUS geographic centre (near Lebanon, Kansas)
48619
+ };
48481
48620
  MAX_CLUSTER_EXTENT_FACTOR = 0.18;
48482
48621
  MAX_COLUMN_ROWS = 7;
48483
48622
  REGION_LABEL_HALO_RATIO = 4.5;
@@ -48491,9 +48630,9 @@ var init_layout15 = __esm({
48491
48630
  COMPACT_WIDTH_PX = 480;
48492
48631
  RELIEF_MIN_AREA = 12;
48493
48632
  RELIEF_MIN_DIM = 2;
48494
- RELIEF_HATCH_SPACING = 2;
48495
- RELIEF_HATCH_WIDTH = 0.15;
48496
- RELIEF_HATCH_STRENGTH = 32;
48633
+ RELIEF_HATCH_SPACING = 1.5;
48634
+ RELIEF_HATCH_WIDTH = 0.2;
48635
+ RELIEF_HATCH_STRENGTH = 26;
48497
48636
  COASTLINE_RING_COUNT = 5;
48498
48637
  COASTLINE_D0 = 16e-4;
48499
48638
  COASTLINE_STEP = 28e-4;
@@ -48571,7 +48710,47 @@ function ringToPath(ring) {
48571
48710
  d += (i ? "L" : "M") + ring[i][0] + "," + ring[i][1];
48572
48711
  return d + "Z";
48573
48712
  }
48574
- function coastlineOuterRings(regions, minExtent) {
48713
+ function polylineToPath(pts) {
48714
+ let d = "";
48715
+ for (let i = 0; i < pts.length; i++)
48716
+ d += (i ? "L" : "M") + pts[i][0] + "," + pts[i][1];
48717
+ return d;
48718
+ }
48719
+ function ringToCoastPaths(ring, frame) {
48720
+ if (!frame) return [ringToPath(ring)];
48721
+ const n = ring.length;
48722
+ const eps = 0.75;
48723
+ const onL = (x) => Math.abs(x) <= eps;
48724
+ const onR = (x) => Math.abs(x - frame.w) <= eps;
48725
+ const onT = (y) => Math.abs(y) <= eps;
48726
+ const onB = (y) => Math.abs(y - frame.h) <= eps;
48727
+ 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]);
48728
+ let firstBreak = -1;
48729
+ for (let i = 0; i < n; i++)
48730
+ if (isFrameEdge(ring[i], ring[(i + 1) % n])) {
48731
+ firstBreak = i;
48732
+ break;
48733
+ }
48734
+ if (firstBreak === -1) return [ringToPath(ring)];
48735
+ const paths = [];
48736
+ let cur = [];
48737
+ const start = (firstBreak + 1) % n;
48738
+ for (let k = 0; k < n; k++) {
48739
+ const i = (start + k) % n;
48740
+ const a = ring[i];
48741
+ const b = ring[(i + 1) % n];
48742
+ if (isFrameEdge(a, b)) {
48743
+ if (cur.length >= 2) paths.push(polylineToPath(cur));
48744
+ cur = [];
48745
+ continue;
48746
+ }
48747
+ if (cur.length === 0) cur.push(a);
48748
+ cur.push(b);
48749
+ }
48750
+ if (cur.length >= 2) paths.push(polylineToPath(cur));
48751
+ return paths;
48752
+ }
48753
+ function coastlineOuterRings(regions, minExtent, frame) {
48575
48754
  const paths = [];
48576
48755
  for (const r of regions) {
48577
48756
  const rings = parsePathRings(r.d);
@@ -48594,7 +48773,7 @@ function coastlineOuterRings(regions, minExtent) {
48594
48773
  for (let j = 0; j < rings.length; j++)
48595
48774
  if (j !== i && pointInRing2(fx, fy, rings[j])) depth++;
48596
48775
  if (depth % 2 === 1) continue;
48597
- paths.push(ringToPath(ring));
48776
+ paths.push(...ringToCoastPaths(ring, frame));
48598
48777
  }
48599
48778
  }
48600
48779
  return paths;
@@ -48638,6 +48817,10 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
48638
48817
  const drawRegion = (g, r, strokeWidth) => {
48639
48818
  const p = g.append("path").attr("d", r.d).attr("fill", r.fill).attr("stroke", r.stroke).attr("stroke-width", strokeWidth);
48640
48819
  if (r.label) p.attr("data-region-name", r.label);
48820
+ if (r.id && r.id !== "lake") p.attr("data-iso", r.id);
48821
+ if (r.labelX !== void 0 && r.labelY !== void 0) {
48822
+ p.attr("data-label-x", r.labelX).attr("data-label-y", r.labelY);
48823
+ }
48641
48824
  if (r.layer !== "base") {
48642
48825
  p.classed("dgmo-map-region", true).attr("data-region", r.id);
48643
48826
  if (r.value !== void 0) p.attr("data-value", r.value);
@@ -48667,7 +48850,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
48667
48850
  const landClip = defs.append("clipPath").attr("id", landClipId);
48668
48851
  for (const r of layout.regions)
48669
48852
  if (r.id !== "lake") landClip.append("path").attr("d", r.d);
48670
- 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");
48853
+ 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");
48671
48854
  for (let y = h.spacing; y < height; y += h.spacing) {
48672
48855
  gRelief.append("line").attr("x1", 0).attr("y1", y).attr("x2", width).attr("y2", y);
48673
48856
  }
@@ -48688,10 +48871,16 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
48688
48871
  mask.append("path").attr("d", d).attr("fill", "black").attr("stroke", "black").attr("stroke-width", 2 * reach).attr("stroke-linejoin", "round");
48689
48872
  }
48690
48873
  }
48691
- const gWater = svg.append("g").attr("class", "dgmo-map-water-lines").attr("fill", "none").attr("mask", `url(#${maskId})`);
48874
+ const gWater = svg.append("g").attr("class", "dgmo-map-water-lines").attr("fill", "none").style("pointer-events", "none").attr("mask", `url(#${maskId})`);
48692
48875
  appendWaterLines(
48693
48876
  gWater,
48694
- coastlineOuterRings(layout.regions, cs.minExtent),
48877
+ // Pass the canvas frame so edges collinear with it (the antimeridian on a
48878
+ // world map, regional clipExtent cuts) don't get ringed as fake coast —
48879
+ // land runs cleanly to the render-area edge.
48880
+ coastlineOuterRings(layout.regions, cs.minExtent, {
48881
+ w: width,
48882
+ h: height
48883
+ }),
48695
48884
  cs,
48696
48885
  layout.background
48697
48886
  );
@@ -48705,7 +48894,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
48705
48894
  gWater.append("path").attr("d", ds.join(" ")).attr("stroke", stroke2).attr("stroke-width", 0.5).attr("stroke-linejoin", "round");
48706
48895
  }
48707
48896
  if (layout.rivers.length) {
48708
- const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none");
48897
+ const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none").style("pointer-events", "none");
48709
48898
  for (const r of layout.rivers) {
48710
48899
  gRivers.append("path").attr("d", r.d).attr("stroke", r.color).attr("stroke-width", r.width).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
48711
48900
  }
@@ -48746,7 +48935,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
48746
48935
  const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
48747
48936
  clip.append("path").attr("d", d);
48748
48937
  }
48749
- 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})`);
48938
+ 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})`);
48750
48939
  appendWaterLines(
48751
48940
  gInsetWater,
48752
48941
  coastlineOuterRings(layout.insetRegions, cs.minExtent),
@@ -54764,10 +54953,12 @@ function renderTimelineHorizontalTimeSort(container, parsed, palette, isDark, se
54764
54953
  const markerLabelY = markerReserve ? -(topScaleH + MARKER_ROW_H / 2) : 0;
54765
54954
  const eraLabelY = eraReserve ? -(topScaleH + markerReserve + ERA_ROW_H / 2) : 0;
54766
54955
  const innerWidth = width - margin.left - margin.right;
54767
- const innerHeight = height - margin.top - margin.bottom;
54768
- const rowH = Math.min(ctx.structural(28), innerHeight / sorted.length);
54956
+ const availInnerHeight = height - margin.top - margin.bottom;
54957
+ const rowH = Math.min(ctx.structural(28), availInnerHeight / sorted.length);
54958
+ const innerHeight = rowH * sorted.length;
54959
+ const usedHeight = margin.top + innerHeight + margin.bottom;
54769
54960
  const xScale = d3Scale2.scaleLinear().domain([minDate - datePadding, maxDate + datePadding]).range([0, innerWidth]);
54770
- 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);
54961
+ 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);
54771
54962
  if (ctx.isBelowFloor) {
54772
54963
  svg.attr("width", "100%");
54773
54964
  }
@@ -58298,6 +58489,9 @@ var DIRECTIVE_KEYWORDS = /* @__PURE__ */ new Set([
58298
58489
  "hide",
58299
58490
  "mode",
58300
58491
  "direction",
58492
+ // Boxes-and-lines
58493
+ "box-metric",
58494
+ "show-values",
58301
58495
  // ER
58302
58496
  "notation",
58303
58497
  // Class
@@ -59020,7 +59214,7 @@ pre.dgmo, code.language-dgmo, pre > code.language-dgmo,
59020
59214
 
59021
59215
  // src/auto/index.ts
59022
59216
  init_safe_href();
59023
- var VERSION = "0.22.0";
59217
+ var VERSION = "0.23.0";
59024
59218
  var DEFAULTS = {
59025
59219
  theme: "auto",
59026
59220
  palette: "nord",