@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/auto.cjs CHANGED
@@ -743,7 +743,7 @@ function withTagAliases(base, aliases) {
743
743
  function isReservedKey(registry, key) {
744
744
  return registry.keys.has(key) || registry.tagAliases.has(key);
745
745
  }
746
- 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;
746
+ 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;
747
747
  var init_reserved_key_registry = __esm({
748
748
  "src/utils/reserved-key-registry.ts"() {
749
749
  "use strict";
@@ -787,10 +787,6 @@ var init_reserved_key_registry = __esm({
787
787
  "description",
788
788
  "domain"
789
789
  ]);
790
- CLASS_REGISTRY = staticRegistry([
791
- "color",
792
- "description"
793
- ]);
794
790
  KANBAN_REGISTRY = staticRegistry([
795
791
  "color",
796
792
  "description",
@@ -866,7 +862,6 @@ var init_reserved_key_registry = __esm({
866
862
  "color",
867
863
  "description"
868
864
  ]);
869
- WIREFRAME_REGISTRY = staticRegistry([]);
870
865
  }
871
866
  });
872
867
 
@@ -4135,6 +4130,9 @@ var init_legend_layout = __esm({
4135
4130
  });
4136
4131
 
4137
4132
  // src/utils/legend-d3.ts
4133
+ function centerText(sel) {
4134
+ return sel.attr("dy", LEGEND_TEXT_DY);
4135
+ }
4138
4136
  function renderLegendD3(container, config, state, palette, isDark, callbacks, containerWidth) {
4139
4137
  const width = containerWidth ?? parseFloat(container.attr("width") || "800");
4140
4138
  let currentState = { ...state };
@@ -4217,21 +4215,21 @@ function renderCapsule(parent, capsule, palette, groupBg, pillBorder, _isDark, c
4217
4215
  const pill = capsule.pill;
4218
4216
  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);
4219
4217
  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);
4220
- 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);
4218
+ 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);
4221
4219
  if (capsule.gradient) {
4222
4220
  const gr = capsule.gradient;
4223
4221
  const gradId = `dgmo-legend-ramp-${capsule.groupName.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
4224
4222
  const def = g.append("defs").append("linearGradient").attr("id", gradId);
4225
4223
  def.append("stop").attr("offset", "0%").attr("stop-color", mix(gr.hue, gr.base, 15));
4226
4224
  def.append("stop").attr("offset", "100%").attr("stop-color", gr.hue);
4227
- 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);
4225
+ 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);
4228
4226
  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})`);
4229
- 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);
4227
+ 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);
4230
4228
  }
4231
4229
  for (const entry of capsule.entries) {
4232
4230
  const entryG = g.append("g").attr("data-legend-entry", entry.value.toLowerCase()).attr("data-series-name", entry.value).style("cursor", "pointer");
4233
4231
  entryG.append("circle").attr("cx", entry.dotCx).attr("cy", entry.dotCy).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
4234
- 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);
4232
+ 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);
4235
4233
  if (callbacks?.onEntryHover) {
4236
4234
  const groupName = capsule.groupName;
4237
4235
  const entryValue = entry.value;
@@ -4251,7 +4249,7 @@ function renderCapsule(parent, capsule, palette, groupBg, pillBorder, _isDark, c
4251
4249
  function renderPill(parent, pill, palette, groupBg, callbacks) {
4252
4250
  const g = parent.append("g").attr("transform", `translate(${pill.x},${pill.y})`).attr("data-legend-group", pill.groupName.toLowerCase()).style("cursor", "pointer");
4253
4251
  g.append("rect").attr("width", pill.width).attr("height", pill.height).attr("rx", pill.height / 2).attr("fill", groupBg);
4254
- 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);
4252
+ 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);
4255
4253
  if (callbacks?.onGroupToggle) {
4256
4254
  const cb = callbacks.onGroupToggle;
4257
4255
  const name = pill.groupName;
@@ -4274,7 +4272,7 @@ function renderControl(parent, ctrl, palette, _groupBg, pillBorder, _isDark, con
4274
4272
  textX = 8 + 14 + LEGEND_ENTRY_DOT_GAP + measureLegendText(ctrl.label, LEGEND_PILL_FONT_SIZE) / 2;
4275
4273
  }
4276
4274
  if (ctrl.label) {
4277
- 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);
4275
+ 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);
4278
4276
  }
