@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/advanced.js CHANGED
@@ -915,9 +915,7 @@ var init_reserved_key_registry = __esm({
915
915
  BOXES_AND_LINES_REGISTRY = staticRegistry([
916
916
  "color",
917
917
  "description",
918
- "width",
919
- "split",
920
- "fanout"
918
+ "value"
921
919
  ]);
922
920
  TIMELINE_REGISTRY = staticRegistry([
923
921
  "color",
@@ -16889,6 +16887,21 @@ function parseBoxesAndLines(content) {
16889
16887
  }
16890
16888
  continue;
16891
16889
  }
16890
+ if (!contentStarted) {
16891
+ const metricMatch = trimmed.match(/^box-metric\s+(.+)$/i);
16892
+ if (metricMatch) {
16893
+ const { label, colorName } = peelTrailingColorName(
16894
+ metricMatch[1].trim()
16895
+ );
16896
+ result.boxMetric = label;
16897
+ if (colorName !== void 0) result.boxMetricColor = colorName;
16898
+ continue;
16899
+ }
16900
+ if (/^show-values$/i.test(trimmed)) {
16901
+ result.showValues = true;
16902
+ continue;
16903
+ }
16904
+ }
16892
16905
  if (!contentStarted) {
16893
16906
  const optMatch = trimmed.match(OPTION_NOCOLON_RE);
16894
16907
  if (optMatch) {
@@ -17267,6 +17280,19 @@ function parseNodeLine(trimmed, lineNum, metaAliasMap, diagnostics, nameAliasMap
17267
17280
  description = [metadata["description"]];
17268
17281
  delete metadata["description"];
17269
17282
  }
17283
+ let value;
17284
+ if (metadata["value"] !== void 0) {
17285
+ const raw = metadata["value"];
17286
+ const num = Number(raw);
17287
+ if (Number.isFinite(num)) {
17288
+ value = num;
17289
+ } else {
17290
+ diagnostics.push(
17291
+ makeDgmoError(lineNum, `value must be a number (got "${raw}")`, "error")
17292
+ );
17293
+ }
17294
+ delete metadata["value"];
17295
+ }
17270
17296
  if (split.alias) {
17271
17297
  nameAliasMap?.set(normalizeName(split.alias), label);
17272
17298
  }
@@ -17275,7 +17301,8 @@ function parseNodeLine(trimmed, lineNum, metaAliasMap, diagnostics, nameAliasMap
17275
17301
  label,
17276
17302
  lineNumber: lineNum,
17277
17303
  metadata,
17278
- ...description !== void 0 && { description }
17304
+ ...description !== void 0 && { description },
17305
+ ...value !== void 0 && { value }
17279
17306
  };
17280
17307
  }
17281
17308
  function splitTargetAndMeta(rest, metaAliasMap) {
@@ -26487,7 +26514,18 @@ function fitLabelToHeader(label, nodeWidth, maxLines) {
26487
26514
  const truncated = label.length > maxChars ? label.slice(0, maxChars - 1) + "\u2026" : label;
26488
26515
  return { lines: [truncated], fontSize: MIN_NODE_FONT_SIZE };
26489
26516
  }
26490
- function nodeColors(node, tagGroups, activeGroupName, palette, isDark, solid) {
26517
+ function nodeColors(node, tagGroups, activeGroupName, palette, isDark, value, solid) {
26518
+ const neutralFill = mix(palette.bg, palette.text, isDark ? 90 : 95);
26519
+ if (value.active) {
26520
+ const fill3 = node.value !== void 0 ? value.fillForValue(node.value) : neutralFill;
26521
+ const stroke3 = value.hue;
26522
+ const text2 = contrastText(
26523
+ fill3,
26524
+ palette.textOnFillLight,
26525
+ palette.textOnFillDark
26526
+ );
26527
+ return { fill: fill3, stroke: stroke3, text: text2 };
26528
+ }
26491
26529
  const tagColor = resolveTagColor(
26492
26530
  node.metadata,
26493
26531
  [...tagGroups],
@@ -26596,25 +26634,65 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26596
26634
  const sGroupLabelZone = sctx.structural(GROUP_LABEL_ZONE);
26597
26635
  const sTitleFontSize = sctx.text(TITLE_FONT_SIZE);
26598
26636
  const sTitleY = sctx.structural(TITLE_Y);
26637
+ const nodeValues = parsed.nodes.filter((n) => n.value !== void 0).map((n) => n.value);
26638
+ const hasRamp = nodeValues.length > 0;
26639
+ const allNonNegative = hasRamp && nodeValues.every((v) => v >= 0);
26640
+ const rampMin = allNonNegative ? 0 : Math.min(...nodeValues);
26641
+ const rampMax = Math.max(...nodeValues);
26642
+ const rampHue = resolveColor(parsed.boxMetricColor ?? "", palette) ?? palette.primary;
26643
+ const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
26644
+ const fillForValue = (v) => {
26645
+ const t = rampMax > rampMin ? (v - rampMin) / (rampMax - rampMin) : 1;
26646
+ const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
26647
+ return mix(rampHue, rampBase, pct);
26648
+ };
26649
+ const VALUE_NAME = hasRamp ? parsed.boxMetric?.trim() || "Value" : null;
26650
+ const matchColorGroup = (v) => {
26651
+ const lv = v.trim().toLowerCase();
26652
+ if (lv === "none") return null;
26653
+ const tg = parsed.tagGroups.find((g) => g.name.toLowerCase() === lv);
26654
+ if (tg) return tg.name;
26655
+ if (lv === VALUE_NAME?.toLowerCase()) return VALUE_NAME;
26656
+ return v;
26657
+ };
26658
+ const override = activeTagGroup;
26659
+ let activeGroup;
26660
+ if (override !== void 0) {
26661
+ activeGroup = override === null ? null : matchColorGroup(override);
26662
+ } else if (parsed.options["active-tag"] !== void 0) {
26663
+ activeGroup = matchColorGroup(parsed.options["active-tag"]);
26664
+ } else {
26665
+ activeGroup = VALUE_NAME ?? (parsed.tagGroups.length > 0 ? parsed.tagGroups[0].name : null);
26666
+ }
26667
+ const activeIsValue = VALUE_NAME !== null && activeGroup === VALUE_NAME;
26668
+ const valueGroup = VALUE_NAME !== null ? {
26669
+ name: VALUE_NAME,
26670
+ entries: [],
26671
+ gradient: {
26672
+ min: rampMin,
26673
+ max: rampMax,
26674
+ hue: rampHue,
26675
+ base: rampBase
26676
+ }
26677
+ } : null;
26678
+ const legendGroups = [
26679
+ ...valueGroup ? [valueGroup] : [],
26680
+ ...parsed.tagGroups
26681
+ ];
26599
26682
  const reserveHasDescriptions = parsed.nodes.some(
26600
26683
  (n) => n.description && n.description.length > 0
26601
26684
  );
26602
- const willRenderLegend = parsed.tagGroups.length > 0 || reserveHasDescriptions && controlsHost !== "app";
26685
+ const willRenderLegend = legendGroups.length > 0 || reserveHasDescriptions && controlsHost !== "app";
26603
26686
  const sLegendHeight = willRenderLegend ? sctx.structural(
26604
26687
  getMaxLegendReservedHeight(
26605
26688
  {
26606
- groups: parsed.tagGroups,
26689
+ groups: legendGroups,
26607
26690
  position: { placement: "top-center", titleRelation: "below-title" },
26608
26691
  mode: exportMode ? "export" : "preview"
26609
26692
  },
26610
26693
  width
26611
26694
  )
26612
26695
  ) : 0;
26613
- const activeGroup = resolveActiveTagGroup(
26614
- parsed.tagGroups,
26615
- parsed.options["active-tag"],
26616
- activeTagGroup
26617
- );
26618
26696
  const hidden = hiddenTagValues ?? parsed.initialHiddenTagValues;
26619
26697
  const nodeMap = /* @__PURE__ */ new Map();
26620
26698
  for (const node of parsed.nodes) nodeMap.set(node.label, node);
@@ -26625,7 +26703,7 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26625
26703
  const hasAnyDescriptions = parsed.nodes.some(
26626
26704
  (n) => n.description && n.description.length > 0
26627
26705
  );
26628
- const needsLegend = parsed.tagGroups.length > 0 || hasAnyDescriptions && onToggleDescriptions;
26706
+ const needsLegend = legendGroups.length > 0 || hasAnyDescriptions && onToggleDescriptions;
26629
26707
  const legendH = needsLegend ? sLegendHeight + 8 : 0;
26630
26708
  const groupLabelsSet = new Set(layout.groups.map((g) => g.label));
26631
26709
  let labelZoneExtension = 0;
@@ -26831,12 +26909,16 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26831
26909
  activeGroup,
26832
26910
  palette,
26833
26911
  isDark,
26912
+ { active: activeIsValue, hue: rampHue, fillForValue },
26834
26913
  parsed.options["solid-fill"] === "on"
26835
26914
  );
26836
26915
  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);
26837
26916
  for (const [key, val] of Object.entries(node.metadata)) {
26838
26917
  nodeG.attr(`data-tag-${key.toLowerCase()}`, val.toLowerCase());
26839
26918
  }
26919
+ if (node.value !== void 0) {
26920
+ nodeG.attr("data-value", node.value);
26921
+ }
26840
26922
  if (onClickItem) {
26841
26923
  nodeG.on("click", (event) => {
26842
26924
  const target = event.target;
@@ -26920,11 +27002,27 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26920
27002
  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]);
26921
27003
  }
26922
27004
  }
27005
+ if (parsed.showValues && node.value !== void 0) {
27006
+ const valueText = String(node.value);
27007
+ const descShown = !!(desc && desc.length > 0 && !hideDescriptions);
27008
+ if (descShown) {
27009
+ const padX = 6;
27010
+ const padY = 5;
27011
+ const bw = valueText.length * VALUE_FONT_SIZE * CHAR_WIDTH_RATIO2 + 8;
27012
+ const bh = VALUE_FONT_SIZE + 4;
27013
+ const bx = ln.width / 2 - bw - 4;
27014
+ const by = -ln.height / 2 + 4;
27015
+ 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);
27016
+ 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);
27017
+ } else {
27018
+ 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);
27019
+ }
27020
+ }
26923
27021
  }
26924
27022
  const hasDescriptions = parsed.nodes.some(
26925
27023
  (n) => n.description && n.description.length > 0
26926
27024
  );
26927
- const hasLegend = parsed.tagGroups.length > 0 || hasDescriptions && controlsHost !== "app";
27025
+ const hasLegend = legendGroups.length > 0 || hasDescriptions && controlsHost !== "app";
26928
27026
  if (hasLegend) {
26929
27027
  let controlsGroup;
26930
27028
  if (hasDescriptions && (onToggleDescriptions || controlsHost === "app")) {
@@ -26942,7 +27040,7 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26942
27040
  };
26943
27041
  }
26944
27042
  const legendConfig = {
26945
- groups: parsed.tagGroups,
27043
+ groups: legendGroups,
26946
27044
  position: { placement: "top-center", titleRelation: "below-title" },
26947
27045
  mode: exportMode ? "export" : "preview",
26948
27046
  // Keep inactive sibling tag groups visible as collapsed pills so the user
@@ -26997,7 +27095,7 @@ function renderBoxesAndLinesForExport(container, parsed, layout, palette, isDark
26997
27095
  }
26998
27096
  });
