@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.cjs CHANGED
@@ -818,7 +818,7 @@ function withTagAliases(base, aliases) {
818
818
  function isReservedKey(registry, key) {
819
819
  return registry.keys.has(key) || registry.tagAliases.has(key);
820
820
  }
821
- 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;
821
+ 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;
822
822
  var init_reserved_key_registry = __esm({
823
823
  "src/utils/reserved-key-registry.ts"() {
824
824
  "use strict";
@@ -862,10 +862,6 @@ var init_reserved_key_registry = __esm({
862
862
  "description",
863
863
  "domain"
864
864
  ]);
865
- CLASS_REGISTRY = staticRegistry([
866
- "color",
867
- "description"
868
- ]);
869
865
  KANBAN_REGISTRY = staticRegistry([
870
866
  "color",
871
867
  "description",
@@ -941,7 +937,6 @@ var init_reserved_key_registry = __esm({
941
937
  "color",
942
938
  "description"
943
939
  ]);
944
- WIREFRAME_REGISTRY = staticRegistry([]);
945
940
  }
946
941
  });
947
942
 
@@ -4210,6 +4205,9 @@ var init_legend_layout = __esm({
4210
4205
  });
4211
4206
 
4212
4207
  // src/utils/legend-d3.ts
4208
+ function centerText(sel) {
4209
+ return sel.attr("dy", LEGEND_TEXT_DY);
4210
+ }
4213
4211
  function renderLegendD3(container, config, state, palette, isDark, callbacks, containerWidth) {
4214
4212
  const width = containerWidth ?? parseFloat(container.attr("width") || "800");
4215
4213
  let currentState = { ...state };
@@ -4292,21 +4290,21 @@ function renderCapsule(parent, capsule, palette, groupBg, pillBorder, _isDark, c
4292
4290
  const pill = capsule.pill;
4293
4291
  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);
4294
4292
  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);
4295
- 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);
4293
+ 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);
4296
4294
  if (capsule.gradient) {
4297
4295
  const gr = capsule.gradient;
4298
4296
  const gradId = `dgmo-legend-ramp-${capsule.groupName.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
4299
4297
  const def = g.append("defs").append("linearGradient").attr("id", gradId);
4300
4298
  def.append("stop").attr("offset", "0%").attr("stop-color", mix(gr.hue, gr.base, 15));
4301
4299
  def.append("stop").attr("offset", "100%").attr("stop-color", gr.hue);
4302
- 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);
4300
+ 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);
4303
4301
  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})`);
4304
- 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);
4302
+ 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);
4305
4303
  }
4306
4304
  for (const entry of capsule.entries) {
4307
4305
  const entryG = g.append("g").attr("data-legend-entry", entry.value.toLowerCase()).attr("data-series-name", entry.value).style("cursor", "pointer");
4308
4306
  entryG.append("circle").attr("cx", entry.dotCx).attr("cy", entry.dotCy).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
4309
- 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);
4307
+ 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);
4310
4308
  if (callbacks?.onEntryHover) {
4311
4309
  const groupName = capsule.groupName;
4312
4310
  const entryValue = entry.value;
@@ -4326,7 +4324,7 @@ function renderCapsule(parent, capsule, palette, groupBg, pillBorder, _isDark, c
4326
4324
  function renderPill(parent, pill, palette, groupBg, callbacks) {
4327
4325
  const g = parent.append("g").attr("transform", `translate(${pill.x},${pill.y})`).attr("data-legend-group", pill.groupName.toLowerCase()).style("cursor", "pointer");
4328
4326
  g.append("rect").attr("width", pill.width).attr("height", pill.height).attr("rx", pill.height / 2).attr("fill", groupBg);
4329
- 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);
4327
+ 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);
4330
4328
  if (callbacks?.onGroupToggle) {
4331
4329
  const cb = callbacks.onGroupToggle;
4332
4330
  const name = pill.groupName;
@@ -4349,7 +4347,7 @@ function renderControl(parent, ctrl, palette, _groupBg, pillBorder, _isDark, con
4349
4347
  textX = 8 + 14 + LEGEND_ENTRY_DOT_GAP + measureLegendText(ctrl.label, LEGEND_PILL_FONT_SIZE) / 2;
4350
4348
  }
4351
4349
  if (ctrl.label) {
4352
- 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);
4350
+ 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);
4353
4351
  }