4279
4277
  if (ctrl.children) {
4280
4278
  let cx = ctrl.width + 4;
@@ -4284,7 +4282,7 @@ function renderControl(parent, ctrl, palette, _groupBg, pillBorder, _isDark, con
4284
4282
  "fill",
4285
4283
  child.isActive ? palette.primary ?? palette.text : "none"
4286
4284
  ).attr("stroke", pillBorder).attr("stroke-width", 0.75);
4287
- 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);
4285
+ 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);
4288
4286
  const configCtrl2 = configControls?.find((c) => c.id === ctrl.id);
4289
4287
  const configChild = configCtrl2?.children?.find((c) => c.id === child.id);
4290
4288
  if (configChild?.onClick) {
@@ -4338,7 +4336,7 @@ function renderControlsGroup(parent, layout, palette, groupBg, pillBorder, callb
4338
4336
  } else {
4339
4337
  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);
4340
4338
  }
4341
- 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);
4339
+ 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);
4342
4340
  if (callbacks?.onControlsToggle && toggle) {
4343
4341
  const cb = callbacks.onControlsToggle;
4344
4342
  const id = tl.id;
@@ -4351,6 +4349,7 @@ function renderControlsGroup(parent, layout, palette, groupBg, pillBorder, callb
4351
4349
  }
4352
4350
  }
4353
4351
  }
4352
+ var LEGEND_TEXT_DY;
4354
4353
  var init_legend_d3 = __esm({
4355
4354
  "src/utils/legend-d3.ts"() {
4356
4355
  "use strict";
@@ -4358,6 +4357,7 @@ var init_legend_d3 = __esm({
4358
4357
  init_legend_layout();
4359
4358
  init_color_utils();
4360
4359
  init_fonts();
4360
+ LEGEND_TEXT_DY = "0.32em";
4361
4361
  }
4362
4362
  });
4363
4363
 
@@ -4608,7 +4608,6 @@ var init_arrows = __esm({
4608
4608
  var parser_exports = {};
4609
4609
  __export(parser_exports, {
4610
4610
  isSequenceBlock: () => isSequenceBlock,
4611
- isSequenceMessage: () => isSequenceMessage,
4612
4611
  isSequenceNote: () => isSequenceNote,
4613
4612
  isSequenceSection: () => isSequenceSection,
4614
4613
  looksLikeSequence: () => looksLikeSequence,
@@ -4624,9 +4623,6 @@ function isHardRemovedToken(remainder) {
4624
4623
  }
4625
4624
  return { removed: false };
4626
4625
  }
4627
- function isSequenceMessage(el) {
4628
- return el.kind === "message";
4629
- }
4630
4626
  function isSequenceBlock(el) {
4631
4627
  return el.kind === "block";
4632
4628
  }
@@ -26492,7 +26488,7 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26492
26488
  const VALUE_NAME = hasRamp ? parsed.boxMetric?.trim() || "Value" : null;
26493
26489
  const matchColorGroup = (v) => {
26494
26490
  const lv = v.trim().toLowerCase();
26495
- if (lv === "none") return null;
26491
+ if (lv === "" || lv === "none") return null;
26496
26492
  const tg = parsed.tagGroups.find((g) => g.name.toLowerCase() === lv);
26497
26493
  if (tg) return tg.name;
26498
26494
  if (lv === VALUE_NAME?.toLowerCase()) return VALUE_NAME;
@@ -26833,6 +26829,22 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26833
26829
  const tooltipText = fullText.length > 200 ? fullText.slice(0, 199) + "\u2026" : fullText;
26834
26830
  nodeG.append("title").text(tooltipText);
26835
26831
  }
26832
+ } else if (parsed.showValues && node.value !== void 0) {
26833
+ const valueLabel = parsed.boxMetric ? `${parsed.boxMetric}: ${node.value}` : String(node.value);
26834
+ const headerH = ln.height / 2;
26835
+ const sepY = -ln.height / 2 + headerH;
26836
+ const fitted = fitLabelToHeader(node.label, ln.width, 2);
26837
+ const labelLineH = fitted.fontSize * 1.3;
26838
+ const labelTotalH = fitted.lines.length * labelLineH;
26839
+ const headerCenterY = -ln.height / 2 + headerH / 2;
26840
+ for (let li = 0; li < fitted.lines.length; li++) {
26841
+ nodeG.append("text").attr("x", 0).attr(
26842
+ "y",
26843
+ headerCenterY - labelTotalH / 2 + labelLineH / 2 + li * labelLineH
26844
+ ).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]);
26845
+ }
26846
+ 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);
26847
+ 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);
26836
26848
  } else {
26837
26849
  const maxLabelLines = Math.max(
26838
26850
  2,
@@ -26845,21 +26857,16 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26845
26857
  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]);