26999
27097
  }
27000
- 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;
27098
+ 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;
27001
27099
  var init_renderer6 = __esm({
27002
27100
  "src/boxes-and-lines/renderer.ts"() {
27003
27101
  "use strict";
@@ -27006,6 +27104,7 @@ var init_renderer6 = __esm({
27006
27104
  init_legend_layout();
27007
27105
  init_title_constants();
27008
27106
  init_color_utils();
27107
+ init_colors();
27009
27108
  init_tag_groups();
27010
27109
  init_inline_markdown();
27011
27110
  init_wrapped_desc();
@@ -27028,6 +27127,8 @@ var init_renderer6 = __esm({
27028
27127
  GROUP_RX = 8;
27029
27128
  GROUP_LABEL_FONT_SIZE = 14;
27030
27129
  GROUP_LABEL_ZONE = 32;
27130
+ RAMP_FLOOR = 15;
27131
+ VALUE_FONT_SIZE = 11;
27031
27132
  lineGeneratorLR = d3Shape4.line().x((d) => d.x).y((d) => d.y).curve(d3Shape4.curveBasis);
27032
27133
  lineGeneratorTB = d3Shape4.line().x((d) => d.x).y((d) => d.y).curve(d3Shape4.curveBasis);
27033
27134
  }
@@ -47593,6 +47694,38 @@ function parsePathRings(d) {
47593
47694
  if (cur.length) rings.push(cur);
47594
47695
  return rings;
47595
47696
  }
47697
+ function dropAntimeridianWrapSlivers(d, width, height) {
47698
+ const rings = parsePathRings(d);
47699
+ if (rings.length <= 1) return d;
47700
+ const eps = 0.75;
47701
+ const minArea = 3e-3 * width * height;
47702
+ const ringArea = (r) => {
47703
+ let s = 0;
47704
+ for (let i = 0; i < r.length; i++) {
47705
+ const a = r[i];
47706
+ const b = r[(i + 1) % r.length];
47707
+ s += a[0] * b[1] - b[0] * a[1];
47708
+ }
47709
+ return Math.abs(s) / 2;
47710
+ };
47711
+ const areas = rings.map(ringArea);
47712
+ const maxArea = Math.max(...areas);
47713
+ 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;
47714
+ let dropped = false;
47715
+ const kept = rings.filter((r, idx) => {
47716
+ if (areas[idx] >= maxArea || areas[idx] >= minArea) return true;
47717
+ const touches = r.some((p, i) => onVEdge(p, r[(i + 1) % r.length]));
47718
+ if (touches) {
47719
+ dropped = true;
47720
+ return false;
47721
+ }
47722
+ return true;
47723
+ });
47724
+ if (!dropped) return d;
47725
+ return kept.map(
47726
+ (r) => r.map((p, i) => (i ? "L" : "M") + p[0] + "," + p[1]).join("") + "Z"
47727
+ ).join("");
47728
+ }
47596
47729
  function layoutMap(resolved, data, size, opts) {
47597
47730
  const { palette, isDark } = opts;
47598
47731
  const { width, height } = size;
@@ -47676,7 +47809,7 @@ function layoutMap(resolved, data, size, opts) {
47676
47809
  const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
47677
47810
  const fillForValue = (s) => {
47678
47811
  const t = rampMax > rampMin ? (s - rampMin) / (rampMax - rampMin) : 1;
47679
- const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
47812
+ const pct = RAMP_FLOOR2 + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR2);
47680
47813
  return mix(rampHue, rampBase, pct);
47681
47814
  };
47682
47815
  const tagFill = (tags, groupName) => {
@@ -47735,10 +47868,11 @@ function layoutMap(resolved, data, size, opts) {
47735
47868
  const by0 = cb[0][1];
47736
47869
  const cw = cb[1][0] - bx0;
47737
47870
  const ch = cb[1][1] - by0;
47738
- const ox = fitBox[0][0];
47739
- const oy = fitBox[0][1];
47740
- const sx = cw > 0 ? (fitBox[1][0] - ox) / cw : 1;
47741
- const sy = ch > 0 ? (fitBox[1][1] - oy) / ch : 1;
47871
+ const topReserve = resolved.title && resolved.pois.length > 0 ? topPad : 0;
47872
+ const ox = 0;
47873
+ const oy = topReserve;
47874
+ const sx = cw > 0 ? width / cw : 1;
47875
+ const sy = ch > 0 ? (height - topReserve) / ch : 1;
47742
47876
  stretchParams = { sx, sy, ox, oy, bx0, by0 };
47743
47877
  const stretch = (x, y) => [
47744
47878
  ox + (x - bx0) * sx,
@@ -48011,7 +48145,8 @@ function layoutMap(resolved, data, size, opts) {
48011
48145
  const r = regionById.get(iso);
48012
48146
  const viewF = shouldCull ? cullFeatureToView(f) : dropFrameFillers(f);
48013
48147
  if (!viewF) continue;
48014
- const d = path(viewF) ?? "";
48148
+ const raw = path(viewF) ?? "";
48149
+ const d = fitIsGlobal ? dropAntimeridianWrapSlivers(raw, width, height) : raw;
48015
48150
  if (!d) continue;
48016
48151
  const isThisLayer = r?.layer === layerKind;
48017
48152
  const isForeign = layerKind === "country" && usContext && iso !== "US";
@@ -48028,6 +48163,9 @@ function layoutMap(resolved, data, size, opts) {
48028
48163
  } else {
48029
48164
  label = f.properties?.name;
48030
48165
  }
48166
+ const labelAnchor = WORLD_LABEL_ANCHORS[iso];
48167
+ const c = labelAnchor ? project(labelAnchor[0], labelAnchor[1]) : path.centroid(viewF);
48168
+ const hasCentroid = c != null && Number.isFinite(c[0]) && Number.isFinite(c[1]);
48031
48169
  regions.push({
48032
48170
  id: iso,
48033
48171
  d,
@@ -48036,6 +48174,7 @@ function layoutMap(resolved, data, size, opts) {
48036
48174
  lineNumber,
48037
48175
  layer,
48038
48176
  ...label !== void 0 && { label },
48177
+ ...hasCentroid && { labelX: c[0], labelY: c[1] },
48039
48178
  ...isThisLayer && r.value !== void 0 && { value: r.value },
48040
48179
  ...isThisLayer && Object.keys(r.tags).length > 0 && { tags: r.tags }
48041
48180
  });
@@ -48476,10 +48615,6 @@ function layoutMap(resolved, data, size, opts) {
48476
48615
  lineNumber
48477
48616
  });
48478
48617
  };
48479
- const WORLD_LABEL_ANCHORS = {
48480
- US: [-98.5, 39.5]
48481
- // CONUS geographic centre (near Lebanon, Kansas)
48482
- };
48483
48618
  const REGION_LABEL_GAP = 2;
48484
48619
  const regionLabelRect = (cx, cy, text) => {
48485
48620
  const w = measureLegendText(text, FONT2) + 2 * REGION_LABEL_GAP;
@@ -48845,7 +48980,7 @@ function layoutMap(resolved, data, size, opts) {
48845
48980
  diagnostics: []
48846
48981
  };
48847
48982
  }
48848
- 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;
48983
+ 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;
48849
48984
  var init_layout15 = __esm({
48850
48985
  "src/map/layout.ts"() {
48851
48986
  "use strict";
@@ -48858,13 +48993,17 @@ var init_layout15 = __esm({
48858
48993
  init_title_constants();
48859
48994
  init_context_labels();
48860
48995
  FIT_PAD = 24;
48861
- RAMP_FLOOR = 15;
48996
+ RAMP_FLOOR2 = 15;
48862
48997
  R_DEFAULT = 6;
48863
48998
  R_MIN = 4;
48864
48999
  R_MAX = 22;
48865
49000
  W_MIN = 1.25;
48866
49001
  W_MAX = 8;
48867
49002
  FONT2 = 11;
49003
+ WORLD_LABEL_ANCHORS = {
49004
+ US: [-98.5, 39.5]
49005
+ // CONUS geographic centre (near Lebanon, Kansas)
49006
+ };
48868
49007
  MAX_CLUSTER_EXTENT_FACTOR = 0.18;
48869
49008
  MAX_COLUMN_ROWS = 7;
48870
49009
  REGION_LABEL_HALO_RATIO = 4.5;
@@ -48878,9 +49017,9 @@ var init_layout15 = __esm({
48878
49017
  COMPACT_WIDTH_PX = 480;
48879
49018
  RELIEF_MIN_AREA = 12;
48880
49019
  RELIEF_MIN_DIM = 2;
48881
- RELIEF_HATCH_SPACING = 2;
48882
- RELIEF_HATCH_WIDTH = 0.15;
48883
- RELIEF_HATCH_STRENGTH = 32;
49020
+ RELIEF_HATCH_SPACING = 1.5;
49021
+ RELIEF_HATCH_WIDTH = 0.2;
49022
+ RELIEF_HATCH_STRENGTH = 26;
48884
49023
  COASTLINE_RING_COUNT = 5;
48885
49024
  COASTLINE_D0 = 16e-4;
48886
49025
  COASTLINE_STEP = 28e-4;
@@ -48959,7 +49098,47 @@ function ringToPath(ring) {
48959
49098
  d += (i ? "L" : "M") + ring[i][0] + "," + ring[i][1];
48960
49099
  return d + "Z";
48961
49100
  }
48962
- function coastlineOuterRings(regions, minExtent) {
49101
+ function polylineToPath(pts) {
49102
+ let d = "";
49103
+ for (let i = 0; i < pts.length; i++)
49104
+ d += (i ? "L" : "M") + pts[i][0] + "," + pts[i][1];
49105
+ return d;
49106
+ }
49107
+ function ringToCoastPaths(ring, frame) {
49108
+ if (!frame) return [ringToPath(ring)];
49109
+ const n = ring.length;
49110
+ const eps = 0.75;
49111
+ const onL = (x) => Math.abs(x) <= eps;
49112
+ const onR = (x) => Math.abs(x - frame.w) <= eps;
49113
+ const onT = (y) => Math.abs(y) <= eps;
49114
+ const onB = (y) => Math.abs(y - frame.h) <= eps;
49115
+ 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]);
49116
+ let firstBreak = -1;
49117
+ for (let i = 0; i < n; i++)
49118
+ if (isFrameEdge(ring[i], ring[(i + 1) % n])) {
49119
+ firstBreak = i;
49120
+ break;
49121
+ }
49122
+ if (firstBreak === -1) return [ringToPath(ring)];
49123
+ const paths = [];
49124
+ let cur = [];
49125
+ const start = (firstBreak + 1) % n;
49126
+ for (let k = 0; k < n; k++) {
49127
+ const i = (start + k) % n;
49128
+ const a = ring[i];
49129
+ const b = ring[(i + 1) % n];
49130
+ if (isFrameEdge(a, b)) {
49131
+ if (cur.length >= 2) paths.push(polylineToPath(cur));
49132
+ cur = [];
49133
+ continue;
49134
+ }
49135
+ if (cur.length === 0) cur.push(a);
49136
+ cur.push(b);
49137
+ }
49138
+ if (cur.length >= 2) paths.push(polylineToPath(cur));
49139
+ return paths;
49140
+ }
49141
+ function coastlineOuterRings(regions, minExtent, frame) {
48963
49142
  const paths = [];
48964
49143
  for (const r of regions) {
48965
49144
  const rings = parsePathRings(r.d);
@@ -48982,7 +49161,7 @@ function coastlineOuterRings(regions, minExtent) {
48982
49161
  for (let j = 0; j < rings.length; j++)
48983
49162
  if (j !== i && pointInRing2(fx, fy, rings[j])) depth++;
48984
49163
  if (depth % 2 === 1) continue;
48985
- paths.push(ringToPath(ring));
49164
+ paths.push(...ringToCoastPaths(ring, frame));
48986
49165
  }
48987
49166
  }
48988
49167
  return paths;
@@ -49026,6 +49205,10 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
49026
49205
  const drawRegion = (g, r, strokeWidth) => {
49027
49206
  const p = g.append("path").attr("d", r.d).attr("fill", r.fill).attr("stroke", r.stroke).attr("stroke-width", strokeWidth);
49028
49207
  if (r.label) p.attr("data-region-name", r.label);
49208
+ if (r.id && r.id !== "lake") p.attr("data-iso", r.id);
49209
+ if (r.labelX !== void 0 && r.labelY !== void 0) {
49210
+ p.attr("data-label-x", r.labelX).attr("data-label-y", r.labelY);
49211
+ }
49029
49212
  if (r.layer !== "base") {
49030
49213
  p.classed("dgmo-map-region", true).attr("data-region", r.id);
49031
49214
  if (r.value !== void 0) p.attr("data-value", r.value);
@@ -49055,7 +49238,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
49055
49238
  const landClip = defs.append("clipPath").attr("id", landClipId);
49056
49239
  for (const r of layout.regions)
49057
49240
  if (r.id !== "lake") landClip.append("path").attr("d", r.d);
49058
- 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");
49241
+ 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");
49059
49242
  for (let y = h.spacing; y < height; y += h.spacing) {
49060
49243
  gRelief.append("line").attr("x1", 0).attr("y1", y).attr("x2", width).attr("y2", y);
49061
49244
  }
@@ -49076,10 +49259,16 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
49076
49259
  mask.append("path").attr("d", d).attr("fill", "black").attr("stroke", "black").attr("stroke-width", 2 * reach).attr("stroke-linejoin", "round");
49077
49260
  }
49078
49261
  }
49079
- const gWater = svg.append("g").attr("class", "dgmo-map-water-lines").attr("fill", "none").attr("mask", `url(#${maskId})`);
49262
+ const gWater = svg.append("g").attr("class", "dgmo-map-water-lines").attr("fill", "none").style("pointer-events", "none").attr("mask", `url(#${maskId})`);
49080
49263
  appendWaterLines(
49081
49264
  gWater,
49082
- coastlineOuterRings(layout.regions, cs.minExtent),
49265
+ // Pass the canvas frame so edges collinear with it (the antimeridian on a
49266
+ // world map, regional clipExtent cuts) don't get ringed as fake coast —
49267
+ // land runs cleanly to the render-area edge.
49268
+ coastlineOuterRings(layout.regions, cs.minExtent, {
49269
+ w: width,
49270
+ h: height
49271
+ }),
49083
49272
  cs,
49084
49273
  layout.background
49085
49274
  );
@@ -49093,7 +49282,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
49093
49282
  gWater.append("path").attr("d", ds.join(" ")).attr("stroke", stroke2).attr("stroke-width", 0.5).attr("stroke-linejoin", "round");
49094
49283
  }
49095
49284
  if (layout.rivers.length) {
49096
- const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none");
49285
+ const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none").style("pointer-events", "none");
49097
49286
  for (const r of layout.rivers) {
49098
49287
  gRivers.append("path").attr("d", r.d).attr("stroke", r.color).attr("stroke-width", r.width).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
49099
49288
  }
@@ -49134,7 +49323,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
49134
49323
  const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
49135
49324
  clip.append("path").attr("d", d);
49136
49325
  }
49137
- 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})`);
49326
+ 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})`);
49138
49327
  appendWaterLines(
49139
49328
  gInsetWater,
49140
49329
  coastlineOuterRings(layout.insetRegions, cs.minExtent),
@@ -55155,10 +55344,12 @@ function renderTimelineHorizontalTimeSort(container, parsed, palette, isDark, se
55155
55344
  const markerLabelY = markerReserve ? -(topScaleH + MARKER_ROW_H / 2) : 0;
55156
55345
  const eraLabelY = eraReserve ? -(topScaleH + markerReserve + ERA_ROW_H / 2) : 0;
55157
55346
  const innerWidth = width - margin.left - margin.right;
55158
- const innerHeight = height - margin.top - margin.bottom;
55159
- const rowH = Math.min(ctx.structural(28), innerHeight / sorted.length);
55347
+ const availInnerHeight = height - margin.top - margin.bottom;
55348
+ const rowH = Math.min(ctx.structural(28), availInnerHeight / sorted.length);
55349
+ const innerHeight = rowH * sorted.length;
55350
+ const usedHeight = margin.top + innerHeight + margin.bottom;
55160
55351
  const xScale = d3Scale2.scaleLinear().domain([minDate - datePadding, maxDate + datePadding]).range([0, innerWidth]);
55161
- 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);
55352
+ 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);
55162
55353
  if (ctx.isBelowFloor) {
55163
55354
  svg.attr("width", "100%");
55164
55355
  }
@@ -60244,7 +60435,9 @@ var COMPLETION_REGISTRY = /* @__PURE__ */ new Map([
60244
60435
  withGlobals({
60245
60436
  direction: { description: "Layout direction", values: ["LR", "TB"] },
60246
60437
  "active-tag": { description: "Active tag group name" },
60247
- hide: { description: "Hide tag:value pairs" }
60438
+ hide: { description: "Hide tag:value pairs" },
60439
+ "box-metric": { description: "Metric label for the value ramp" },
60440
+ "show-values": { description: "Print box values as text" }
60248
60441
  })
60249
60442
  ],
60250
60443
  [
@@ -60472,13 +60665,10 @@ var PIPE_METADATA = /* @__PURE__ */ new Map([
60472
60665
  "boxes-and-lines",
60473
60666
  {
60474
60667
  node: {
60475
- description: { description: "Node description text" }
60668
+ description: { description: "Node description text" },
60669
+ value: { description: "Numeric value for the metric ramp" }
60476
60670
  },
60477
- edge: {
60478
- width: { description: "Edge stroke width in pixels" },
60479
- split: { description: "Traffic split percentage" },
60480
- fanout: { description: "Fanout multiplier (integer >= 1)" }
60481
- }
60671
+ edge: {}
60482
60672
  }
60483
60673
  ],
60484
60674
  [