@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.mjs CHANGED
@@ -818,9 +818,7 @@ var init_reserved_key_registry = __esm({
818
818
  BOXES_AND_LINES_REGISTRY = staticRegistry([
819
819
  "color",
820
820
  "description",
821
- "width",
822
- "split",
823
- "fanout"
821
+ "value"
824
822
  ]);
825
823
  TIMELINE_REGISTRY = staticRegistry([
826
824
  "color",
@@ -16840,6 +16838,21 @@ function parseBoxesAndLines(content) {
16840
16838
  }
16841
16839
  continue;
16842
16840
  }
16841
+ if (!contentStarted) {
16842
+ const metricMatch = trimmed.match(/^box-metric\s+(.+)$/i);
16843
+ if (metricMatch) {
16844
+ const { label, colorName } = peelTrailingColorName(
16845
+ metricMatch[1].trim()
16846
+ );
16847
+ result.boxMetric = label;
16848
+ if (colorName !== void 0) result.boxMetricColor = colorName;
16849
+ continue;
16850
+ }
16851
+ if (/^show-values$/i.test(trimmed)) {
16852
+ result.showValues = true;
16853
+ continue;
16854
+ }
16855
+ }
16843
16856
  if (!contentStarted) {
16844
16857
  const optMatch = trimmed.match(OPTION_NOCOLON_RE);
16845
16858
  if (optMatch) {
@@ -17218,6 +17231,19 @@ function parseNodeLine(trimmed, lineNum, metaAliasMap, diagnostics, nameAliasMap
17218
17231
  description = [metadata["description"]];
17219
17232
  delete metadata["description"];
17220
17233
  }
17234
+ let value;
17235
+ if (metadata["value"] !== void 0) {
17236
+ const raw = metadata["value"];
17237
+ const num = Number(raw);
17238
+ if (Number.isFinite(num)) {
17239
+ value = num;
17240
+ } else {
17241
+ diagnostics.push(
17242
+ makeDgmoError(lineNum, `value must be a number (got "${raw}")`, "error")
17243
+ );
17244
+ }
17245
+ delete metadata["value"];
17246
+ }
17221
17247
  if (split.alias) {
17222
17248
  nameAliasMap?.set(normalizeName(split.alias), label);
17223
17249
  }
@@ -17226,7 +17252,8 @@ function parseNodeLine(trimmed, lineNum, metaAliasMap, diagnostics, nameAliasMap
17226
17252
  label,
17227
17253
  lineNumber: lineNum,
17228
17254
  metadata,
17229
- ...description !== void 0 && { description }
17255
+ ...description !== void 0 && { description },
17256
+ ...value !== void 0 && { value }
17230
17257
  };
17231
17258
  }
17232
17259
  function splitTargetAndMeta(rest, metaAliasMap) {
@@ -26348,7 +26375,18 @@ function fitLabelToHeader(label, nodeWidth, maxLines) {
26348
26375
  const truncated = label.length > maxChars ? label.slice(0, maxChars - 1) + "\u2026" : label;
26349
26376
  return { lines: [truncated], fontSize: MIN_NODE_FONT_SIZE };
26350
26377
  }
26351
- function nodeColors(node, tagGroups, activeGroupName, palette, isDark, solid) {
26378
+ function nodeColors(node, tagGroups, activeGroupName, palette, isDark, value, solid) {
26379
+ const neutralFill = mix(palette.bg, palette.text, isDark ? 90 : 95);
26380
+ if (value.active) {
26381
+ const fill3 = node.value !== void 0 ? value.fillForValue(node.value) : neutralFill;
26382
+ const stroke3 = value.hue;
26383
+ const text2 = contrastText(
26384
+ fill3,
26385
+ palette.textOnFillLight,
26386
+ palette.textOnFillDark
26387
+ );
26388
+ return { fill: fill3, stroke: stroke3, text: text2 };
26389
+ }
26352
26390
  const tagColor = resolveTagColor(
26353
26391
  node.metadata,
26354
26392
  [...tagGroups],
@@ -26457,25 +26495,65 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26457
26495
  const sGroupLabelZone = sctx.structural(GROUP_LABEL_ZONE);
26458
26496
  const sTitleFontSize = sctx.text(TITLE_FONT_SIZE);
26459
26497
  const sTitleY = sctx.structural(TITLE_Y);
26498
+ const nodeValues = parsed.nodes.filter((n) => n.value !== void 0).map((n) => n.value);
26499
+ const hasRamp = nodeValues.length > 0;
26500
+ const allNonNegative = hasRamp && nodeValues.every((v) => v >= 0);
26501
+ const rampMin = allNonNegative ? 0 : Math.min(...nodeValues);
26502
+ const rampMax = Math.max(...nodeValues);
26503
+ const rampHue = resolveColor(parsed.boxMetricColor ?? "", palette) ?? palette.primary;
26504
+ const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
26505
+ const fillForValue = (v) => {
26506
+ const t = rampMax > rampMin ? (v - rampMin) / (rampMax - rampMin) : 1;
26507
+ const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
26508
+ return mix(rampHue, rampBase, pct);
26509
+ };
26510
+ const VALUE_NAME = hasRamp ? parsed.boxMetric?.trim() || "Value" : null;
26511
+ const matchColorGroup = (v) => {
26512
+ const lv = v.trim().toLowerCase();
26513
+ if (lv === "none") return null;
26514
+ const tg = parsed.tagGroups.find((g) => g.name.toLowerCase() === lv);
26515
+ if (tg) return tg.name;
26516
+ if (lv === VALUE_NAME?.toLowerCase()) return VALUE_NAME;
26517
+ return v;
26518
+ };
26519
+ const override = activeTagGroup;
26520
+ let activeGroup;
26521
+ if (override !== void 0) {
26522
+ activeGroup = override === null ? null : matchColorGroup(override);
26523
+ } else if (parsed.options["active-tag"] !== void 0) {
26524
+ activeGroup = matchColorGroup(parsed.options["active-tag"]);
26525
+ } else {
26526
+ activeGroup = VALUE_NAME ?? (parsed.tagGroups.length > 0 ? parsed.tagGroups[0].name : null);
26527
+ }
26528
+ const activeIsValue = VALUE_NAME !== null && activeGroup === VALUE_NAME;
26529
+ const valueGroup = VALUE_NAME !== null ? {
26530
+ name: VALUE_NAME,
26531
+ entries: [],
26532
+ gradient: {
26533
+ min: rampMin,
26534
+ max: rampMax,
26535
+ hue: rampHue,
26536
+ base: rampBase
26537
+ }
26538
+ } : null;
26539
+ const legendGroups = [
26540
+ ...valueGroup ? [valueGroup] : [],
26541
+ ...parsed.tagGroups
26542
+ ];
26460
26543
  const reserveHasDescriptions = parsed.nodes.some(
26461
26544
  (n) => n.description && n.description.length > 0
26462
26545
  );
26463
- const willRenderLegend = parsed.tagGroups.length > 0 || reserveHasDescriptions && controlsHost !== "app";
26546
+ const willRenderLegend = legendGroups.length > 0 || reserveHasDescriptions && controlsHost !== "app";
26464
26547
  const sLegendHeight = willRenderLegend ? sctx.structural(
26465
26548
  getMaxLegendReservedHeight(
26466
26549
  {
26467
- groups: parsed.tagGroups,
26550
+ groups: legendGroups,
26468
26551
  position: { placement: "top-center", titleRelation: "below-title" },
26469
26552
  mode: exportMode ? "export" : "preview"
26470
26553
  },
26471
26554
  width
26472
26555
  )
26473
26556
  ) : 0;
26474
- const activeGroup = resolveActiveTagGroup(
26475
- parsed.tagGroups,
26476
- parsed.options["active-tag"],
26477
- activeTagGroup
26478
- );
26479
26557
  const hidden = hiddenTagValues ?? parsed.initialHiddenTagValues;
26480
26558
  const nodeMap = /* @__PURE__ */ new Map();
26481
26559
  for (const node of parsed.nodes) nodeMap.set(node.label, node);
@@ -26486,7 +26564,7 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26486
26564
  const hasAnyDescriptions = parsed.nodes.some(
26487
26565
  (n) => n.description && n.description.length > 0
26488
26566
  );
26489
- const needsLegend = parsed.tagGroups.length > 0 || hasAnyDescriptions && onToggleDescriptions;
26567
+ const needsLegend = legendGroups.length > 0 || hasAnyDescriptions && onToggleDescriptions;
26490
26568
  const legendH = needsLegend ? sLegendHeight + 8 : 0;
26491
26569
  const groupLabelsSet = new Set(layout.groups.map((g) => g.label));
26492
26570
  let labelZoneExtension = 0;
@@ -26692,12 +26770,16 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26692
26770
  activeGroup,
26693
26771
  palette,
26694
26772
  isDark,
26773
+ { active: activeIsValue, hue: rampHue, fillForValue },
26695
26774
  parsed.options["solid-fill"] === "on"
26696
26775
  );
26697
26776
  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);
26698
26777
  for (const [key, val] of Object.entries(node.metadata)) {
26699
26778
  nodeG.attr(`data-tag-${key.toLowerCase()}`, val.toLowerCase());
26700
26779
  }
26780
+ if (node.value !== void 0) {
26781
+ nodeG.attr("data-value", node.value);
26782
+ }
26701
26783
  if (onClickItem) {
26702
26784
  nodeG.on("click", (event) => {
26703
26785
  const target = event.target;
@@ -26781,11 +26863,27 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26781
26863
  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]);
26782
26864
  }
26783
26865
  }
26866
+ if (parsed.showValues && node.value !== void 0) {
26867
+ const valueText = String(node.value);
26868
+ const descShown = !!(desc && desc.length > 0 && !hideDescriptions);
26869
+ if (descShown) {
26870
+ const padX = 6;
26871
+ const padY = 5;
26872
+ const bw = valueText.length * VALUE_FONT_SIZE * CHAR_WIDTH_RATIO2 + 8;
26873
+ const bh = VALUE_FONT_SIZE + 4;
26874
+ const bx = ln.width / 2 - bw - 4;
26875
+ const by = -ln.height / 2 + 4;
26876
+ 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);
26877
+ 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);
26878
+ } else {
26879
+ 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);
26880
+ }
26881
+ }
26784
26882
  }
26785
26883
  const hasDescriptions = parsed.nodes.some(
26786
26884
  (n) => n.description && n.description.length > 0
26787
26885
  );
26788
- const hasLegend = parsed.tagGroups.length > 0 || hasDescriptions && controlsHost !== "app";
26886
+ const hasLegend = legendGroups.length > 0 || hasDescriptions && controlsHost !== "app";
26789
26887
  if (hasLegend) {
26790
26888
  let controlsGroup;
26791
26889
  if (hasDescriptions && (onToggleDescriptions || controlsHost === "app")) {
@@ -26803,7 +26901,7 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26803
26901
  };
26804
26902
  }
26805
26903
  const legendConfig = {
26806
- groups: parsed.tagGroups,
26904
+ groups: legendGroups,
26807
26905
  position: { placement: "top-center", titleRelation: "below-title" },
26808
26906
  mode: exportMode ? "export" : "preview",
26809
26907
  // Keep inactive sibling tag groups visible as collapsed pills so the user
@@ -26858,7 +26956,7 @@ function renderBoxesAndLinesForExport(container, parsed, layout, palette, isDark
26858
26956
  }
26859
26957
  });