26846
26858
  }
26847
26859
  }
26848
- if (parsed.showValues && node.value !== void 0) {
26860
+ if (parsed.showValues && node.value !== void 0 && desc && desc.length > 0 && !hideDescriptions) {
26849
26861
  const valueText = String(node.value);
26850
- const descShown = !!(desc && desc.length > 0 && !hideDescriptions);
26851
- if (descShown) {
26852
- const padX = 6;
26853
- const padY = 5;
26854
- const bw = valueText.length * VALUE_FONT_SIZE * CHAR_WIDTH_RATIO2 + 8;
26855
- const bh = VALUE_FONT_SIZE + 4;
26856
- const bx = ln.width / 2 - bw - 4;
26857
- const by = -ln.height / 2 + 4;
26858
- nodeG.append("rect").attr("x", bx).attr("y", by).attr("width", bw).attr("height", bh).attr("rx", 3).attr("fill", palette.bg).attr("opacity", 0.85);
26859
- nodeG.append("text").attr("class", "bl-node-value").attr("x", bx + bw - padX).attr("y", by + padY).attr("text-anchor", "end").attr("dominant-baseline", "central").attr("font-size", VALUE_FONT_SIZE).attr("font-weight", "600").attr("fill", palette.textMuted).text(valueText);
26860
- } else {
26861
- nodeG.append("text").attr("class", "bl-node-value").attr("x", 0).attr("y", ln.height / 2 - VALUE_FONT_SIZE).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", VALUE_FONT_SIZE).attr("font-weight", "600").attr("fill", colors.text).attr("opacity", 0.8).text(valueText);
26862
- }
26862
+ const padX = 6;
26863
+ const padY = 5;
26864
+ const bw = valueText.length * VALUE_FONT_SIZE * CHAR_WIDTH_RATIO2 + 8;
26865
+ const bh = VALUE_FONT_SIZE + 4;
26866
+ const bx = Math.max(-ln.width / 2 + 4, ln.width / 2 - bw - 4);
26867
+ const by = -ln.height / 2 + 4;
26868
+ 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);
26869
+ 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);
26863
26870
  }
26864
26871
  }
