@diagrammo/dgmo 0.23.0 → 0.25.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.
package/dist/index.js CHANGED
@@ -816,7 +816,7 @@ function withTagAliases(base, aliases) {
816
816
  function isReservedKey(registry, key) {
817
817
  return registry.keys.has(key) || registry.tagAliases.has(key);
818
818
  }
819
- var SEQUENCE_REGISTRY, INFRA_REGISTRY, MAP_REGISTRY, ORG_REGISTRY, C4_REGISTRY, ER_REGISTRY, CLASS_REGISTRY, KANBAN_REGISTRY, SITEMAP_REGISTRY, GANTT_REGISTRY, PERT_REGISTRY, BOXES_AND_LINES_REGISTRY, TIMELINE_REGISTRY, MINDMAP_REGISTRY, TECH_RADAR_REGISTRY, CYCLE_REGISTRY, JOURNEY_MAP_REGISTRY, PYRAMID_REGISTRY, RING_REGISTRY, RACI_REGISTRY, WIREFRAME_REGISTRY;
819
+ var SEQUENCE_REGISTRY, INFRA_REGISTRY, MAP_REGISTRY, ORG_REGISTRY, C4_REGISTRY, ER_REGISTRY, KANBAN_REGISTRY, SITEMAP_REGISTRY, GANTT_REGISTRY, PERT_REGISTRY, BOXES_AND_LINES_REGISTRY, TIMELINE_REGISTRY, MINDMAP_REGISTRY, TECH_RADAR_REGISTRY, CYCLE_REGISTRY, JOURNEY_MAP_REGISTRY, PYRAMID_REGISTRY, RING_REGISTRY, RACI_REGISTRY;
820
820
  var init_reserved_key_registry = __esm({
821
821
  "src/utils/reserved-key-registry.ts"() {
822
822
  "use strict";
@@ -860,10 +860,6 @@ var init_reserved_key_registry = __esm({
860
860
  "description",
861
861
  "domain"
862
862
  ]);
863
- CLASS_REGISTRY = staticRegistry([
864
- "color",
865
- "description"
866
- ]);
867
863
  KANBAN_REGISTRY = staticRegistry([
868
864
  "color",
869
865
  "description",
@@ -939,7 +935,6 @@ var init_reserved_key_registry = __esm({
939
935
  "color",
940
936
  "description"
941
937
  ]);
942
- WIREFRAME_REGISTRY = staticRegistry([]);
943
938
  }
944
939
  });
945
940
 
@@ -4208,6 +4203,9 @@ var init_legend_layout = __esm({
4208
4203
  });
4209
4204
 
4210
4205
  // src/utils/legend-d3.ts
4206
+ function centerText(sel) {
4207
+ return sel.attr("dy", LEGEND_TEXT_DY);
4208
+ }
4211
4209
  function renderLegendD3(container, config, state, palette, isDark, callbacks, containerWidth) {
4212
4210
  const width = containerWidth ?? parseFloat(container.attr("width") || "800");
4213
4211
  let currentState = { ...state };
@@ -4290,21 +4288,21 @@ function renderCapsule(parent, capsule, palette, groupBg, pillBorder, _isDark, c
4290
4288
  const pill = capsule.pill;
4291
4289
  g.append("rect").attr("x", pill.x).attr("y", pill.y).attr("width", pill.width).attr("height", pill.height).attr("rx", pill.height / 2).attr("fill", palette.bg);
4292
4290
  g.append("rect").attr("x", pill.x).attr("y", pill.y).attr("width", pill.width).attr("height", pill.height).attr("rx", pill.height / 2).attr("fill", "none").attr("stroke", pillBorder).attr("stroke-width", 0.75);
4293
- g.append("text").attr("x", pill.x + pill.width / 2).attr("y", LEGEND_HEIGHT / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", LEGEND_PILL_FONT_SIZE).attr("font-weight", 500).attr("fill", palette.text).attr("pointer-events", "none").attr("font-family", FONT_FAMILY).text(capsule.groupName);
4291
+ g.append("text").attr("x", pill.x + pill.width / 2).attr("y", LEGEND_HEIGHT / 2).attr("text-anchor", "middle").call(centerText).attr("font-size", LEGEND_PILL_FONT_SIZE).attr("font-weight", 500).attr("fill", palette.text).attr("pointer-events", "none").attr("font-family", FONT_FAMILY).text(capsule.groupName);
4294
4292
  if (capsule.gradient) {
4295
4293
  const gr = capsule.gradient;
4296
4294
  const gradId = `dgmo-legend-ramp-${capsule.groupName.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
4297
4295
  const def = g.append("defs").append("linearGradient").attr("id", gradId);
4298
4296
  def.append("stop").attr("offset", "0%").attr("stop-color", mix(gr.hue, gr.base, 15));
4299
4297
  def.append("stop").attr("offset", "100%").attr("stop-color", gr.hue);
4300
- g.append("text").attr("x", gr.minX).attr("y", gr.textY).attr("dominant-baseline", "central").attr("font-size", LEGEND_ENTRY_FONT_SIZE).attr("fill", palette.textMuted).attr("pointer-events", "none").attr("font-family", FONT_FAMILY).text(gr.minText);
4298
+ g.append("text").attr("x", gr.minX).attr("y", gr.textY).call(centerText).attr("font-size", LEGEND_ENTRY_FONT_SIZE).attr("fill", palette.textMuted).attr("pointer-events", "none").attr("font-family", FONT_FAMILY).text(gr.minText);
4301
4299
  g.append("rect").attr("class", "dgmo-legend-gradient-ramp").attr("data-ramp-min", gr.min).attr("data-ramp-max", gr.max).attr("x", gr.rampX).attr("y", gr.rampY).attr("width", gr.rampW).attr("height", gr.rampH).attr("rx", 2).attr("fill", `url(#${gradId})`);
4302
- g.append("text").attr("x", gr.maxX).attr("y", gr.textY).attr("dominant-baseline", "central").attr("font-size", LEGEND_ENTRY_FONT_SIZE).attr("fill", palette.textMuted).attr("pointer-events", "none").attr("font-family", FONT_FAMILY).text(gr.maxText);
4300
+ g.append("text").attr("x", gr.maxX).attr("y", gr.textY).call(centerText).attr("font-size", LEGEND_ENTRY_FONT_SIZE).attr("fill", palette.textMuted).attr("pointer-events", "none").attr("font-family", FONT_FAMILY).text(gr.maxText);
4303
4301
  }
4304
4302
  for (const entry of capsule.entries) {
4305
4303
  const entryG = g.append("g").attr("data-legend-entry", entry.value.toLowerCase()).attr("data-series-name", entry.value).style("cursor", "pointer");
4306
4304
  entryG.append("circle").attr("cx", entry.dotCx).attr("cy", entry.dotCy).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
4307
- entryG.append("text").attr("x", entry.textX).attr("y", entry.textY).attr("dominant-baseline", "central").attr("font-size", LEGEND_ENTRY_FONT_SIZE).attr("fill", palette.textMuted).attr("font-family", FONT_FAMILY).text(entry.displayValue ?? entry.value);
4305
+ entryG.append("text").attr("x", entry.textX).attr("y", entry.textY).call(centerText).attr("font-size", LEGEND_ENTRY_FONT_SIZE).attr("fill", palette.textMuted).attr("font-family", FONT_FAMILY).text(entry.displayValue ?? entry.value);
4308
4306
  if (callbacks?.onEntryHover) {
4309
4307
  const groupName = capsule.groupName;
4310
4308
  const entryValue = entry.value;
@@ -4324,7 +4322,7 @@ function renderCapsule(parent, capsule, palette, groupBg, pillBorder, _isDark, c
4324
4322
  function renderPill(parent, pill, palette, groupBg, callbacks) {
4325
4323
  const g = parent.append("g").attr("transform", `translate(${pill.x},${pill.y})`).attr("data-legend-group", pill.groupName.toLowerCase()).style("cursor", "pointer");
4326
4324
  g.append("rect").attr("width", pill.width).attr("height", pill.height).attr("rx", pill.height / 2).attr("fill", groupBg);
4327
- g.append("text").attr("x", pill.width / 2).attr("y", pill.height / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", LEGEND_PILL_FONT_SIZE).attr("font-weight", 500).attr("fill", palette.textMuted).attr("pointer-events", "none").attr("font-family", FONT_FAMILY).text(pill.groupName);
4325
+ g.append("text").attr("x", pill.width / 2).attr("y", pill.height / 2).attr("text-anchor", "middle").call(centerText).attr("font-size", LEGEND_PILL_FONT_SIZE).attr("font-weight", 500).attr("fill", palette.textMuted).attr("pointer-events", "none").attr("font-family", FONT_FAMILY).text(pill.groupName);
4328
4326
  if (callbacks?.onGroupToggle) {
4329
4327
  const cb = callbacks.onGroupToggle;
4330
4328
  const name = pill.groupName;
@@ -4347,7 +4345,7 @@ function renderControl(parent, ctrl, palette, _groupBg, pillBorder, _isDark, con
4347
4345
  textX = 8 + 14 + LEGEND_ENTRY_DOT_GAP + measureLegendText(ctrl.label, LEGEND_PILL_FONT_SIZE) / 2;
4348
4346
  }
4349
4347
  if (ctrl.label) {
4350
- g.append("text").attr("x", textX).attr("y", ctrl.height / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", LEGEND_PILL_FONT_SIZE).attr("font-weight", 500).attr("fill", palette.textMuted).attr("pointer-events", "none").attr("font-family", FONT_FAMILY).text(ctrl.label);
4348
+ g.append("text").attr("x", textX).attr("y", ctrl.height / 2).attr("text-anchor", "middle").call(centerText).attr("font-size", LEGEND_PILL_FONT_SIZE).attr("font-weight", 500).attr("fill", palette.textMuted).attr("pointer-events", "none").attr("font-family", FONT_FAMILY).text(ctrl.label);
4351
4349
  }
4352
4350
  if (ctrl.children) {
4353
4351
  let cx = ctrl.width + 4;
@@ -4357,7 +4355,7 @@ function renderControl(parent, ctrl, palette, _groupBg, pillBorder, _isDark, con
4357
4355
  "fill",
4358
4356
  child.isActive ? palette.primary ?? palette.text : "none"
4359
4357
  ).attr("stroke", pillBorder).attr("stroke-width", 0.75);
4360
- childG.append("text").attr("x", child.width / 2).attr("y", ctrl.height / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", LEGEND_ENTRY_FONT_SIZE).attr("fill", child.isActive ? palette.bg : palette.textMuted).attr("font-family", FONT_FAMILY).text(child.label);
4358
+ childG.append("text").attr("x", child.width / 2).attr("y", ctrl.height / 2).attr("text-anchor", "middle").call(centerText).attr("font-size", LEGEND_ENTRY_FONT_SIZE).attr("fill", child.isActive ? palette.bg : palette.textMuted).attr("font-family", FONT_FAMILY).text(child.label);
4361
4359
  const configCtrl2 = configControls?.find((c) => c.id === ctrl.id);
4362
4360
  const configChild = configCtrl2?.children?.find((c) => c.id === child.id);
4363
4361
  if (configChild?.onClick) {
@@ -4411,7 +4409,7 @@ function renderControlsGroup(parent, layout, palette, groupBg, pillBorder, callb
4411
4409
  } else {
4412
4410
  entryG.append("circle").attr("cx", tl.dotCx).attr("cy", tl.dotCy).attr("r", LEGEND_TOGGLE_DOT_R).attr("fill", "none").attr("stroke", palette.textMuted).attr("stroke-width", 1);
4413
4411
  }
4414
- entryG.append("text").attr("x", tl.textX).attr("y", tl.textY).attr("dominant-baseline", "central").attr("font-size", LEGEND_ENTRY_FONT_SIZE).attr("fill", palette.textMuted).attr("opacity", tl.active ? 1 : LEGEND_TOGGLE_OFF_OPACITY).attr("font-family", FONT_FAMILY).text(tl.label);
4412
+ entryG.append("text").attr("x", tl.textX).attr("y", tl.textY).call(centerText).attr("font-size", LEGEND_ENTRY_FONT_SIZE).attr("fill", palette.textMuted).attr("opacity", tl.active ? 1 : LEGEND_TOGGLE_OFF_OPACITY).attr("font-family", FONT_FAMILY).text(tl.label);
4415
4413
  if (callbacks?.onControlsToggle && toggle) {
4416
4414
  const cb = callbacks.onControlsToggle;
4417
4415
  const id = tl.id;
@@ -4424,6 +4422,7 @@ function renderControlsGroup(parent, layout, palette, groupBg, pillBorder, callb
4424
4422
  }
4425
4423
  }
4426
4424
  }
4425
+ var LEGEND_TEXT_DY;
4427
4426
  var init_legend_d3 = __esm({
4428
4427
  "src/utils/legend-d3.ts"() {
4429
4428
  "use strict";
@@ -4431,6 +4430,7 @@ var init_legend_d3 = __esm({
4431
4430
  init_legend_layout();
4432
4431
  init_color_utils();
4433
4432
  init_fonts();
4433
+ LEGEND_TEXT_DY = "0.32em";
4434
4434
  }
4435
4435
  });
4436
4436
 
@@ -4681,7 +4681,6 @@ var init_arrows = __esm({
4681
4681
  var parser_exports = {};
4682
4682
  __export(parser_exports, {
4683
4683
  isSequenceBlock: () => isSequenceBlock,
4684
- isSequenceMessage: () => isSequenceMessage,
4685
4684
  isSequenceNote: () => isSequenceNote,
4686
4685
  isSequenceSection: () => isSequenceSection,
4687
4686
  looksLikeSequence: () => looksLikeSequence,
@@ -4697,9 +4696,6 @@ function isHardRemovedToken(remainder) {
4697
4696
  }
4698
4697
  return { removed: false };
4699
4698
  }
4700
- function isSequenceMessage(el) {
4701
- return el.kind === "message";
4702
- }
4703
4699
  function isSequenceBlock(el) {
4704
4700
  return el.kind === "block";
4705
4701
  }
@@ -26585,7 +26581,7 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26585
26581
  const VALUE_NAME = hasRamp ? parsed.boxMetric?.trim() || "Value" : null;
26586
26582
  const matchColorGroup = (v) => {
26587
26583
  const lv = v.trim().toLowerCase();
26588
- if (lv === "none") return null;
26584
+ if (lv === "" || lv === "none") return null;
26589
26585
  const tg = parsed.tagGroups.find((g) => g.name.toLowerCase() === lv);
26590
26586
  if (tg) return tg.name;
26591
26587
  if (lv === VALUE_NAME?.toLowerCase()) return VALUE_NAME;
@@ -26926,6 +26922,22 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26926
26922
  const tooltipText = fullText.length > 200 ? fullText.slice(0, 199) + "\u2026" : fullText;
26927
26923
  nodeG.append("title").text(tooltipText);
26928
26924
  }
26925
+ } else if (parsed.showValues && node.value !== void 0) {
26926
+ const valueLabel = parsed.boxMetric ? `${parsed.boxMetric}: ${node.value}` : String(node.value);
26927
+ const headerH = ln.height / 2;
26928
+ const sepY = -ln.height / 2 + headerH;
26929
+ const fitted = fitLabelToHeader(node.label, ln.width, 2);
26930
+ const labelLineH = fitted.fontSize * 1.3;
26931
+ const labelTotalH = fitted.lines.length * labelLineH;
26932
+ const headerCenterY = -ln.height / 2 + headerH / 2;
26933
+ for (let li = 0; li < fitted.lines.length; li++) {
26934
+ nodeG.append("text").attr("x", 0).attr(
26935
+ "y",
26936
+ headerCenterY - labelTotalH / 2 + labelLineH / 2 + li * labelLineH
26937
+ ).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]);
26938
+ }
26939
+ nodeG.append("line").attr("x1", -ln.width / 2).attr("y1", sepY).attr("x2", ln.width / 2).attr("y2", sepY).attr("stroke", colors.stroke).attr("stroke-opacity", 0.3).attr("stroke-width", 1);
26940
+ nodeG.append("text").attr("class", "bl-node-value").attr("x", 0).attr("y", (sepY + ln.height / 2) / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", VALUE_FONT_SIZE).attr("fill", colors.text).attr("opacity", 0.85).text(valueLabel);
26929
26941
  } else {
26930
26942
  const maxLabelLines = Math.max(
26931
26943
  2,
@@ -26938,21 +26950,16 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26938
26950
  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]);
26939
26951
  }
26940
26952
  }
26941
- if (parsed.showValues && node.value !== void 0) {
26953
+ if (parsed.showValues && node.value !== void 0 && desc && desc.length > 0 && !hideDescriptions) {
26942
26954
  const valueText = String(node.value);
26943
- const descShown = !!(desc && desc.length > 0 && !hideDescriptions);
26944
- if (descShown) {
26945
- const padX = 6;
26946
- const padY = 5;
26947
- const bw = valueText.length * VALUE_FONT_SIZE * CHAR_WIDTH_RATIO2 + 8;
26948
- const bh = VALUE_FONT_SIZE + 4;
26949
- const bx = ln.width / 2 - bw - 4;
26950
- const by = -ln.height / 2 + 4;
26951
- nodeG.append("rect").attr("x", bx).attr("y", by).attr("width", bw).attr("height", bh).attr("rx", 3).attr("fill", palette.bg).attr("opacity", 0.85);
26952
- nodeG.append("text").attr("class", "bl-node-value").attr("x", bx + bw - padX).attr("y", by + padY).attr("text-anchor", "end").attr("dominant-baseline", "central").attr("font-size", VALUE_FONT_SIZE).attr("font-weight", "600").attr("fill", palette.textMuted).text(valueText);
26953
- } else {
26954
- nodeG.append("text").attr("class", "bl-node-value").attr("x", 0).attr("y", ln.height / 2 - VALUE_FONT_SIZE).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", VALUE_FONT_SIZE).attr("font-weight", "600").attr("fill", colors.text).attr("opacity", 0.8).text(valueText);
26955
- }
26955
+ const padX = 6;
26956
+ const padY = 5;
26957
+ const bw = valueText.length * VALUE_FONT_SIZE * CHAR_WIDTH_RATIO2 + 8;
26958
+ const bh = VALUE_FONT_SIZE + 4;
26959
+ const bx = Math.max(-ln.width / 2 + 4, ln.width / 2 - bw - 4);
26960
+ const by = -ln.height / 2 + 4;
26961
+ 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);
26962
+ 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);
26956
26963
  }
26957
26964
  }
26958
26965
  const hasDescriptions = parsed.nodes.some(
@@ -27046,7 +27053,7 @@ var init_renderer6 = __esm({
27046
27053
  init_wrapped_desc();
27047
27054
  init_scaling();
27048
27055
  DIAGRAM_PADDING6 = 20;
27049
- NODE_FONT_SIZE = 13;
27056
+ NODE_FONT_SIZE = 11;
27050
27057
  MIN_NODE_FONT_SIZE = 9;
27051
27058
  EDGE_LABEL_FONT_SIZE4 = 11;
27052
27059
  EDGE_STROKE_WIDTH5 = 1.5;
@@ -46783,7 +46790,11 @@ function resolveMap(parsed, data) {
46783
46790
  if (bb && !isWholeSphere(bb)) containerBoxes.push(bb);
46784
46791
  }
46785
46792
  const containerUnion = unionExtent(containerBoxes, points);
46786
- if (containerUnion) extent2 = pad(containerUnion, PAD_FRACTION);
46793
+ if (containerUnion)
46794
+ extent2 = pad(
46795
+ clampContainerToCluster(containerUnion, points),
46796
+ PAD_FRACTION
46797
+ );
46787
46798
  }
46788
46799
  if (isPoiOnly) {
46789
46800
  const cx = (extent2[0][0] + extent2[1][0]) / 2;
@@ -46864,6 +46875,22 @@ function mostCommonCountry(regions, poiCountries) {
46864
46875
  }
46865
46876
  return best;
46866
46877
  }
46878
+ function clampContainerToCluster(container, points) {
46879
+ const poi = unionExtent([], points);
46880
+ if (!poi) return container;
46881
+ let [[west, south], [east, north]] = container;
46882
+ const [[pWest, pSouth], [pEast, pNorth]] = poi;
46883
+ south = Math.max(south, pSouth - CONTAINER_OVERSHOOT_DEG);
46884
+ north = Math.min(north, pNorth + CONTAINER_OVERSHOOT_DEG);
46885
+ if (east <= 180 && pEast <= 180) {
46886
+ west = Math.max(west, pWest - CONTAINER_OVERSHOOT_DEG);
46887
+ east = Math.min(east, pEast + CONTAINER_OVERSHOOT_DEG);
46888
+ }
46889
+ return [
46890
+ [west, south],
46891
+ [east, north]
46892
+ ];
46893
+ }
46867
46894
  function pad(e, frac) {
46868
46895
  const dLon = (e[1][0] - e[0][0]) * frac || 1;
46869
46896
  const dLat = (e[1][1] - e[0][1]) * frac || 1;
@@ -46876,7 +46903,7 @@ function firstError(diags) {
46876
46903
  const e = diags.find((d) => d.severity === "error");
46877
46904
  return e ? formatDgmoError(e) : null;
46878
46905
  }
46879
- var WORLD_SPAN, MERCATOR_MAX_LAT, PAD_FRACTION, REGION_PAD_FRACTION, WORLD_LAT_SOUTH, WORLD_LAT_NORTH, POI_ZOOM_FLOOR_DEG, US_NATIONAL_LON_SPAN, REGION_ALIASES, US_STATE_POSTAL;
46906
+ var WORLD_SPAN, MERCATOR_MAX_LAT, PAD_FRACTION, REGION_PAD_FRACTION, WORLD_LAT_SOUTH, WORLD_LAT_NORTH, POI_ZOOM_FLOOR_DEG, CONTAINER_OVERSHOOT_DEG, US_NATIONAL_LON_SPAN, REGION_ALIASES, US_STATE_POSTAL;
46880
46907
  var init_resolver2 = __esm({
46881
46908
  "src/map/resolver.ts"() {
46882
46909
  "use strict";
@@ -46889,6 +46916,7 @@ var init_resolver2 = __esm({
46889
46916
  WORLD_LAT_SOUTH = -58;
46890
46917
  WORLD_LAT_NORTH = 78;
46891
46918
  POI_ZOOM_FLOOR_DEG = 7;
46919
+ CONTAINER_OVERSHOOT_DEG = 8;
46892
46920
  US_NATIONAL_LON_SPAN = 48;
46893
46921
  REGION_ALIASES = {
46894
46922
  // Common everyday names → the Natural-Earth display name actually shipped.
@@ -46967,6 +46995,55 @@ var init_resolver2 = __esm({
46967
46995
  }
46968
46996
  });
46969
46997
 
46998
+ // src/map/legend-band.ts
46999
+ function mapLegendGroups(legend) {
47000
+ const ramp = legend.ramp;
47001
+ const scoreGroup = ramp ? {
47002
+ name: ramp.metric?.trim() || "Value",
47003
+ entries: [],
47004
+ gradient: {
47005
+ min: ramp.min,
47006
+ max: ramp.max,
47007
+ hue: ramp.hue,
47008
+ base: ramp.base
47009
+ }
47010
+ } : null;
47011
+ const tagGroups = legend.tagGroups.filter((g) => g.entries.length > 0).map((g) => ({ name: g.name, entries: [...g.entries] }));
47012
+ return [...scoreGroup ? [scoreGroup] : [], ...tagGroups];
47013
+ }
47014
+ function mapLegendConfig(groups, mode) {
47015
+ return {
47016
+ groups,
47017
+ position: { placement: "top-center", titleRelation: "below-title" },
47018
+ mode,
47019
+ showEmptyGroups: false,
47020
+ showInactivePills: true
47021
+ };
47022
+ }
47023
+ function mapLegendTop(hasTitle, hasSubtitle) {
47024
+ return (hasTitle ? TITLE_Y + TITLE_FONT_SIZE : 0) + (hasSubtitle ? TITLE_FONT_SIZE : 0) + LEGEND_TOP_GAP2;
47025
+ }
47026
+ function mapLegendBand(legend, opts) {
47027
+ if (!legend) return 0;
47028
+ const groups = mapLegendGroups(legend);
47029
+ if (groups.length === 0) return 0;
47030
+ const config = mapLegendConfig(groups, opts.mode);
47031
+ const state = { activeGroup: legend.activeGroup };
47032
+ const { height } = computeLegendLayout(config, state, opts.width);
47033
+ if (height <= 0) return 0;
47034
+ return mapLegendTop(opts.hasTitle, opts.hasSubtitle) + height + LEGEND_BOTTOM_GAP2;
47035
+ }
47036
+ var LEGEND_TOP_GAP2, LEGEND_BOTTOM_GAP2;
47037
+ var init_legend_band = __esm({
47038
+ "src/map/legend-band.ts"() {
47039
+ "use strict";
47040
+ init_legend_layout();
47041
+ init_title_constants();
47042
+ LEGEND_TOP_GAP2 = 8;
47043
+ LEGEND_BOTTOM_GAP2 = 10;
47044
+ }
47045
+ });
47046
+
46970
47047
  // src/map/colorize.ts
46971
47048
  function assignColors(isos, adjacency) {
46972
47049
  const sorted = [...isos].sort();
@@ -47558,12 +47635,43 @@ function layoutMap(resolved, data, size, opts) {
47558
47635
  return tagFill(r.tags, activeGroup) ?? neutralFill;
47559
47636
  };
47560
47637
  const regionById = new Map(resolved.regions.map((r) => [r.iso, r]));
47638
+ let legend = null;
47639
+ if (!resolved.directives.noLegend) {
47640
+ const legendTagGroups = resolved.tagGroups.map((g) => ({
47641
+ name: g.name,
47642
+ entries: g.entries.map((e) => ({ value: e.value, color: e.color }))
47643
+ }));
47644
+ if (legendTagGroups.length > 0 || hasRamp) {
47645
+ legend = {
47646
+ tagGroups: legendTagGroups,
47647
+ activeGroup,
47648
+ ...hasRamp && {
47649
+ ramp: {
47650
+ ...resolved.directives.regionMetric !== void 0 && {
47651
+ metric: resolved.directives.regionMetric
47652
+ },
47653
+ min: rampMin,
47654
+ max: rampMax,
47655
+ hue: rampHue,
47656
+ base: rampBase
47657
+ }
47658
+ }
47659
+ };
47660
+ }
47661
+ }
47561
47662
  const TITLE_GAP2 = 16;
47562
47663
  let topPad = FIT_PAD;
47563
47664
  if (resolved.title && resolved.pois.length > 0) {
47564
47665
  const bannerBottom = (resolved.subtitle ? TITLE_Y + TITLE_FONT_SIZE : TITLE_Y) + TITLE_FONT_SIZE / 2;
47565
47666
  topPad = Math.max(FIT_PAD, bannerBottom + TITLE_GAP2);
47566
47667
  }
47668
+ const legendBand = mapLegendBand(legend, {
47669
+ width,
47670
+ mode: opts.legendMode ?? "preview",
47671
+ hasTitle: Boolean(resolved.title),
47672
+ hasSubtitle: Boolean(resolved.subtitle)
47673
+ });
47674
+ if (legendBand > topPad) topPad = legendBand;
47567
47675
  const fitBox = [
47568
47676
  [FIT_PAD, topPad],
47569
47677
  [
@@ -47581,7 +47689,7 @@ function layoutMap(resolved, data, size, opts) {
47581
47689
  const by0 = cb[0][1];
47582
47690
  const cw = cb[1][0] - bx0;
47583
47691
  const ch = cb[1][1] - by0;
47584
- const topReserve = resolved.title && resolved.pois.length > 0 ? topPad : 0;
47692
+ const topReserve = resolved.title && resolved.pois.length > 0 || legendBand > 0 ? topPad : 0;
47585
47693
  const ox = 0;
47586
47694
  const oy = topReserve;
47587
47695
  const sx = cw > 0 ? width / cw : 1;
@@ -48645,30 +48753,6 @@ function layoutMap(resolved, data, size, opts) {
48645
48753
  });
48646
48754
  labels.push(...contextLabels);
48647
48755
  }
48648
- let legend = null;
48649
- if (!resolved.directives.noLegend) {
48650
- const tagGroups = resolved.tagGroups.map((g) => ({
48651
- name: g.name,
48652
- entries: g.entries.map((e) => ({ value: e.value, color: e.color }))
48653
- }));
48654
- if (tagGroups.length > 0 || hasRamp) {
48655
- legend = {
48656
- tagGroups,
48657
- activeGroup,
48658
- ...hasRamp && {
48659
- ramp: {
48660
- ...resolved.directives.regionMetric !== void 0 && {
48661
- metric: resolved.directives.regionMetric
48662
- },
48663
- min: rampMin,
48664
- max: rampMax,
48665
- hue: rampHue,
48666
- base: rampBase
48667
- }
48668
- }
48669
- };
48670
- }
48671
- }
48672
48756
  return {
48673
48757
  width,
48674
48758
  height,
@@ -48704,6 +48788,7 @@ var init_layout15 = __esm({
48704
48788
  init_label_layout();
48705
48789
  init_legend_constants();
48706
48790
  init_title_constants();
48791
+ init_legend_band();
48707
48792
  init_context_labels();
48708
48793
  FIT_PAD = 24;
48709
48794
  RAMP_FLOOR2 = 15;
@@ -48904,6 +48989,9 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
48904
48989
  // stretch-distorting. The in-app preview pane passes no exportDims → unset →
48905
48990
  // keeps the global stretch-fill.
48906
48991
  preferContain: exportDims?.preferContain ?? false,
48992
+ // Reserve the legend band for the mode actually drawn below (export shows
48993
+ // only the active group; preview keeps the inactive pills).
48994
+ legendMode: exportDims ? "export" : "preview",
48907
48995
  ...activeGroupOverride !== void 0 && {
48908
48996
  activeGroup: activeGroupOverride
48909
48997
  }
@@ -49195,30 +49283,12 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
49195
49283
  if (layout.legend) {
49196
49284
  const legendY = (layout.title ? TITLE_Y + TITLE_FONT_SIZE : 0) + (layout.subtitle ? TITLE_FONT_SIZE : 0) + 8;
49197
49285
  const legendG = svg.append("g").attr("class", "dgmo-map-legend").attr("transform", `translate(0, ${legendY})`);
49198
- const ramp = layout.legend.ramp;
49199
- const scoreGroup = ramp ? {
49200
- name: ramp.metric?.trim() || "Value",
49201
- entries: [],
49202
- gradient: {
49203
- min: ramp.min,
49204
- max: ramp.max,
49205
- hue: ramp.hue,
49206
- base: ramp.base
49207
- }
49208
- } : null;
49209
- const tagGroups = layout.legend.tagGroups.filter((g) => g.entries.length > 0).map((g) => ({ name: g.name, entries: [...g.entries] }));
49210
- const groups = [...scoreGroup ? [scoreGroup] : [], ...tagGroups];
49286
+ const groups = mapLegendGroups(layout.legend);
49211
49287
  if (groups.length > 0) {
49212
- const config = {
49288
+ const config = mapLegendConfig(
49213
49289
  groups,
49214
- position: { placement: "top-center", titleRelation: "below-title" },
49215
- mode: exportDims ? "export" : "preview",
49216
- showEmptyGroups: false,
49217
- // Keep inactive siblings visible as pills so the user can click to flip
49218
- // the active colouring dimension (preview only — export shows just the
49219
- // active group).
49220
- showInactivePills: true
49221
- };
49290
+ exportDims ? "export" : "preview"
49291
+ );
49222
49292
  const state = { activeGroup: layout.legend.activeGroup };
49223
49293
  renderLegendD3(legendG, config, state, palette, isDark, void 0, width);
49224
49294
  }
@@ -49262,6 +49332,7 @@ var init_renderer16 = __esm({
49262
49332
  init_title_constants();
49263
49333
  init_color_utils();
49264
49334
  init_legend_d3();
49335
+ init_legend_band();
49265
49336
  init_layout15();
49266
49337
  LABEL_FONT = 11;
49267
49338
  }
@@ -49283,9 +49354,10 @@ function mapContentAspect(resolved, data, ref = REF) {
49283
49354
  const aspect = w / h;
49284
49355
  return Number.isFinite(aspect) && aspect > 0 ? aspect : FALLBACK_ASPECT;
49285
49356
  }
49286
- function mapExportDimensions(resolved, data, baseWidth = 1200) {
49287
- const raw = mapContentAspect(resolved, data);
49288
- const clamped = Math.max(ASPECT_MIN, Math.min(ASPECT_MAX, raw));
49357
+ function mapExportDimensions(resolved, data, baseWidth = 1200, aspectOverride) {
49358
+ const useOverride = aspectOverride !== void 0 && Number.isFinite(aspectOverride) && aspectOverride > 0;
49359
+ const raw = useOverride ? aspectOverride : mapContentAspect(resolved, data);
49360
+ const clamped = useOverride ? raw : Math.max(ASPECT_MIN, Math.min(ASPECT_MAX, raw));
49289
49361
  const width = baseWidth;
49290
49362
  let height = Math.round(width / clamped);
49291
49363
  let chromeReserve = 0;
@@ -49298,7 +49370,7 @@ function mapExportDimensions(resolved, data, baseWidth = 1200) {
49298
49370
  height = Math.round(chromeReserve + MIN_MAP_BAND);
49299
49371
  floored = true;
49300
49372
  }
49301
- const preferContain = clamped !== raw || floored;
49373
+ const preferContain = useOverride ? floored : clamped !== raw || floored;
49302
49374
  return { width, height, preferContain };
49303
49375
  }
49304
49376
  var FIT_PAD2, TITLE_GAP, ASPECT_MAX, ASPECT_MIN, MIN_MAP_BAND, FALLBACK_ASPECT, REF;
@@ -55008,7 +55080,6 @@ function renderTimelineTagLegendOverlay(container, parsed, palette, isDark, setu
55008
55080
  function renderTimelineHorizontalTimeSort(container, parsed, palette, isDark, setup, hovers, onClickItem, _exportDims, _swimlaneTagGroup, _activeTagGroup, _onTagStateChange, _viewMode) {
55009
55081
  const {
55010
55082
  width,
55011
- height,
55012
55083
  tooltip,
55013
55084
  solid,
55014
55085
  textColor,
@@ -55057,8 +55128,7 @@ function renderTimelineHorizontalTimeSort(container, parsed, palette, isDark, se
55057
55128
  const markerLabelY = markerReserve ? -(topScaleH + MARKER_ROW_H / 2) : 0;
55058
55129
  const eraLabelY = eraReserve ? -(topScaleH + markerReserve + ERA_ROW_H / 2) : 0;
55059
55130
  const innerWidth = width - margin.left - margin.right;
55060
- const availInnerHeight = height - margin.top - margin.bottom;
55061
- const rowH = Math.min(ctx.structural(28), availInnerHeight / sorted.length);
55131
+ const rowH = ctx.structural(28);
55062
55132
  const innerHeight = rowH * sorted.length;
55063
55133
  const usedHeight = margin.top + innerHeight + margin.bottom;
55064
55134
  const xScale = d3Scale2.scaleLinear().domain([minDate - datePadding, maxDate + datePadding]).range([0, innerWidth]);
@@ -57583,7 +57653,12 @@ async function renderForExport(content, theme, palette, viewState, options) {
57583
57653
  }
57584
57654
  }
57585
57655
  const mapResolved = resolveMap2(mapParsed, mapData);
57586
- const dims2 = mapExportDimensions2(mapResolved, mapData, EXPORT_WIDTH);
57656
+ const dims2 = mapExportDimensions2(
57657
+ mapResolved,
57658
+ mapData,
57659
+ EXPORT_WIDTH,
57660
+ options?.mapAspect
57661
+ );
57587
57662
  const container2 = createExportContainer(dims2.width, dims2.height);
57588
57663
  renderMapForExport2(
57589
57664
  container2,
@@ -58727,6 +58802,134 @@ function extractInfraCounts(content) {
58727
58802
  return { nodes: parsed.nodes.length };
58728
58803
  }
58729
58804
 
58805
+ // src/utils/svg-embed.ts
58806
+ function normalizeSvgForEmbed(input) {
58807
+ let svg = input;
58808
+ const rootMatch = svg.match(/<svg[^>]*>/);
58809
+ const rootTag = rootMatch?.[0] ?? "";
58810
+ if (rootTag && !rootTag.includes("viewBox")) {
58811
+ const wh = rootTag.match(/width="(\d+)"[^>]*height="(\d+)"/);
58812
+ if (wh) {
58813
+ svg = svg.replace(/<svg/, `<svg viewBox="0 0 ${wh[1]} ${wh[2]}"`);
58814
+ }
58815
+ }
58816
+ const tight = computeBBox(svg);
58817
+ if (tight && tight.width > 0 && tight.height > 0) {
58818
+ const pad2 = 16;
58819
+ const vb = `${tight.x - pad2} ${tight.y - pad2} ${tight.width + pad2 * 2} ${tight.height + pad2 * 2}`;
58820
+ svg = svg.replace(/(<svg[^>]*?)viewBox="[^"]*"/, `$1viewBox="${vb}"`);
58821
+ }
58822
+ svg = svg.replace(/(<svg[^>]*?) width="[^"]*"/g, "$1");
58823
+ svg = svg.replace(/(<svg[^>]*?) height="[^"]*"/g, "$1");
58824
+ svg = svg.replace(/(<svg[^>]*?style="[^"]*?)background:[^;"]*;?\s*/g, "$1");
58825
+ svg = svg.replace(/<svg\s{2,}/g, "<svg ");
58826
+ return svg;
58827
+ }
58828
+ function getEmbedSvgViewBox(svg) {
58829
+ const tight = computeBBox(svg);
58830
+ if (!tight || tight.width <= 0 || tight.height <= 0) return null;
58831
+ const pad2 = 16;
58832
+ return {
58833
+ x: tight.x - pad2,
58834
+ y: tight.y - pad2,
58835
+ width: tight.width + pad2 * 2,
58836
+ height: tight.height + pad2 * 2
58837
+ };
58838
+ }
58839
+ function computeBBox(svg) {
58840
+ const xs = [];
58841
+ const ys = [];
58842
+ function push(x, y) {
58843
+ if (Number.isFinite(x) && Number.isFinite(y)) {
58844
+ xs.push(x);
58845
+ ys.push(y);
58846
+ }
58847
+ }
58848
+ function attr(tag, name) {
58849
+ const m = tag.match(new RegExp(`\\b${name}="([^"]*)"`));
58850
+ if (!m) return null;
58851
+ const n = parseFloat(m[1]);
58852
+ return Number.isFinite(n) ? n : null;
58853
+ }
58854
+ for (const m of svg.matchAll(/<rect\b[^>]*?\/?>/g)) {
58855
+ const tag = m[0];
58856
+ const x = attr(tag, "x");
58857
+ const y = attr(tag, "y");
58858
+ const w = attr(tag, "width");
58859
+ const h = attr(tag, "height");
58860
+ if (x !== null && y !== null && w !== null && h !== null) {
58861
+ push(x, y);
58862
+ push(x + w, y + h);
58863
+ }
58864
+ }
58865
+ for (const m of svg.matchAll(/<line\b[^>]*?\/?>/g)) {
58866
+ const tag = m[0];
58867
+ const x1 = attr(tag, "x1");
58868
+ const y1 = attr(tag, "y1");
58869
+ const x2 = attr(tag, "x2");
58870
+ const y2 = attr(tag, "y2");
58871
+ if (x1 !== null && y1 !== null && x2 !== null && y2 !== null) {
58872
+ push(x1, y1);
58873
+ push(x2, y2);
58874
+ }
58875
+ }
58876
+ for (const m of svg.matchAll(/<circle\b[^>]*?\/?>/g)) {
58877
+ const tag = m[0];
58878
+ const cx = attr(tag, "cx");
58879
+ const cy = attr(tag, "cy");
58880
+ const r = attr(tag, "r");
58881
+ if (cx !== null && cy !== null && r !== null) {
58882
+ push(cx - r, cy - r);
58883
+ push(cx + r, cy + r);
58884
+ }
58885
+ }
58886
+ for (const m of svg.matchAll(/<ellipse\b[^>]*?\/?>/g)) {
58887
+ const tag = m[0];
58888
+ const cx = attr(tag, "cx");
58889
+ const cy = attr(tag, "cy");
58890
+ const rx = attr(tag, "rx");
58891
+ const ry = attr(tag, "ry");
58892
+ if (cx !== null && cy !== null && rx !== null && ry !== null) {
58893
+ push(cx - rx, cy - ry);
58894
+ push(cx + rx, cy + ry);
58895
+ }
58896
+ }
58897
+ for (const m of svg.matchAll(/<text\b([^>]*?)>([\s\S]*?)<\/text>/g)) {
58898
+ const tag = `<text${m[1]}>`;
58899
+ const text = m[2].replace(/<[^>]+>/g, "");
58900
+ const x = attr(tag, "x");
58901
+ const y = attr(tag, "y");
58902
+ if (x !== null && y !== null) {
58903
+ const w = text.length * 7;
58904
+ push(x - w / 2, y - 14);
58905
+ push(x + w / 2, y + 4);
58906
+ }
58907
+ }
58908
+ for (const m of svg.matchAll(/<path\b[^>]*?\bd="([^"]+)"/g)) {
58909
+ const d = m[1];
58910
+ const nums = d.match(/-?\d+(?:\.\d+)?/g);
58911
+ if (!nums) continue;
58912
+ for (let i = 0; i + 1 < nums.length; i += 2) {
58913
+ push(parseFloat(nums[i]), parseFloat(nums[i + 1]));
58914
+ }
58915
+ }
58916
+ for (const m of svg.matchAll(
58917
+ /<(?:polygon|polyline)\b[^>]*?\bpoints="([^"]+)"/g
58918
+ )) {
58919
+ const nums = m[1].match(/-?\d+(?:\.\d+)?/g);
58920
+ if (!nums) continue;
58921
+ for (let i = 0; i + 1 < nums.length; i += 2) {
58922
+ push(parseFloat(nums[i]), parseFloat(nums[i + 1]));
58923
+ }
58924
+ }
58925
+ if (xs.length === 0 || ys.length === 0) return null;
58926
+ const minX = Math.min(...xs);
58927
+ const maxX = Math.max(...xs);
58928
+ const minY = Math.min(...ys);
58929
+ const maxY = Math.max(...ys);
58930
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
58931
+ }
58932
+
58730
58933
  // src/map/completion.ts
58731
58934
  var fold2 = (s) => s.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase().trim();
58732
58935
  var groupThousands = (n) => String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
@@ -58850,8 +59053,10 @@ export {
58850
59053
  decodeDiagramUrl2 as decodeDiagramUrl,
58851
59054
  encodeDiagramUrl2 as encodeDiagramUrl,
58852
59055
  formatDgmoError,
59056
+ getEmbedSvgViewBox,
58853
59057
  getMinDimensions,
58854
59058
  getPalette,
59059
+ normalizeSvgForEmbed,
58855
59060
  palettes,
58856
59061
  render2 as render,
58857
59062
  themes,