26860
26958
  }
26861
- 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;
26959
+ 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;
26862
26960
  var init_renderer6 = __esm({
26863
26961
  "src/boxes-and-lines/renderer.ts"() {
26864
26962
  "use strict";
@@ -26867,6 +26965,7 @@ var init_renderer6 = __esm({
26867
26965
  init_legend_layout();
26868
26966
  init_title_constants();
26869
26967
  init_color_utils();
26968
+ init_colors();
26870
26969
  init_tag_groups();
26871
26970
  init_inline_markdown();
26872
26971
  init_wrapped_desc();
@@ -26889,6 +26988,8 @@ var init_renderer6 = __esm({
26889
26988
  GROUP_RX = 8;
26890
26989
  GROUP_LABEL_FONT_SIZE = 14;
26891
26990
  GROUP_LABEL_ZONE = 32;
26991
+ RAMP_FLOOR = 15;
26992
+ VALUE_FONT_SIZE = 11;
26892
26993
  lineGeneratorLR = d3Shape4.line().x((d) => d.x).y((d) => d.y).curve(d3Shape4.curveBasis);
26893
26994
  lineGeneratorTB = d3Shape4.line().x((d) => d.x).y((d) => d.y).curve(d3Shape4.curveBasis);
26894
26995
  }
@@ -47231,6 +47332,38 @@ function parsePathRings(d) {
47231
47332
  if (cur.length) rings.push(cur);
47232
47333
  return rings;
47233
47334
  }
47335
+ function dropAntimeridianWrapSlivers(d, width, height) {
47336
+ const rings = parsePathRings(d);
47337
+ if (rings.length <= 1) return d;
47338
+ const eps = 0.75;
47339
+ const minArea = 3e-3 * width * height;
47340
+ const ringArea = (r) => {
47341
+ let s = 0;
47342
+ for (let i = 0; i < r.length; i++) {
47343
+ const a = r[i];
47344
+ const b = r[(i + 1) % r.length];
47345
+ s += a[0] * b[1] - b[0] * a[1];
47346
+ }
47347
+ return Math.abs(s) / 2;
47348
+ };
47349
+ const areas = rings.map(ringArea);
47350
+ const maxArea = Math.max(...areas);
47351
+ 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;
47352
+ let dropped = false;
47353
+ const kept = rings.filter((r, idx) => {
47354
+ if (areas[idx] >= maxArea || areas[idx] >= minArea) return true;
47355
+ const touches = r.some((p, i) => onVEdge(p, r[(i + 1) % r.length]));
47356
+ if (touches) {
47357
+ dropped = true;
47358
+ return false;
47359
+ }
47360
+ return true;
47361
+ });
47362
+ if (!dropped) return d;
47363
+ return kept.map(
47364
+ (r) => r.map((p, i) => (i ? "L" : "M") + p[0] + "," + p[1]).join("") + "Z"
47365
+ ).join("");
47366
+ }
47234
47367
  function layoutMap(resolved, data, size, opts) {
47235
47368
  const { palette, isDark } = opts;
47236
47369
  const { width, height } = size;
@@ -47314,7 +47447,7 @@ function layoutMap(resolved, data, size, opts) {
47314
47447
  const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
47315
47448
  const fillForValue = (s) => {
47316
47449
  const t = rampMax > rampMin ? (s - rampMin) / (rampMax - rampMin) : 1;
47317
- const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
47450
+ const pct = RAMP_FLOOR2 + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR2);
47318
47451
  return mix(rampHue, rampBase, pct);
47319
47452
  };
47320
47453
  const tagFill = (tags, groupName) => {
@@ -47373,10 +47506,11 @@ function layoutMap(resolved, data, size, opts) {
47373
47506
  const by0 = cb[0][1];
47374
47507
  const cw = cb[1][0] - bx0;
47375
47508
  const ch = cb[1][1] - by0;
47376
- const ox = fitBox[0][0];
47377
- const oy = fitBox[0][1];
47378
- const sx = cw > 0 ? (fitBox[1][0] - ox) / cw : 1;
47379
- const sy = ch > 0 ? (fitBox[1][1] - oy) / ch : 1;
47509
+ const topReserve = resolved.title && resolved.pois.length > 0 ? topPad : 0;
47510
+ const ox = 0;
47511
+ const oy = topReserve;
47512
+ const sx = cw > 0 ? width / cw : 1;
47513
+ const sy = ch > 0 ? (height - topReserve) / ch : 1;
47380
47514
  stretchParams = { sx, sy, ox, oy, bx0, by0 };
47381
47515
  const stretch = (x, y) => [
47382
47516
  ox + (x - bx0) * sx,
@@ -47649,7 +47783,8 @@ function layoutMap(resolved, data, size, opts) {
47649
47783
  const r = regionById.get(iso);
47650
47784
  const viewF = shouldCull ? cullFeatureToView(f) : dropFrameFillers(f);
47651
47785
  if (!viewF) continue;
47652
- const d = path(viewF) ?? "";
47786
+ const raw = path(viewF) ?? "";
47787
+ const d = fitIsGlobal ? dropAntimeridianWrapSlivers(raw, width, height) : raw;
47653
47788
  if (!d) continue;
47654
47789
  const isThisLayer = r?.layer === layerKind;
47655
47790
  const isForeign = layerKind === "country" && usContext && iso !== "US";
@@ -47666,6 +47801,9 @@ function layoutMap(resolved, data, size, opts) {
47666
47801
  } else {
47667
47802
  label = f.properties?.name;
47668
47803
  }
47804
+ const labelAnchor = WORLD_LABEL_ANCHORS[iso];
47805
+ const c = labelAnchor ? project(labelAnchor[0], labelAnchor[1]) : path.centroid(viewF);
47806
+ const hasCentroid = c != null && Number.isFinite(c[0]) && Number.isFinite(c[1]);
47669
47807
  regions.push({
47670
47808
  id: iso,
47671
47809
  d,
@@ -47674,6 +47812,7 @@ function layoutMap(resolved, data, size, opts) {
47674
47812
  lineNumber,
47675
47813
  layer,
47676
47814
  ...label !== void 0 && { label },
47815
+ ...hasCentroid && { labelX: c[0], labelY: c[1] },
47677
47816
  ...isThisLayer && r.value !== void 0 && { value: r.value },
47678
47817
  ...isThisLayer && Object.keys(r.tags).length > 0 && { tags: r.tags }
47679
47818
  });
@@ -48114,10 +48253,6 @@ function layoutMap(resolved, data, size, opts) {
48114
48253
  lineNumber
48115
48254
  });
48116
48255
  };
48117
- const WORLD_LABEL_ANCHORS = {
48118
- US: [-98.5, 39.5]
48119
- // CONUS geographic centre (near Lebanon, Kansas)
48120
- };
48121
48256
  const REGION_LABEL_GAP = 2;
48122
48257
  const regionLabelRect = (cx, cy, text) => {
48123
48258
  const w = measureLegendText(text, FONT2) + 2 * REGION_LABEL_GAP;
@@ -48483,7 +48618,7 @@ function layoutMap(resolved, data, size, opts) {
48483
48618
  diagnostics: []
48484
48619
  };
48485
48620
  }
48486
- 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;
48621
+ 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;
48487
48622
  var init_layout15 = __esm({
48488
48623
  "src/map/layout.ts"() {
48489
48624
  "use strict";
@@ -48496,13 +48631,17 @@ var init_layout15 = __esm({
48496
48631
  init_title_constants();
48497
48632
  init_context_labels();
48498
48633
  FIT_PAD = 24;
48499
- RAMP_FLOOR = 15;
48634
+ RAMP_FLOOR2 = 15;
48500
48635
  R_DEFAULT = 6;
48501
48636
  R_MIN = 4;
48502
48637
  R_MAX = 22;
48503
48638
  W_MIN = 1.25;
48504
48639
  W_MAX = 8;
48505
48640
  FONT2 = 11;
48641
+ WORLD_LABEL_ANCHORS = {
48642
+ US: [-98.5, 39.5]
48643
+ // CONUS geographic centre (near Lebanon, Kansas)
48644
+ };
48506
48645
  MAX_CLUSTER_EXTENT_FACTOR = 0.18;
48507
48646
  MAX_COLUMN_ROWS = 7;
48508
48647
  REGION_LABEL_HALO_RATIO = 4.5;
@@ -48516,9 +48655,9 @@ var init_layout15 = __esm({
48516
48655
  COMPACT_WIDTH_PX = 480;
48517
48656
  RELIEF_MIN_AREA = 12;
48518
48657
  RELIEF_MIN_DIM = 2;
48519
- RELIEF_HATCH_SPACING = 2;
48520
- RELIEF_HATCH_WIDTH = 0.15;
48521
- RELIEF_HATCH_STRENGTH = 32;
48658
+ RELIEF_HATCH_SPACING = 1.5;
48659
+ RELIEF_HATCH_WIDTH = 0.2;
48660
+ RELIEF_HATCH_STRENGTH = 26;
48522
48661
  COASTLINE_RING_COUNT = 5;
48523
48662
  COASTLINE_D0 = 16e-4;
48524
48663
  COASTLINE_STEP = 28e-4;
@@ -48597,7 +48736,47 @@ function ringToPath(ring) {
48597
48736
  d += (i ? "L" : "M") + ring[i][0] + "," + ring[i][1];
48598
48737
  return d + "Z";
48599
48738
  }
48600
- function coastlineOuterRings(regions, minExtent) {
48739
+ function polylineToPath(pts) {
48740
+ let d = "";
48741
+ for (let i = 0; i < pts.length; i++)
48742
+ d += (i ? "L" : "M") + pts[i][0] + "," + pts[i][1];
48743
+ return d;
48744
+ }
48745
+ function ringToCoastPaths(ring, frame) {
48746
+ if (!frame) return [ringToPath(ring)];
48747
+ const n = ring.length;
48748
+ const eps = 0.75;
48749
+ const onL = (x) => Math.abs(x) <= eps;
48750
+ const onR = (x) => Math.abs(x - frame.w) <= eps;
48751
+ const onT = (y) => Math.abs(y) <= eps;
48752
+ const onB = (y) => Math.abs(y - frame.h) <= eps;
48753
+ 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]);
48754
+ let firstBreak = -1;
48755
+ for (let i = 0; i < n; i++)
48756
+ if (isFrameEdge(ring[i], ring[(i + 1) % n])) {
48757
+ firstBreak = i;
48758
+ break;
48759
+ }
48760
+ if (firstBreak === -1) return [ringToPath(ring)];
48761
+ const paths = [];
48762
+ let cur = [];
48763
+ const start = (firstBreak + 1) % n;
48764
+ for (let k = 0; k < n; k++) {
48765
+ const i = (start + k) % n;
48766
+ const a = ring[i];
48767
+ const b = ring[(i + 1) % n];
48768
+ if (isFrameEdge(a, b)) {
48769
+ if (cur.length >= 2) paths.push(polylineToPath(cur));
48770
+ cur = [];
48771
+ continue;
48772
+ }
48773
+ if (cur.length === 0) cur.push(a);
48774
+ cur.push(b);
48775
+ }
48776
+ if (cur.length >= 2) paths.push(polylineToPath(cur));
48777
+ return paths;
48778
+ }
48779
+ function coastlineOuterRings(regions, minExtent, frame) {
48601
48780
  const paths = [];
48602
48781
  for (const r of regions) {
48603
48782
  const rings = parsePathRings(r.d);
@@ -48620,7 +48799,7 @@ function coastlineOuterRings(regions, minExtent) {
48620
48799
  for (let j = 0; j < rings.length; j++)
48621
48800
  if (j !== i && pointInRing2(fx, fy, rings[j])) depth++;
48622
48801
  if (depth % 2 === 1) continue;
48623
- paths.push(ringToPath(ring));
48802
+ paths.push(...ringToCoastPaths(ring, frame));
48624
48803
  }
48625
48804
  }
48626
48805
  return paths;
@@ -48664,6 +48843,10 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
48664
48843
  const drawRegion = (g, r, strokeWidth) => {
48665
48844
  const p = g.append("path").attr("d", r.d).attr("fill", r.fill).attr("stroke", r.stroke).attr("stroke-width", strokeWidth);
48666
48845
  if (r.label) p.attr("data-region-name", r.label);
48846
+ if (r.id && r.id !== "lake") p.attr("data-iso", r.id);
48847
+ if (r.labelX !== void 0 && r.labelY !== void 0) {
48848
+ p.attr("data-label-x", r.labelX).attr("data-label-y", r.labelY);
48849
+ }
48667
48850
  if (r.layer !== "base") {
48668
48851
  p.classed("dgmo-map-region", true).attr("data-region", r.id);
48669
48852
  if (r.value !== void 0) p.attr("data-value", r.value);
@@ -48693,7 +48876,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
48693
48876
  const landClip = defs.append("clipPath").attr("id", landClipId);
48694
48877
  for (const r of layout.regions)
48695
48878
  if (r.id !== "lake") landClip.append("path").attr("d", r.d);
48696
- 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");
48879
+ 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");
48697
48880
  for (let y = h.spacing; y < height; y += h.spacing) {
48698
48881
  gRelief.append("line").attr("x1", 0).attr("y1", y).attr("x2", width).attr("y2", y);
48699
48882
  }
@@ -48714,10 +48897,16 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
48714
48897
  mask.append("path").attr("d", d).attr("fill", "black").attr("stroke", "black").attr("stroke-width", 2 * reach).attr("stroke-linejoin", "round");
48715
48898
  }
48716
48899
  }
48717
- const gWater = svg.append("g").attr("class", "dgmo-map-water-lines").attr("fill", "none").attr("mask", `url(#${maskId})`);
48900
+ const gWater = svg.append("g").attr("class", "dgmo-map-water-lines").attr("fill", "none").style("pointer-events", "none").attr("mask", `url(#${maskId})`);
48718
48901
  appendWaterLines(
48719
48902
  gWater,
48720
- coastlineOuterRings(layout.regions, cs.minExtent),
48903
+ // Pass the canvas frame so edges collinear with it (the antimeridian on a
48904
+ // world map, regional clipExtent cuts) don't get ringed as fake coast —
48905
+ // land runs cleanly to the render-area edge.
48906
+ coastlineOuterRings(layout.regions, cs.minExtent, {
48907
+ w: width,
48908
+ h: height
48909
+ }),
48721
48910
  cs,
48722
48911
  layout.background
48723
48912
  );
@@ -48731,7 +48920,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
48731
48920
  gWater.append("path").attr("d", ds.join(" ")).attr("stroke", stroke2).attr("stroke-width", 0.5).attr("stroke-linejoin", "round");
48732
48921
  }
48733
48922
  if (layout.rivers.length) {
48734
- const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none");
48923
+ const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none").style("pointer-events", "none");
48735
48924
  for (const r of layout.rivers) {
48736
48925
  gRivers.append("path").attr("d", r.d).attr("stroke", r.color).attr("stroke-width", r.width).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
48737
48926
  }
@@ -48772,7 +48961,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
48772
48961
  const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
48773
48962
  clip.append("path").attr("d", d);
48774
48963
  }
48775
- 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})`);
48964
+ 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})`);
48776
48965
  appendWaterLines(
48777
48966
  gInsetWater,
48778
48967
  coastlineOuterRings(layout.insetRegions, cs.minExtent),
@@ -54793,10 +54982,12 @@ function renderTimelineHorizontalTimeSort(container, parsed, palette, isDark, se
54793
54982
  const markerLabelY = markerReserve ? -(topScaleH + MARKER_ROW_H / 2) : 0;
54794
54983
  const eraLabelY = eraReserve ? -(topScaleH + markerReserve + ERA_ROW_H / 2) : 0;
54795
54984
  const innerWidth = width - margin.left - margin.right;
54796
- const innerHeight = height - margin.top - margin.bottom;
54797
- const rowH = Math.min(ctx.structural(28), innerHeight / sorted.length);
54985
+ const availInnerHeight = height - margin.top - margin.bottom;
54986
+ const rowH = Math.min(ctx.structural(28), availInnerHeight / sorted.length);
54987
+ const innerHeight = rowH * sorted.length;
54988
+ const usedHeight = margin.top + innerHeight + margin.bottom;
54798
54989
  const xScale = d3Scale2.scaleLinear().domain([minDate - datePadding, maxDate + datePadding]).range([0, innerWidth]);
54799
- 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);
54990
+ 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);
54800
54991
  if (ctx.isBelowFloor) {
54801
54992
  svg.attr("width", "100%");
54802
54993
  }
@@ -58308,6 +58499,9 @@ var DIRECTIVE_KEYWORDS = /* @__PURE__ */ new Set([
58308
58499
  "hide",
58309
58500
  "mode",
58310
58501
  "direction",
58502
+ // Boxes-and-lines
58503
+ "box-metric",
58504
+ "show-values",
58311
58505
  // ER
58312
58506
  "notation",
58313
58507
  // Class
@@ -59030,7 +59224,7 @@ pre.dgmo, code.language-dgmo, pre > code.language-dgmo,
59030
59224
 
59031
59225
  // src/auto/index.ts
59032
59226
  init_safe_href();
59033
- var VERSION = "0.22.0";
59227
+ var VERSION = "0.23.0";
59034
59228
  var DEFAULTS = {
59035
59229
  theme: "auto",
59036
59230
  palette: "nord",