26865
26872
  const hasDescriptions = parsed.nodes.some(
@@ -26955,7 +26962,7 @@ var init_renderer6 = __esm({
26955
26962
  init_wrapped_desc();
26956
26963
  init_scaling();
26957
26964
  DIAGRAM_PADDING6 = 20;
26958
- NODE_FONT_SIZE = 13;
26965
+ NODE_FONT_SIZE = 11;
26959
26966
  MIN_NODE_FONT_SIZE = 9;
26960
26967
  EDGE_LABEL_FONT_SIZE4 = 11;
26961
26968
  EDGE_STROKE_WIDTH5 = 1.5;
@@ -46692,7 +46699,11 @@ function resolveMap(parsed, data) {
46692
46699
  if (bb && !isWholeSphere(bb)) containerBoxes.push(bb);
46693
46700
  }
46694
46701
  const containerUnion = unionExtent(containerBoxes, points);
46695
- if (containerUnion) extent2 = pad(containerUnion, PAD_FRACTION);
46702
+ if (containerUnion)
46703
+ extent2 = pad(
46704
+ clampContainerToCluster(containerUnion, points),
46705
+ PAD_FRACTION
46706
+ );
46696
46707
  }
46697
46708
  if (isPoiOnly) {
46698
46709
  const cx = (extent2[0][0] + extent2[1][0]) / 2;
@@ -46773,6 +46784,22 @@ function mostCommonCountry(regions, poiCountries) {
46773
46784
  }
46774
46785
  return best;
46775
46786
  }
46787
+ function clampContainerToCluster(container, points) {
46788
+ const poi = unionExtent([], points);
46789
+ if (!poi) return container;
46790
+ let [[west, south], [east, north]] = container;
46791
+ const [[pWest, pSouth], [pEast, pNorth]] = poi;
46792
+ south = Math.max(south, pSouth - CONTAINER_OVERSHOOT_DEG);
46793
+ north = Math.min(north, pNorth + CONTAINER_OVERSHOOT_DEG);
46794
+ if (east <= 180 && pEast <= 180) {
46795
+ west = Math.max(west, pWest - CONTAINER_OVERSHOOT_DEG);
46796
+ east = Math.min(east, pEast + CONTAINER_OVERSHOOT_DEG);
46797
+ }
46798
+ return [
46799
+ [west, south],
46800
+ [east, north]
46801
+ ];
46802
+ }
46776
46803
  function pad(e, frac) {
46777
46804
  const dLon = (e[1][0] - e[0][0]) * frac || 1;
46778
46805
  const dLat = (e[1][1] - e[0][1]) * frac || 1;
@@ -46785,7 +46812,7 @@ function firstError(diags) {
46785
46812
  const e = diags.find((d) => d.severity === "error");
46786
46813
  return e ? formatDgmoError(e) : null;
46787
46814
  }
46788
- 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;
46815
+ 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;
46789
46816
  var init_resolver2 = __esm({
46790
46817
  "src/map/resolver.ts"() {
46791
46818
  "use strict";
@@ -46798,6 +46825,7 @@ var init_resolver2 = __esm({
46798
46825
  WORLD_LAT_SOUTH = -58;
46799
46826
  WORLD_LAT_NORTH = 78;
46800
46827
  POI_ZOOM_FLOOR_DEG = 7;
46828
+ CONTAINER_OVERSHOOT_DEG = 8;
46801
46829
  US_NATIONAL_LON_SPAN = 48;
46802
46830
  REGION_ALIASES = {
46803
46831
  // Common everyday names → the Natural-Earth display name actually shipped.
@@ -46876,6 +46904,55 @@ var init_resolver2 = __esm({
46876
46904
  }
46877
46905
  });
46878
46906
 
46907
+ // src/map/legend-band.ts
46908
+ function mapLegendGroups(legend) {
46909
+ const ramp = legend.ramp;
46910
+ const scoreGroup = ramp ? {
46911
+ name: ramp.metric?.trim() || "Value",
46912
+ entries: [],
46913
+ gradient: {
46914
+ min: ramp.min,
46915
+ max: ramp.max,
46916
+ hue: ramp.hue,
46917
+ base: ramp.base
46918
+ }
46919
+ } : null;
46920
+ const tagGroups = legend.tagGroups.filter((g) => g.entries.length > 0).map((g) => ({ name: g.name, entries: [...g.entries] }));
46921
+ return [...scoreGroup ? [scoreGroup] : [], ...tagGroups];
46922
+ }
46923
+ function mapLegendConfig(groups, mode) {
46924
+ return {
46925
+ groups,
46926
+ position: { placement: "top-center", titleRelation: "below-title" },
46927
+ mode,
46928
+ showEmptyGroups: false,
46929
+ showInactivePills: true
46930
+ };
46931
+ }
46932
+ function mapLegendTop(hasTitle, hasSubtitle) {
46933
+ return (hasTitle ? TITLE_Y + TITLE_FONT_SIZE : 0) + (hasSubtitle ? TITLE_FONT_SIZE : 0) + LEGEND_TOP_GAP2;
46934
+ }
46935
+ function mapLegendBand(legend, opts) {
46936
+ if (!legend) return 0;
46937
+ const groups = mapLegendGroups(legend);
46938
+ if (groups.length === 0) return 0;
46939
+ const config = mapLegendConfig(groups, opts.mode);
46940
+ const state = { activeGroup: legend.activeGroup };
46941
+ const { height } = computeLegendLayout(config, state, opts.width);
46942
+ if (height <= 0) return 0;
46943
+ return mapLegendTop(opts.hasTitle, opts.hasSubtitle) + height + LEGEND_BOTTOM_GAP2;
46944
+ }
46945
+ var LEGEND_TOP_GAP2, LEGEND_BOTTOM_GAP2;
46946
+ var init_legend_band = __esm({
46947
+ "src/map/legend-band.ts"() {
46948
+ "use strict";
46949
+ init_legend_layout();
46950
+ init_title_constants();
46951
+ LEGEND_TOP_GAP2 = 8;
46952
+ LEGEND_BOTTOM_GAP2 = 10;
46953
+ }
46954
+ });
46955
+
46879
46956
  // src/map/colorize.ts
46880
46957
  function assignColors(isos, adjacency) {
46881
46958
  const sorted = [...isos].sort();
@@ -47456,12 +47533,43 @@ function layoutMap(resolved, data, size, opts) {
47456
47533
  return tagFill(r.tags, activeGroup) ?? neutralFill;
47457
47534
  };
47458
47535
  const regionById = new Map(resolved.regions.map((r) => [r.iso, r]));
47536
+ let legend = null;
47537
+ if (!resolved.directives.noLegend) {
47538
+ const legendTagGroups = resolved.tagGroups.map((g) => ({
47539
+ name: g.name,
47540
+ entries: g.entries.map((e) => ({ value: e.value, color: e.color }))
47541
+ }));
47542
+ if (legendTagGroups.length > 0 || hasRamp) {
47543
+ legend = {
47544
+ tagGroups: legendTagGroups,
47545
+ activeGroup,
47546
+ ...hasRamp && {
47547
+ ramp: {
47548
+ ...resolved.directives.regionMetric !== void 0 && {
47549
+ metric: resolved.directives.regionMetric
47550
+ },
47551
+ min: rampMin,
47552
+ max: rampMax,
47553
+ hue: rampHue,
47554
+ base: rampBase
47555
+ }
47556
+ }
47557
+ };
47558
+ }
47559
+ }
47459
47560
  const TITLE_GAP2 = 16;
47460
47561
  let topPad = FIT_PAD;
47461
47562
  if (resolved.title && resolved.pois.length > 0) {
47462
47563
  const bannerBottom = (resolved.subtitle ? TITLE_Y + TITLE_FONT_SIZE : TITLE_Y) + TITLE_FONT_SIZE / 2;
47463
47564
  topPad = Math.max(FIT_PAD, bannerBottom + TITLE_GAP2);
47464
47565
  }
47566
+ const legendBand = mapLegendBand(legend, {
47567
+ width,
47568
+ mode: opts.legendMode ?? "preview",
47569
+ hasTitle: Boolean(resolved.title),
47570
+ hasSubtitle: Boolean(resolved.subtitle)
47571
+ });
47572
+ if (legendBand > topPad) topPad = legendBand;
47465
47573
  const fitBox = [
47466
47574
  [FIT_PAD, topPad],
47467
47575
  [
@@ -47479,7 +47587,7 @@ function layoutMap(resolved, data, size, opts) {
47479
47587
  const by0 = cb[0][1];
47480
47588
  const cw = cb[1][0] - bx0;
47481
47589
  const ch = cb[1][1] - by0;
47482
- const topReserve = resolved.title && resolved.pois.length > 0 ? topPad : 0;
47590
+ const topReserve = resolved.title && resolved.pois.length > 0 || legendBand > 0 ? topPad : 0;
47483
47591
  const ox = 0;
47484
47592
  const oy = topReserve;
47485
47593
  const sx = cw > 0 ? width / cw : 1;
@@ -48543,30 +48651,6 @@ function layoutMap(resolved, data, size, opts) {
48543
48651
  });
48544
48652
  labels.push(...contextLabels);
48545
48653
  }
48546
- let legend = null;
48547
- if (!resolved.directives.noLegend) {
48548
- const tagGroups = resolved.tagGroups.map((g) => ({
48549
- name: g.name,
48550
- entries: g.entries.map((e) => ({ value: e.value, color: e.color }))
48551
- }));
48552
- if (tagGroups.length > 0 || hasRamp) {
48553
- legend = {
48554
- tagGroups,
48555
- activeGroup,
48556
- ...hasRamp && {
48557
- ramp: {
48558
- ...resolved.directives.regionMetric !== void 0 && {
48559
- metric: resolved.directives.regionMetric
48560
- },
48561
- min: rampMin,
48562
- max: rampMax,
48563
- hue: rampHue,
48564
- base: rampBase
48565
- }
48566
- }
48567
- };
48568
- }
48569
- }
48570
48654
  return {
48571
48655
  width,
48572
48656
  height,
@@ -48604,6 +48688,7 @@ var init_layout15 = __esm({
48604
48688
  init_label_layout();
48605
48689
  init_legend_constants();
48606
48690
  init_title_constants();
48691
+ init_legend_band();
48607
48692
  init_context_labels();
48608
48693
  FIT_PAD = 24;
48609
48694
  RAMP_FLOOR2 = 15;
@@ -48803,6 +48888,9 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
48803
48888
  // stretch-distorting. The in-app preview pane passes no exportDims → unset →
48804
48889
  // keeps the global stretch-fill.
48805
48890
  preferContain: exportDims?.preferContain ?? false,
48891
+ // Reserve the legend band for the mode actually drawn below (export shows
48892
+ // only the active group; preview keeps the inactive pills).
48893
+ legendMode: exportDims ? "export" : "preview",
48806
48894
  ...activeGroupOverride !== void 0 && {
48807
48895
  activeGroup: activeGroupOverride
48808
48896
  }
@@ -49094,30 +49182,12 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
49094
49182
  if (layout.legend) {
49095
49183
  const legendY = (layout.title ? TITLE_Y + TITLE_FONT_SIZE : 0) + (layout.subtitle ? TITLE_FONT_SIZE : 0) + 8;
49096
49184
  const legendG = svg.append("g").attr("class", "dgmo-map-legend").attr("transform", `translate(0, ${legendY})`);
49097
- const ramp = layout.legend.ramp;
49098
- const scoreGroup = ramp ? {
49099
- name: ramp.metric?.trim() || "Value",
49100
- entries: [],
49101
- gradient: {
49102
- min: ramp.min,
49103
- max: ramp.max,
49104
- hue: ramp.hue,
49105
- base: ramp.base
49106
- }
49107
- } : null;
49108
- const tagGroups = layout.legend.tagGroups.filter((g) => g.entries.length > 0).map((g) => ({ name: g.name, entries: [...g.entries] }));
49109
- const groups = [...scoreGroup ? [scoreGroup] : [], ...tagGroups];
49185
+ const groups = mapLegendGroups(layout.legend);
49110
49186
  if (groups.length > 0) {
49111
- const config = {
49187
+ const config = mapLegendConfig(
49112
49188
  groups,
49113
- position: { placement: "top-center", titleRelation: "below-title" },
49114
- mode: exportDims ? "export" : "preview",
49115
- showEmptyGroups: false,
49116
- // Keep inactive siblings visible as pills so the user can click to flip
49117
- // the active colouring dimension (preview only — export shows just the
49118
- // active group).
49119
- showInactivePills: true
49120
- };
49189
+ exportDims ? "export" : "preview"
49190
+ );
49121
49191
  const state = { activeGroup: layout.legend.activeGroup };
49122
49192
  renderLegendD3(legendG, config, state, palette, isDark, void 0, width);
49123
49193
  }
@@ -49162,6 +49232,7 @@ var init_renderer16 = __esm({
49162
49232
  init_title_constants();
49163
49233
  init_color_utils();
49164
49234
  init_legend_d3();
49235
+ init_legend_band();
49165
49236
  init_layout15();
49166
49237
  LABEL_FONT = 11;
49167
49238
  }
@@ -49182,9 +49253,10 @@ function mapContentAspect(resolved, data, ref = REF) {
49182
49253
  const aspect = w / h;
49183
49254
  return Number.isFinite(aspect) && aspect > 0 ? aspect : FALLBACK_ASPECT;
49184
49255
  }
49185
- function mapExportDimensions(resolved, data, baseWidth = 1200) {
49186
- const raw = mapContentAspect(resolved, data);
49187
- const clamped = Math.max(ASPECT_MIN, Math.min(ASPECT_MAX, raw));
49256
+ function mapExportDimensions(resolved, data, baseWidth = 1200, aspectOverride) {
49257
+ const useOverride = aspectOverride !== void 0 && Number.isFinite(aspectOverride) && aspectOverride > 0;
49258
+ const raw = useOverride ? aspectOverride : mapContentAspect(resolved, data);
49259
+ const clamped = useOverride ? raw : Math.max(ASPECT_MIN, Math.min(ASPECT_MAX, raw));
49188
49260
  const width = baseWidth;
49189
49261
  let height = Math.round(width / clamped);
49190
49262
  let chromeReserve = 0;
@@ -49197,7 +49269,7 @@ function mapExportDimensions(resolved, data, baseWidth = 1200) {
49197
49269
  height = Math.round(chromeReserve + MIN_MAP_BAND);
49198
49270
  floored = true;
49199
49271
  }
49200
- const preferContain = clamped !== raw || floored;
49272
+ const preferContain = useOverride ? floored : clamped !== raw || floored;
49201
49273
  return { width, height, preferContain };
49202
49274
  }
49203
49275
  var import_d3_geo3, FIT_PAD2, TITLE_GAP, ASPECT_MAX, ASPECT_MIN, MIN_MAP_BAND, FALLBACK_ASPECT, REF;
@@ -54904,7 +54976,6 @@ function renderTimelineTagLegendOverlay(container, parsed, palette, isDark, setu
54904
54976
  function renderTimelineHorizontalTimeSort(container, parsed, palette, isDark, setup, hovers, onClickItem, _exportDims, _swimlaneTagGroup, _activeTagGroup, _onTagStateChange, _viewMode) {
54905
54977
  const {
54906
54978
  width,
54907
- height,
54908
54979
  tooltip,
54909
54980
  solid,
54910
54981
  textColor,
@@ -54953,8 +55024,7 @@ function renderTimelineHorizontalTimeSort(container, parsed, palette, isDark, se
54953
55024
  const markerLabelY = markerReserve ? -(topScaleH + MARKER_ROW_H / 2) : 0;
54954
55025
  const eraLabelY = eraReserve ? -(topScaleH + markerReserve + ERA_ROW_H / 2) : 0;
54955
55026
  const innerWidth = width - margin.left - margin.right;
54956
- const availInnerHeight = height - margin.top - margin.bottom;
54957
- const rowH = Math.min(ctx.structural(28), availInnerHeight / sorted.length);
55027
+ const rowH = ctx.structural(28);
54958
55028
  const innerHeight = rowH * sorted.length;
54959
55029
  const usedHeight = margin.top + innerHeight + margin.bottom;
54960
55030
  const xScale = d3Scale2.scaleLinear().domain([minDate - datePadding, maxDate + datePadding]).range([0, innerWidth]);
@@ -57479,7 +57549,12 @@ async function renderForExport(content, theme, palette, viewState, options) {
57479
57549
  }
57480
57550
  }
57481
57551
  const mapResolved = resolveMap2(mapParsed, mapData);
57482
- const dims2 = mapExportDimensions2(mapResolved, mapData, EXPORT_WIDTH);
57552
+ const dims2 = mapExportDimensions2(
57553
+ mapResolved,
57554
+ mapData,
57555
+ EXPORT_WIDTH,
57556
+ options?.mapAspect
57557
+ );
57483
57558
  const container2 = createExportContainer(dims2.width, dims2.height);
57484
57559
  renderMapForExport2(
57485
57560
  container2,
@@ -59214,7 +59289,7 @@ pre.dgmo, code.language-dgmo, pre > code.language-dgmo,
59214
59289
 
59215
59290
  // src/auto/index.ts
59216
59291
  init_safe_href();
59217
- var VERSION = "0.23.0";
59292
+ var VERSION = "0.25.0";
59218
59293
  var DEFAULTS = {
59219
59294
  theme: "auto",
59220
59295
  palette: "nord",