4354
4352
  if (ctrl.children) {
4355
4353
  let cx = ctrl.width + 4;
@@ -4359,7 +4357,7 @@ function renderControl(parent, ctrl, palette, _groupBg, pillBorder, _isDark, con
4359
4357
  "fill",
4360
4358
  child.isActive ? palette.primary ?? palette.text : "none"
4361
4359
  ).attr("stroke", pillBorder).attr("stroke-width", 0.75);
4362
- 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);
4360
+ 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);
4363
4361
  const configCtrl2 = configControls?.find((c) => c.id === ctrl.id);
4364
4362
  const configChild = configCtrl2?.children?.find((c) => c.id === child.id);
4365
4363
  if (configChild?.onClick) {
@@ -4413,7 +4411,7 @@ function renderControlsGroup(parent, layout, palette, groupBg, pillBorder, callb
4413
4411
  } else {
4414
4412
  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);
4415
4413
  }
4416
- 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);
4414
+ 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);
4417
4415
  if (callbacks?.onControlsToggle && toggle) {
4418
4416
  const cb = callbacks.onControlsToggle;
4419
4417
  const id = tl.id;
@@ -4426,6 +4424,7 @@ function renderControlsGroup(parent, layout, palette, groupBg, pillBorder, callb
4426
4424
  }
4427
4425
  }
4428
4426
  }
4427
+ var LEGEND_TEXT_DY;
4429
4428
  var init_legend_d3 = __esm({
4430
4429
  "src/utils/legend-d3.ts"() {
4431
4430
  "use strict";
@@ -4433,6 +4432,7 @@ var init_legend_d3 = __esm({
4433
4432
  init_legend_layout();
4434
4433
  init_color_utils();
4435
4434
  init_fonts();
4435
+ LEGEND_TEXT_DY = "0.32em";
4436
4436
  }
4437
4437
  });
4438
4438
 
@@ -4683,7 +4683,6 @@ var init_arrows = __esm({
4683
4683
  var parser_exports = {};
4684
4684
  __export(parser_exports, {
4685
4685
  isSequenceBlock: () => isSequenceBlock,
4686
- isSequenceMessage: () => isSequenceMessage,
4687
4686
  isSequenceNote: () => isSequenceNote,
4688
4687
  isSequenceSection: () => isSequenceSection,
4689
4688
  looksLikeSequence: () => looksLikeSequence,
@@ -4699,9 +4698,6 @@ function isHardRemovedToken(remainder) {
4699
4698
  }
4700
4699
  return { removed: false };
4701
4700
  }
4702
- function isSequenceMessage(el) {
4703
- return el.kind === "message";
4704
- }
4705
4701
  function isSequenceBlock(el) {
4706
4702
  return el.kind === "block";
4707
4703
  }
@@ -26567,7 +26563,7 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26567
26563
  const VALUE_NAME = hasRamp ? parsed.boxMetric?.trim() || "Value" : null;
26568
26564
  const matchColorGroup = (v) => {
26569
26565
  const lv = v.trim().toLowerCase();
26570
- if (lv === "none") return null;
26566
+ if (lv === "" || lv === "none") return null;
26571
26567
  const tg = parsed.tagGroups.find((g) => g.name.toLowerCase() === lv);
26572
26568
  if (tg) return tg.name;
26573
26569
  if (lv === VALUE_NAME?.toLowerCase()) return VALUE_NAME;
@@ -26908,6 +26904,22 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26908
26904
  const tooltipText = fullText.length > 200 ? fullText.slice(0, 199) + "\u2026" : fullText;
26909
26905
  nodeG.append("title").text(tooltipText);
26910
26906
  }
26907
+ } else if (parsed.showValues && node.value !== void 0) {
26908
+ const valueLabel = parsed.boxMetric ? `${parsed.boxMetric}: ${node.value}` : String(node.value);
26909
+ const headerH = ln.height / 2;
26910
+ const sepY = -ln.height / 2 + headerH;
26911
+ const fitted = fitLabelToHeader(node.label, ln.width, 2);
26912
+ const labelLineH = fitted.fontSize * 1.3;
26913
+ const labelTotalH = fitted.lines.length * labelLineH;
26914
+ const headerCenterY = -ln.height / 2 + headerH / 2;
26915
+ for (let li = 0; li < fitted.lines.length; li++) {
26916
+ nodeG.append("text").attr("x", 0).attr(
26917
+ "y",
26918
+ headerCenterY - labelTotalH / 2 + labelLineH / 2 + li * labelLineH
26919
+ ).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]);
26920
+ }
26921
+ 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);
26922
+ 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);
26911
26923
  } else {
26912
26924
  const maxLabelLines = Math.max(
26913
26925
  2,
@@ -26920,21 +26932,16 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26920
26932
  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
26933
  }
26922
26934
  }
26923
- if (parsed.showValues && node.value !== void 0) {
26935
+ if (parsed.showValues && node.value !== void 0 && desc && desc.length > 0 && !hideDescriptions) {
26924
26936
  const valueText = String(node.value);
26925
- const descShown = !!(desc && desc.length > 0 && !hideDescriptions);
26926
- if (descShown) {
26927
- const padX = 6;
26928
- const padY = 5;
26929
- const bw = valueText.length * VALUE_FONT_SIZE * CHAR_WIDTH_RATIO2 + 8;
26930
- const bh = VALUE_FONT_SIZE + 4;
26931
- const bx = ln.width / 2 - bw - 4;
26932
- const by = -ln.height / 2 + 4;
26933
- nodeG.append("rect").attr("x", bx).attr("y", by).attr("width", bw).attr("height", bh).attr("rx", 3).attr("fill", palette.bg).attr("opacity", 0.85);
26934
- nodeG.append("text").attr("class", "bl-node-value").attr("x", bx + bw - padX).attr("y", by + padY).attr("text-anchor", "end").attr("dominant-baseline", "central").attr("font-size", VALUE_FONT_SIZE).attr("font-weight", "600").attr("fill", palette.textMuted).text(valueText);
26935
- } else {
26936
- nodeG.append("text").attr("class", "bl-node-value").attr("x", 0).attr("y", ln.height / 2 - VALUE_FONT_SIZE).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", VALUE_FONT_SIZE).attr("font-weight", "600").attr("fill", colors.text).attr("opacity", 0.8).text(valueText);
26937
- }
26937
+ const padX = 6;
26938
+ const padY = 5;
26939
+ const bw = valueText.length * VALUE_FONT_SIZE * CHAR_WIDTH_RATIO2 + 8;
26940
+ const bh = VALUE_FONT_SIZE + 4;
26941
+ const bx = Math.max(-ln.width / 2 + 4, ln.width / 2 - bw - 4);
26942
+ const by = -ln.height / 2 + 4;
26943
+ 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);
26944
+ 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);
26938
26945
  }
26939
26946
  }
26940
26947
  const hasDescriptions = parsed.nodes.some(
@@ -27030,7 +27037,7 @@ var init_renderer6 = __esm({
27030
27037
  init_wrapped_desc();
27031
27038
  init_scaling();
27032
27039
  DIAGRAM_PADDING6 = 20;
27033
- NODE_FONT_SIZE = 13;
27040
+ NODE_FONT_SIZE = 11;
27034
27041
  MIN_NODE_FONT_SIZE = 9;
27035
27042
  EDGE_LABEL_FONT_SIZE4 = 11;
27036
27043
  EDGE_STROKE_WIDTH5 = 1.5;
@@ -46767,7 +46774,11 @@ function resolveMap(parsed, data) {
46767
46774
  if (bb && !isWholeSphere(bb)) containerBoxes.push(bb);
46768
46775
  }
46769
46776
  const containerUnion = unionExtent(containerBoxes, points);
46770
- if (containerUnion) extent2 = pad(containerUnion, PAD_FRACTION);
46777
+ if (containerUnion)
46778
+ extent2 = pad(
46779
+ clampContainerToCluster(containerUnion, points),
46780
+ PAD_FRACTION
46781
+ );
46771
46782
  }
46772
46783
  if (isPoiOnly) {
46773
46784
  const cx = (extent2[0][0] + extent2[1][0]) / 2;
@@ -46848,6 +46859,22 @@ function mostCommonCountry(regions, poiCountries) {
46848
46859
  }
46849
46860
  return best;
46850
46861
  }
46862
+ function clampContainerToCluster(container, points) {
46863
+ const poi = unionExtent([], points);
46864
+ if (!poi) return container;
46865
+ let [[west, south], [east, north]] = container;
46866
+ const [[pWest, pSouth], [pEast, pNorth]] = poi;
46867
+ south = Math.max(south, pSouth - CONTAINER_OVERSHOOT_DEG);
46868
+ north = Math.min(north, pNorth + CONTAINER_OVERSHOOT_DEG);
46869
+ if (east <= 180 && pEast <= 180) {
46870
+ west = Math.max(west, pWest - CONTAINER_OVERSHOOT_DEG);
46871
+ east = Math.min(east, pEast + CONTAINER_OVERSHOOT_DEG);
46872
+ }
46873
+ return [
46874
+ [west, south],
46875
+ [east, north]
46876
+ ];
46877
+ }
46851
46878
  function pad(e, frac) {
46852
46879
  const dLon = (e[1][0] - e[0][0]) * frac || 1;
46853
46880
  const dLat = (e[1][1] - e[0][1]) * frac || 1;
@@ -46860,7 +46887,7 @@ function firstError(diags) {
46860
46887
  const e = diags.find((d) => d.severity === "error");
46861
46888
  return e ? formatDgmoError(e) : null;
46862
46889
  }
46863
- 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;
46890
+ 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;
46864
46891
  var init_resolver2 = __esm({
46865
46892
  "src/map/resolver.ts"() {
46866
46893
  "use strict";
@@ -46873,6 +46900,7 @@ var init_resolver2 = __esm({
46873
46900
  WORLD_LAT_SOUTH = -58;
46874
46901
  WORLD_LAT_NORTH = 78;
46875
46902
  POI_ZOOM_FLOOR_DEG = 7;
46903
+ CONTAINER_OVERSHOOT_DEG = 8;
46876
46904
  US_NATIONAL_LON_SPAN = 48;
46877
46905
  REGION_ALIASES = {
46878
46906
  // Common everyday names → the Natural-Earth display name actually shipped.
@@ -46951,6 +46979,55 @@ var init_resolver2 = __esm({
46951
46979
  }
46952
46980
  });
46953
46981
 
46982
+ // src/map/legend-band.ts
46983
+ function mapLegendGroups(legend) {
46984
+ const ramp = legend.ramp;
46985
+ const scoreGroup = ramp ? {
46986
+ name: ramp.metric?.trim() || "Value",
46987
+ entries: [],
46988
+ gradient: {
46989
+ min: ramp.min,
46990
+ max: ramp.max,
46991
+ hue: ramp.hue,
46992
+ base: ramp.base
46993
+ }
46994
+ } : null;
46995
+ const tagGroups = legend.tagGroups.filter((g) => g.entries.length > 0).map((g) => ({ name: g.name, entries: [...g.entries] }));
46996
+ return [...scoreGroup ? [scoreGroup] : [], ...tagGroups];
46997
+ }
46998
+ function mapLegendConfig(groups, mode) {
46999
+ return {
47000
+ groups,
47001
+ position: { placement: "top-center", titleRelation: "below-title" },
47002
+ mode,
47003
+ showEmptyGroups: false,
47004
+ showInactivePills: true
47005
+ };
47006
+ }
47007
+ function mapLegendTop(hasTitle, hasSubtitle) {
47008
+ return (hasTitle ? TITLE_Y + TITLE_FONT_SIZE : 0) + (hasSubtitle ? TITLE_FONT_SIZE : 0) + LEGEND_TOP_GAP2;
47009
+ }
47010
+ function mapLegendBand(legend, opts) {
47011
+ if (!legend) return 0;
47012
+ const groups = mapLegendGroups(legend);
47013
+ if (groups.length === 0) return 0;
47014
+ const config = mapLegendConfig(groups, opts.mode);
47015
+ const state = { activeGroup: legend.activeGroup };
47016
+ const { height } = computeLegendLayout(config, state, opts.width);
47017
+ if (height <= 0) return 0;
47018
+ return mapLegendTop(opts.hasTitle, opts.hasSubtitle) + height + LEGEND_BOTTOM_GAP2;
47019
+ }
47020
+ var LEGEND_TOP_GAP2, LEGEND_BOTTOM_GAP2;
47021
+ var init_legend_band = __esm({
47022
+ "src/map/legend-band.ts"() {
47023
+ "use strict";
47024
+ init_legend_layout();
47025
+ init_title_constants();
47026
+ LEGEND_TOP_GAP2 = 8;
47027
+ LEGEND_BOTTOM_GAP2 = 10;
47028
+ }
47029
+ });
47030
+
46954
47031
  // src/map/colorize.ts
46955
47032
  function assignColors(isos, adjacency) {
46956
47033
  const sorted = [...isos].sort();
@@ -47531,12 +47608,43 @@ function layoutMap(resolved, data, size, opts) {
47531
47608
  return tagFill(r.tags, activeGroup) ?? neutralFill;
47532
47609
  };
47533
47610
  const regionById = new Map(resolved.regions.map((r) => [r.iso, r]));
47611
+ let legend = null;
47612
+ if (!resolved.directives.noLegend) {
47613
+ const legendTagGroups = resolved.tagGroups.map((g) => ({
47614
+ name: g.name,
47615
+ entries: g.entries.map((e) => ({ value: e.value, color: e.color }))
47616
+ }));
47617
+ if (legendTagGroups.length > 0 || hasRamp) {
47618
+ legend = {
47619
+ tagGroups: legendTagGroups,
47620
+ activeGroup,
47621
+ ...hasRamp && {
47622
+ ramp: {
47623
+ ...resolved.directives.regionMetric !== void 0 && {
47624
+ metric: resolved.directives.regionMetric
47625
+ },
47626
+ min: rampMin,
47627
+ max: rampMax,
47628
+ hue: rampHue,
47629
+ base: rampBase
47630
+ }
47631
+ }
47632
+ };
47633
+ }
47634
+ }
47534
47635
  const TITLE_GAP2 = 16;
47535
47636
  let topPad = FIT_PAD;
47536
47637
  if (resolved.title && resolved.pois.length > 0) {
47537
47638
  const bannerBottom = (resolved.subtitle ? TITLE_Y + TITLE_FONT_SIZE : TITLE_Y) + TITLE_FONT_SIZE / 2;
47538
47639
  topPad = Math.max(FIT_PAD, bannerBottom + TITLE_GAP2);
47539
47640
  }
47641
+ const legendBand = mapLegendBand(legend, {
47642
+ width,
47643
+ mode: opts.legendMode ?? "preview",
47644
+ hasTitle: Boolean(resolved.title),
47645
+ hasSubtitle: Boolean(resolved.subtitle)
47646
+ });
47647
+ if (legendBand > topPad) topPad = legendBand;
47540
47648
  const fitBox = [
47541
47649
  [FIT_PAD, topPad],
47542
47650
  [
@@ -47554,7 +47662,7 @@ function layoutMap(resolved, data, size, opts) {
47554
47662
  const by0 = cb[0][1];
47555
47663
  const cw = cb[1][0] - bx0;
47556
47664
  const ch = cb[1][1] - by0;
47557
- const topReserve = resolved.title && resolved.pois.length > 0 ? topPad : 0;
47665
+ const topReserve = resolved.title && resolved.pois.length > 0 || legendBand > 0 ? topPad : 0;
47558
47666
  const ox = 0;
47559
47667
  const oy = topReserve;
47560
47668
  const sx = cw > 0 ? width / cw : 1;
@@ -48618,30 +48726,6 @@ function layoutMap(resolved, data, size, opts) {
48618
48726
  });
48619
48727
  labels.push(...contextLabels);
48620
48728
  }
48621
- let legend = null;
48622
- if (!resolved.directives.noLegend) {
48623
- const tagGroups = resolved.tagGroups.map((g) => ({
48624
- name: g.name,
48625
- entries: g.entries.map((e) => ({ value: e.value, color: e.color }))
48626
- }));
48627
- if (tagGroups.length > 0 || hasRamp) {
48628
- legend = {
48629
- tagGroups,
48630
- activeGroup,
48631
- ...hasRamp && {
48632
- ramp: {
48633
- ...resolved.directives.regionMetric !== void 0 && {
48634
- metric: resolved.directives.regionMetric
48635
- },
48636
- min: rampMin,
48637
- max: rampMax,
48638
- hue: rampHue,
48639
- base: rampBase
48640
- }
48641
- }
48642
- };
48643
- }
48644
- }
48645
48729
  return {
48646
48730
  width,
48647
48731
  height,
@@ -48679,6 +48763,7 @@ var init_layout15 = __esm({
48679
48763
  init_label_layout();
48680
48764
  init_legend_constants();
48681
48765
  init_title_constants();
48766
+ init_legend_band();
48682
48767
  init_context_labels();
48683
48768
  FIT_PAD = 24;
48684
48769
  RAMP_FLOOR2 = 15;
@@ -48878,6 +48963,9 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
48878
48963
  // stretch-distorting. The in-app preview pane passes no exportDims → unset →
48879
48964
  // keeps the global stretch-fill.
48880
48965
  preferContain: exportDims?.preferContain ?? false,
48966
+ // Reserve the legend band for the mode actually drawn below (export shows
48967
+ // only the active group; preview keeps the inactive pills).
48968
+ legendMode: exportDims ? "export" : "preview",
48881
48969
  ...activeGroupOverride !== void 0 && {
48882
48970
  activeGroup: activeGroupOverride
48883
48971
  }
@@ -49169,30 +49257,12 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
49169
49257
  if (layout.legend) {
49170
49258
  const legendY = (layout.title ? TITLE_Y + TITLE_FONT_SIZE : 0) + (layout.subtitle ? TITLE_FONT_SIZE : 0) + 8;
49171
49259
  const legendG = svg.append("g").attr("class", "dgmo-map-legend").attr("transform", `translate(0, ${legendY})`);
49172
- const ramp = layout.legend.ramp;
49173
- const scoreGroup = ramp ? {
49174
- name: ramp.metric?.trim() || "Value",
49175
- entries: [],
49176
- gradient: {
49177
- min: ramp.min,
49178
- max: ramp.max,
49179
- hue: ramp.hue,
49180
- base: ramp.base
49181
- }
49182
- } : null;
49183
- const tagGroups = layout.legend.tagGroups.filter((g) => g.entries.length > 0).map((g) => ({ name: g.name, entries: [...g.entries] }));
49184
- const groups = [...scoreGroup ? [scoreGroup] : [], ...tagGroups];
49260
+ const groups = mapLegendGroups(layout.legend);
49185
49261
  if (groups.length > 0) {
49186
- const config = {
49262
+ const config = mapLegendConfig(
49187
49263
  groups,
49188
- position: { placement: "top-center", titleRelation: "below-title" },
49189
- mode: exportDims ? "export" : "preview",
49190
- showEmptyGroups: false,
49191
- // Keep inactive siblings visible as pills so the user can click to flip
49192
- // the active colouring dimension (preview only — export shows just the
49193
- // active group).
49194
- showInactivePills: true
49195
- };
49264
+ exportDims ? "export" : "preview"
49265
+ );
49196
49266
  const state = { activeGroup: layout.legend.activeGroup };
49197
49267
  renderLegendD3(legendG, config, state, palette, isDark, void 0, width);
49198
49268
  }
@@ -49237,6 +49307,7 @@ var init_renderer16 = __esm({
49237
49307
  init_title_constants();
49238
49308
  init_color_utils();
49239
49309
  init_legend_d3();
49310
+ init_legend_band();
49240
49311
  init_layout15();
49241
49312
  LABEL_FONT = 11;
49242
49313
  }
@@ -49257,9 +49328,10 @@ function mapContentAspect(resolved, data, ref = REF) {
49257
49328
  const aspect = w / h;
49258
49329
  return Number.isFinite(aspect) && aspect > 0 ? aspect : FALLBACK_ASPECT;
49259
49330
  }
49260
- function mapExportDimensions(resolved, data, baseWidth = 1200) {
49261
- const raw = mapContentAspect(resolved, data);
49262
- const clamped = Math.max(ASPECT_MIN, Math.min(ASPECT_MAX, raw));
49331
+ function mapExportDimensions(resolved, data, baseWidth = 1200, aspectOverride) {
49332
+ const useOverride = aspectOverride !== void 0 && Number.isFinite(aspectOverride) && aspectOverride > 0;
49333
+ const raw = useOverride ? aspectOverride : mapContentAspect(resolved, data);
49334
+ const clamped = useOverride ? raw : Math.max(ASPECT_MIN, Math.min(ASPECT_MAX, raw));
49263
49335
  const width = baseWidth;
49264
49336
  let height = Math.round(width / clamped);
49265
49337
  let chromeReserve = 0;
@@ -49272,7 +49344,7 @@ function mapExportDimensions(resolved, data, baseWidth = 1200) {
49272
49344
  height = Math.round(chromeReserve + MIN_MAP_BAND);
49273
49345
  floored = true;
49274
49346
  }
49275
- const preferContain = clamped !== raw || floored;
49347
+ const preferContain = useOverride ? floored : clamped !== raw || floored;
49276
49348
  return { width, height, preferContain };
49277
49349
  }
49278
49350
  var import_d3_geo3, FIT_PAD2, TITLE_GAP, ASPECT_MAX, ASPECT_MIN, MIN_MAP_BAND, FALLBACK_ASPECT, REF;
@@ -54979,7 +55051,6 @@ function renderTimelineTagLegendOverlay(container, parsed, palette, isDark, setu
54979
55051
  function renderTimelineHorizontalTimeSort(container, parsed, palette, isDark, setup, hovers, onClickItem, _exportDims, _swimlaneTagGroup, _activeTagGroup, _onTagStateChange, _viewMode) {
54980
55052
  const {
54981
55053
  width,
54982
- height,
54983
55054
  tooltip,
54984
55055
  solid,
54985
55056
  textColor,
@@ -55028,8 +55099,7 @@ function renderTimelineHorizontalTimeSort(container, parsed, palette, isDark, se
55028
55099
  const markerLabelY = markerReserve ? -(topScaleH + MARKER_ROW_H / 2) : 0;
55029
55100
  const eraLabelY = eraReserve ? -(topScaleH + markerReserve + ERA_ROW_H / 2) : 0;
55030
55101
  const innerWidth = width - margin.left - margin.right;
55031
- const availInnerHeight = height - margin.top - margin.bottom;
55032
- const rowH = Math.min(ctx.structural(28), availInnerHeight / sorted.length);
55102
+ const rowH = ctx.structural(28);
55033
55103
  const innerHeight = rowH * sorted.length;
55034
55104
  const usedHeight = margin.top + innerHeight + margin.bottom;
55035
55105
  const xScale = d3Scale2.scaleLinear().domain([minDate - datePadding, maxDate + datePadding]).range([0, innerWidth]);
@@ -57554,7 +57624,12 @@ async function renderForExport(content, theme, palette, viewState, options) {
57554
57624
  }
57555
57625
  }
57556
57626
  const mapResolved = resolveMap2(mapParsed, mapData);
57557
- const dims2 = mapExportDimensions2(mapResolved, mapData, EXPORT_WIDTH);
57627
+ const dims2 = mapExportDimensions2(
57628
+ mapResolved,
57629
+ mapData,
57630
+ EXPORT_WIDTH,
57631
+ options?.mapAspect
57632
+ );
57558
57633
  const container2 = createExportContainer(dims2.width, dims2.height);
57559
57634
  renderMapForExport2(
57560
57635
  container2,
@@ -58347,8 +58422,10 @@ __export(index_exports, {
58347
58422
  decodeDiagramUrl: () => decodeDiagramUrl2,
58348
58423
  encodeDiagramUrl: () => encodeDiagramUrl2,
58349
58424
  formatDgmoError: () => formatDgmoError,
58425
+ getEmbedSvgViewBox: () => getEmbedSvgViewBox,
58350
58426
  getMinDimensions: () => getMinDimensions,
58351
58427
  getPalette: () => getPalette,
58428
+ normalizeSvgForEmbed: () => normalizeSvgForEmbed,
58352
58429
  palettes: () => palettes,
58353
58430
  render: () => render2,
58354
58431
  themes: () => themes,
@@ -58720,6 +58797,134 @@ function extractInfraCounts(content) {
58720
58797
  return { nodes: parsed.nodes.length };
58721
58798
  }
58722
58799
 
58800
+ // src/utils/svg-embed.ts
58801
+ function normalizeSvgForEmbed(input) {
58802
+ let svg = input;
58803
+ const rootMatch = svg.match(/<svg[^>]*>/);
58804
+ const rootTag = rootMatch?.[0] ?? "";
58805
+ if (rootTag && !rootTag.includes("viewBox")) {
58806
+ const wh = rootTag.match(/width="(\d+)"[^>]*height="(\d+)"/);
58807
+ if (wh) {
58808
+ svg = svg.replace(/<svg/, `<svg viewBox="0 0 ${wh[1]} ${wh[2]}"`);
58809
+ }
58810
+ }
58811
+ const tight = computeBBox(svg);
58812
+ if (tight && tight.width > 0 && tight.height > 0) {
58813
+ const pad2 = 16;
58814
+ const vb = `${tight.x - pad2} ${tight.y - pad2} ${tight.width + pad2 * 2} ${tight.height + pad2 * 2}`;
58815
+ svg = svg.replace(/(<svg[^>]*?)viewBox="[^"]*"/, `$1viewBox="${vb}"`);
58816
+ }
58817
+ svg = svg.replace(/(<svg[^>]*?) width="[^"]*"/g, "$1");
58818
+ svg = svg.replace(/(<svg[^>]*?) height="[^"]*"/g, "$1");
58819
+ svg = svg.replace(/(<svg[^>]*?style="[^"]*?)background:[^;"]*;?\s*/g, "$1");
58820
+ svg = svg.replace(/<svg\s{2,}/g, "<svg ");
58821
+ return svg;
58822
+ }
58823
+ function getEmbedSvgViewBox(svg) {
58824
+ const tight = computeBBox(svg);
58825
+ if (!tight || tight.width <= 0 || tight.height <= 0) return null;
58826
+ const pad2 = 16;
58827
+ return {
58828
+ x: tight.x - pad2,
58829
+ y: tight.y - pad2,
58830
+ width: tight.width + pad2 * 2,
58831
+ height: tight.height + pad2 * 2
58832
+ };
58833
+ }
58834
+ function computeBBox(svg) {
58835
+ const xs = [];
58836
+ const ys = [];
58837
+ function push(x, y) {
58838
+ if (Number.isFinite(x) && Number.isFinite(y)) {
58839
+ xs.push(x);
58840
+ ys.push(y);
58841
+ }
58842
+ }
58843
+ function attr(tag, name) {
58844
+ const m = tag.match(new RegExp(`\\b${name}="([^"]*)"`));
58845
+ if (!m) return null;
58846
+ const n = parseFloat(m[1]);
58847
+ return Number.isFinite(n) ? n : null;
58848
+ }
58849
+ for (const m of svg.matchAll(/<rect\b[^>]*?\/?>/g)) {
58850
+ const tag = m[0];
58851
+ const x = attr(tag, "x");
58852
+ const y = attr(tag, "y");
58853
+ const w = attr(tag, "width");
58854
+ const h = attr(tag, "height");
58855
+ if (x !== null && y !== null && w !== null && h !== null) {
58856
+ push(x, y);
58857
+ push(x + w, y + h);
58858
+ }
58859
+ }
58860
+ for (const m of svg.matchAll(/<line\b[^>]*?\/?>/g)) {
58861
+ const tag = m[0];
58862
+ const x1 = attr(tag, "x1");
58863
+ const y1 = attr(tag, "y1");
58864
+ const x2 = attr(tag, "x2");
58865
+ const y2 = attr(tag, "y2");
58866
+ if (x1 !== null && y1 !== null && x2 !== null && y2 !== null) {
58867
+ push(x1, y1);
58868
+ push(x2, y2);
58869
+ }
58870
+ }
58871
+ for (const m of svg.matchAll(/<circle\b[^>]*?\/?>/g)) {
58872
+ const tag = m[0];
58873
+ const cx = attr(tag, "cx");
58874
+ const cy = attr(tag, "cy");
58875
+ const r = attr(tag, "r");
58876
+ if (cx !== null && cy !== null && r !== null) {
58877
+ push(cx - r, cy - r);
58878
+ push(cx + r, cy + r);
58879
+ }
58880
+ }
58881
+ for (const m of svg.matchAll(/<ellipse\b[^>]*?\/?>/g)) {
58882
+ const tag = m[0];
58883
+ const cx = attr(tag, "cx");
58884
+ const cy = attr(tag, "cy");
58885
+ const rx = attr(tag, "rx");
58886
+ const ry = attr(tag, "ry");
58887
+ if (cx !== null && cy !== null && rx !== null && ry !== null) {
58888
+ push(cx - rx, cy - ry);
58889
+ push(cx + rx, cy + ry);
58890
+ }
58891
+ }
58892
+ for (const m of svg.matchAll(/<text\b([^>]*?)>([\s\S]*?)<\/text>/g)) {
58893
+ const tag = `<text${m[1]}>`;
58894
+ const text = m[2].replace(/<[^>]+>/g, "");
58895
+ const x = attr(tag, "x");
58896
+ const y = attr(tag, "y");
58897
+ if (x !== null && y !== null) {
58898
+ const w = text.length * 7;
58899
+ push(x - w / 2, y - 14);
58900
+ push(x + w / 2, y + 4);
58901
+ }
58902
+ }
58903
+ for (const m of svg.matchAll(/<path\b[^>]*?\bd="([^"]+)"/g)) {
58904
+ const d = m[1];
58905
+ const nums = d.match(/-?\d+(?:\.\d+)?/g);
58906
+ if (!nums) continue;
58907
+ for (let i = 0; i + 1 < nums.length; i += 2) {
58908
+ push(parseFloat(nums[i]), parseFloat(nums[i + 1]));
58909
+ }
58910
+ }
58911
+ for (const m of svg.matchAll(
58912
+ /<(?:polygon|polyline)\b[^>]*?\bpoints="([^"]+)"/g
58913
+ )) {
58914
+ const nums = m[1].match(/-?\d+(?:\.\d+)?/g);
58915
+ if (!nums) continue;
58916
+ for (let i = 0; i + 1 < nums.length; i += 2) {
58917
+ push(parseFloat(nums[i]), parseFloat(nums[i + 1]));
58918
+ }
58919
+ }
58920
+ if (xs.length === 0 || ys.length === 0) return null;
58921
+ const minX = Math.min(...xs);
58922
+ const maxX = Math.max(...xs);
58923
+ const minY = Math.min(...ys);
58924
+ const maxY = Math.max(...ys);
58925
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
58926
+ }
58927
+
58723
58928
  // src/map/completion.ts
58724
58929
  var fold2 = (s) => s.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase().trim();
58725
58930
  var groupThousands = (n) => String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
@@ -58844,8 +59049,10 @@ function decodeDiagramUrl2(url) {
58844
59049
  decodeDiagramUrl,
58845
59050
  encodeDiagramUrl,
58846
59051
  formatDgmoError,
59052
+ getEmbedSvgViewBox,
58847
59053
  getMinDimensions,
58848
59054
  getPalette,
59055
+ normalizeSvgForEmbed,
58849
59056
  palettes,
58850
59057
  render,
58851
59058
  themes,