@diagrammo/dgmo 0.23.0 → 0.24.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/advanced.js CHANGED
@@ -26649,7 +26649,7 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26649
26649
  const VALUE_NAME = hasRamp ? parsed.boxMetric?.trim() || "Value" : null;
26650
26650
  const matchColorGroup = (v) => {
26651
26651
  const lv = v.trim().toLowerCase();
26652
- if (lv === "none") return null;
26652
+ if (lv === "" || lv === "none") return null;
26653
26653
  const tg = parsed.tagGroups.find((g) => g.name.toLowerCase() === lv);
26654
26654
  if (tg) return tg.name;
26655
26655
  if (lv === VALUE_NAME?.toLowerCase()) return VALUE_NAME;
@@ -26990,6 +26990,22 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26990
26990
  const tooltipText = fullText.length > 200 ? fullText.slice(0, 199) + "\u2026" : fullText;
26991
26991
  nodeG.append("title").text(tooltipText);
26992
26992
  }
26993
+ } else if (parsed.showValues && node.value !== void 0) {
26994
+ const valueLabel = parsed.boxMetric ? `${parsed.boxMetric}: ${node.value}` : String(node.value);
26995
+ const headerH = ln.height / 2;
26996
+ const sepY = -ln.height / 2 + headerH;
26997
+ const fitted = fitLabelToHeader(node.label, ln.width, 2);
26998
+ const labelLineH = fitted.fontSize * 1.3;
26999
+ const labelTotalH = fitted.lines.length * labelLineH;
27000
+ const headerCenterY = -ln.height / 2 + headerH / 2;
27001
+ for (let li = 0; li < fitted.lines.length; li++) {
27002
+ nodeG.append("text").attr("x", 0).attr(
27003
+ "y",
27004
+ headerCenterY - labelTotalH / 2 + labelLineH / 2 + li * labelLineH
27005
+ ).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]);
27006
+ }
27007
+ 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);
27008
+ 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);
26993
27009
  } else {
26994
27010
  const maxLabelLines = Math.max(
26995
27011
  2,
@@ -27002,21 +27018,16 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
27002
27018
  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]);
27003
27019
  }
27004
27020
  }
27005
- if (parsed.showValues && node.value !== void 0) {
27021
+ if (parsed.showValues && node.value !== void 0 && desc && desc.length > 0 && !hideDescriptions) {
27006
27022
  const valueText = String(node.value);
27007
- const descShown = !!(desc && desc.length > 0 && !hideDescriptions);
27008
- if (descShown) {
27009
- const padX = 6;
27010
- const padY = 5;
27011
- const bw = valueText.length * VALUE_FONT_SIZE * CHAR_WIDTH_RATIO2 + 8;
27012
- const bh = VALUE_FONT_SIZE + 4;
27013
- const bx = ln.width / 2 - bw - 4;
27014
- const by = -ln.height / 2 + 4;
27015
- nodeG.append("rect").attr("x", bx).attr("y", by).attr("width", bw).attr("height", bh).attr("rx", 3).attr("fill", palette.bg).attr("opacity", 0.85);
27016
- nodeG.append("text").attr("class", "bl-node-value").attr("x", bx + bw - padX).attr("y", by + padY).attr("text-anchor", "end").attr("dominant-baseline", "central").attr("font-size", VALUE_FONT_SIZE).attr("font-weight", "600").attr("fill", palette.textMuted).text(valueText);
27017
- } else {
27018
- nodeG.append("text").attr("class", "bl-node-value").attr("x", 0).attr("y", ln.height / 2 - VALUE_FONT_SIZE).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", VALUE_FONT_SIZE).attr("font-weight", "600").attr("fill", colors.text).attr("opacity", 0.8).text(valueText);
27019
- }
27023
+ const padX = 6;
27024
+ const padY = 5;
27025
+ const bw = valueText.length * VALUE_FONT_SIZE * CHAR_WIDTH_RATIO2 + 8;
27026
+ const bh = VALUE_FONT_SIZE + 4;
27027
+ const bx = Math.max(-ln.width / 2 + 4, ln.width / 2 - bw - 4);
27028
+ const by = -ln.height / 2 + 4;
27029
+ 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);
27030
+ 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);
27020
27031
  }
27021
27032
  }
27022
27033
  const hasDescriptions = parsed.nodes.some(
@@ -27110,7 +27121,7 @@ var init_renderer6 = __esm({
27110
27121
  init_wrapped_desc();
27111
27122
  init_scaling();
27112
27123
  DIAGRAM_PADDING6 = 20;
27113
- NODE_FONT_SIZE = 13;
27124
+ NODE_FONT_SIZE = 11;
27114
27125
  MIN_NODE_FONT_SIZE = 9;
27115
27126
  EDGE_LABEL_FONT_SIZE4 = 11;
27116
27127
  EDGE_STROKE_WIDTH5 = 1.5;
@@ -47070,7 +47081,11 @@ function resolveMap(parsed, data) {
47070
47081
  if (bb && !isWholeSphere(bb)) containerBoxes.push(bb);
47071
47082
  }
47072
47083
  const containerUnion = unionExtent(containerBoxes, points);
47073
- if (containerUnion) extent2 = pad(containerUnion, PAD_FRACTION);
47084
+ if (containerUnion)
47085
+ extent2 = pad(
47086
+ clampContainerToCluster(containerUnion, points),
47087
+ PAD_FRACTION
47088
+ );
47074
47089
  }
47075
47090
  if (isPoiOnly) {
47076
47091
  const cx = (extent2[0][0] + extent2[1][0]) / 2;
@@ -47151,6 +47166,22 @@ function mostCommonCountry(regions, poiCountries) {
47151
47166
  }
47152
47167
  return best;
47153
47168
  }
47169
+ function clampContainerToCluster(container, points) {
47170
+ const poi = unionExtent([], points);
47171
+ if (!poi) return container;
47172
+ let [[west, south], [east, north]] = container;
47173
+ const [[pWest, pSouth], [pEast, pNorth]] = poi;
47174
+ south = Math.max(south, pSouth - CONTAINER_OVERSHOOT_DEG);
47175
+ north = Math.min(north, pNorth + CONTAINER_OVERSHOOT_DEG);
47176
+ if (east <= 180 && pEast <= 180) {
47177
+ west = Math.max(west, pWest - CONTAINER_OVERSHOOT_DEG);
47178
+ east = Math.min(east, pEast + CONTAINER_OVERSHOOT_DEG);
47179
+ }
47180
+ return [
47181
+ [west, south],
47182
+ [east, north]
47183
+ ];
47184
+ }
47154
47185
  function pad(e, frac) {
47155
47186
  const dLon = (e[1][0] - e[0][0]) * frac || 1;
47156
47187
  const dLat = (e[1][1] - e[0][1]) * frac || 1;
@@ -47163,7 +47194,7 @@ function firstError(diags) {
47163
47194
  const e = diags.find((d) => d.severity === "error");
47164
47195
  return e ? formatDgmoError(e) : null;
47165
47196
  }
47166
- 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;
47197
+ 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;
47167
47198
  var init_resolver2 = __esm({
47168
47199
  "src/map/resolver.ts"() {
47169
47200
  "use strict";
@@ -47176,6 +47207,7 @@ var init_resolver2 = __esm({
47176
47207
  WORLD_LAT_SOUTH = -58;
47177
47208
  WORLD_LAT_NORTH = 78;
47178
47209
  POI_ZOOM_FLOOR_DEG = 7;
47210
+ CONTAINER_OVERSHOOT_DEG = 8;
47179
47211
  US_NATIONAL_LON_SPAN = 48;
47180
47212
  REGION_ALIASES = {
47181
47213
  // Common everyday names → the Natural-Earth display name actually shipped.
@@ -47254,6 +47286,55 @@ var init_resolver2 = __esm({
47254
47286
  }
47255
47287
  });
47256
47288
 
47289
+ // src/map/legend-band.ts
47290
+ function mapLegendGroups(legend) {
47291
+ const ramp = legend.ramp;
47292
+ const scoreGroup = ramp ? {
47293
+ name: ramp.metric?.trim() || "Value",
47294
+ entries: [],
47295
+ gradient: {
47296
+ min: ramp.min,
47297
+ max: ramp.max,
47298
+ hue: ramp.hue,
47299
+ base: ramp.base
47300
+ }
47301
+ } : null;
47302
+ const tagGroups = legend.tagGroups.filter((g) => g.entries.length > 0).map((g) => ({ name: g.name, entries: [...g.entries] }));
47303
+ return [...scoreGroup ? [scoreGroup] : [], ...tagGroups];
47304
+ }
47305
+ function mapLegendConfig(groups, mode) {
47306
+ return {
47307
+ groups,
47308
+ position: { placement: "top-center", titleRelation: "below-title" },
47309
+ mode,
47310
+ showEmptyGroups: false,
47311
+ showInactivePills: true
47312
+ };
47313
+ }
47314
+ function mapLegendTop(hasTitle, hasSubtitle) {
47315
+ return (hasTitle ? TITLE_Y + TITLE_FONT_SIZE : 0) + (hasSubtitle ? TITLE_FONT_SIZE : 0) + LEGEND_TOP_GAP2;
47316
+ }
47317
+ function mapLegendBand(legend, opts) {
47318
+ if (!legend) return 0;
47319
+ const groups = mapLegendGroups(legend);
47320
+ if (groups.length === 0) return 0;
47321
+ const config = mapLegendConfig(groups, opts.mode);
47322
+ const state = { activeGroup: legend.activeGroup };
47323
+ const { height } = computeLegendLayout(config, state, opts.width);
47324
+ if (height <= 0) return 0;
47325
+ return mapLegendTop(opts.hasTitle, opts.hasSubtitle) + height + LEGEND_BOTTOM_GAP2;
47326
+ }
47327
+ var LEGEND_TOP_GAP2, LEGEND_BOTTOM_GAP2;
47328
+ var init_legend_band = __esm({
47329
+ "src/map/legend-band.ts"() {
47330
+ "use strict";
47331
+ init_legend_layout();
47332
+ init_title_constants();
47333
+ LEGEND_TOP_GAP2 = 8;
47334
+ LEGEND_BOTTOM_GAP2 = 10;
47335
+ }
47336
+ });
47337
+
47257
47338
  // src/map/colorize.ts
47258
47339
  function assignColors(isos, adjacency) {
47259
47340
  const sorted = [...isos].sort();
@@ -47845,12 +47926,43 @@ function layoutMap(resolved, data, size, opts) {
47845
47926
  return tagFill(r.tags, activeGroup) ?? neutralFill;
47846
47927
  };
47847
47928
  const regionById = new Map(resolved.regions.map((r) => [r.iso, r]));
47929
+ let legend = null;
47930
+ if (!resolved.directives.noLegend) {
47931
+ const legendTagGroups = resolved.tagGroups.map((g) => ({
47932
+ name: g.name,
47933
+ entries: g.entries.map((e) => ({ value: e.value, color: e.color }))
47934
+ }));
47935
+ if (legendTagGroups.length > 0 || hasRamp) {
47936
+ legend = {
47937
+ tagGroups: legendTagGroups,
47938
+ activeGroup,
47939
+ ...hasRamp && {
47940
+ ramp: {
47941
+ ...resolved.directives.regionMetric !== void 0 && {
47942
+ metric: resolved.directives.regionMetric
47943
+ },
47944
+ min: rampMin,
47945
+ max: rampMax,
47946
+ hue: rampHue,
47947
+ base: rampBase
47948
+ }
47949
+ }
47950
+ };
47951
+ }
47952
+ }
47848
47953
  const TITLE_GAP2 = 16;
47849
47954
  let topPad = FIT_PAD;
47850
47955
  if (resolved.title && resolved.pois.length > 0) {
47851
47956
  const bannerBottom = (resolved.subtitle ? TITLE_Y + TITLE_FONT_SIZE : TITLE_Y) + TITLE_FONT_SIZE / 2;
47852
47957
  topPad = Math.max(FIT_PAD, bannerBottom + TITLE_GAP2);
47853
47958
  }
47959
+ const legendBand = mapLegendBand(legend, {
47960
+ width,
47961
+ mode: opts.legendMode ?? "preview",
47962
+ hasTitle: Boolean(resolved.title),
47963
+ hasSubtitle: Boolean(resolved.subtitle)
47964
+ });
47965
+ if (legendBand > topPad) topPad = legendBand;
47854
47966
  const fitBox = [
47855
47967
  [FIT_PAD, topPad],
47856
47968
  [
@@ -47868,7 +47980,7 @@ function layoutMap(resolved, data, size, opts) {
47868
47980
  const by0 = cb[0][1];
47869
47981
  const cw = cb[1][0] - bx0;
47870
47982
  const ch = cb[1][1] - by0;
47871
- const topReserve = resolved.title && resolved.pois.length > 0 ? topPad : 0;
47983
+ const topReserve = resolved.title && resolved.pois.length > 0 || legendBand > 0 ? topPad : 0;
47872
47984
  const ox = 0;
47873
47985
  const oy = topReserve;
47874
47986
  const sx = cw > 0 ? width / cw : 1;
@@ -48932,30 +49044,6 @@ function layoutMap(resolved, data, size, opts) {
48932
49044
  });
48933
49045
  labels.push(...contextLabels);
48934
49046
  }
48935
- let legend = null;
48936
- if (!resolved.directives.noLegend) {
48937
- const tagGroups = resolved.tagGroups.map((g) => ({
48938
- name: g.name,
48939
- entries: g.entries.map((e) => ({ value: e.value, color: e.color }))
48940
- }));
48941
- if (tagGroups.length > 0 || hasRamp) {
48942
- legend = {
48943
- tagGroups,
48944
- activeGroup,
48945
- ...hasRamp && {
48946
- ramp: {
48947
- ...resolved.directives.regionMetric !== void 0 && {
48948
- metric: resolved.directives.regionMetric
48949
- },
48950
- min: rampMin,
48951
- max: rampMax,
48952
- hue: rampHue,
48953
- base: rampBase
48954
- }
48955
- }
48956
- };
48957
- }
48958
- }
48959
49047
  return {
48960
49048
  width,
48961
49049
  height,
@@ -48991,6 +49079,7 @@ var init_layout15 = __esm({
48991
49079
  init_label_layout();
48992
49080
  init_legend_constants();
48993
49081
  init_title_constants();
49082
+ init_legend_band();
48994
49083
  init_context_labels();
48995
49084
  FIT_PAD = 24;
48996
49085
  RAMP_FLOOR2 = 15;
@@ -49191,6 +49280,9 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
49191
49280
  // stretch-distorting. The in-app preview pane passes no exportDims → unset →
49192
49281
  // keeps the global stretch-fill.
49193
49282
  preferContain: exportDims?.preferContain ?? false,
49283
+ // Reserve the legend band for the mode actually drawn below (export shows
49284
+ // only the active group; preview keeps the inactive pills).
49285
+ legendMode: exportDims ? "export" : "preview",
49194
49286
  ...activeGroupOverride !== void 0 && {
49195
49287
  activeGroup: activeGroupOverride
49196
49288
  }
@@ -49482,30 +49574,12 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
49482
49574
  if (layout.legend) {
49483
49575
  const legendY = (layout.title ? TITLE_Y + TITLE_FONT_SIZE : 0) + (layout.subtitle ? TITLE_FONT_SIZE : 0) + 8;
49484
49576
  const legendG = svg.append("g").attr("class", "dgmo-map-legend").attr("transform", `translate(0, ${legendY})`);
49485
- const ramp = layout.legend.ramp;
49486
- const scoreGroup = ramp ? {
49487
- name: ramp.metric?.trim() || "Value",
49488
- entries: [],
49489
- gradient: {
49490
- min: ramp.min,
49491
- max: ramp.max,
49492
- hue: ramp.hue,
49493
- base: ramp.base
49494
- }
49495
- } : null;
49496
- const tagGroups = layout.legend.tagGroups.filter((g) => g.entries.length > 0).map((g) => ({ name: g.name, entries: [...g.entries] }));
49497
- const groups = [...scoreGroup ? [scoreGroup] : [], ...tagGroups];
49577
+ const groups = mapLegendGroups(layout.legend);
49498
49578
  if (groups.length > 0) {
49499
- const config = {
49579
+ const config = mapLegendConfig(
49500
49580
  groups,
49501
- position: { placement: "top-center", titleRelation: "below-title" },
49502
- mode: exportDims ? "export" : "preview",
49503
- showEmptyGroups: false,
49504
- // Keep inactive siblings visible as pills so the user can click to flip
49505
- // the active colouring dimension (preview only — export shows just the
49506
- // active group).
49507
- showInactivePills: true
49508
- };
49581
+ exportDims ? "export" : "preview"
49582
+ );
49509
49583
  const state = { activeGroup: layout.legend.activeGroup };
49510
49584
  renderLegendD3(legendG, config, state, palette, isDark, void 0, width);
49511
49585
  }
@@ -49549,6 +49623,7 @@ var init_renderer16 = __esm({
49549
49623
  init_title_constants();
49550
49624
  init_color_utils();
49551
49625
  init_legend_d3();
49626
+ init_legend_band();
49552
49627
  init_layout15();
49553
49628
  LABEL_FONT = 11;
49554
49629
  }
@@ -49570,9 +49645,10 @@ function mapContentAspect(resolved, data, ref = REF) {
49570
49645
  const aspect = w / h;
49571
49646
  return Number.isFinite(aspect) && aspect > 0 ? aspect : FALLBACK_ASPECT;
49572
49647
  }
49573
- function mapExportDimensions(resolved, data, baseWidth = 1200) {
49574
- const raw = mapContentAspect(resolved, data);
49575
- const clamped = Math.max(ASPECT_MIN, Math.min(ASPECT_MAX, raw));
49648
+ function mapExportDimensions(resolved, data, baseWidth = 1200, aspectOverride) {
49649
+ const useOverride = aspectOverride !== void 0 && Number.isFinite(aspectOverride) && aspectOverride > 0;
49650
+ const raw = useOverride ? aspectOverride : mapContentAspect(resolved, data);
49651
+ const clamped = useOverride ? raw : Math.max(ASPECT_MIN, Math.min(ASPECT_MAX, raw));
49576
49652
  const width = baseWidth;
49577
49653
  let height = Math.round(width / clamped);
49578
49654
  let chromeReserve = 0;
@@ -49585,7 +49661,7 @@ function mapExportDimensions(resolved, data, baseWidth = 1200) {
49585
49661
  height = Math.round(chromeReserve + MIN_MAP_BAND);
49586
49662
  floored = true;
49587
49663
  }
49588
- const preferContain = clamped !== raw || floored;
49664
+ const preferContain = useOverride ? floored : clamped !== raw || floored;
49589
49665
  return { width, height, preferContain };
49590
49666
  }
49591
49667
  var FIT_PAD2, TITLE_GAP, ASPECT_MAX, ASPECT_MIN, MIN_MAP_BAND, FALLBACK_ASPECT, REF;
@@ -55295,7 +55371,6 @@ function renderTimelineTagLegendOverlay(container, parsed, palette, isDark, setu
55295
55371
  function renderTimelineHorizontalTimeSort(container, parsed, palette, isDark, setup, hovers, onClickItem, _exportDims, _swimlaneTagGroup, _activeTagGroup, _onTagStateChange, _viewMode) {
55296
55372
  const {
55297
55373
  width,
55298
- height,
55299
55374
  tooltip,
55300
55375
  solid,
55301
55376
  textColor,
@@ -55344,8 +55419,7 @@ function renderTimelineHorizontalTimeSort(container, parsed, palette, isDark, se
55344
55419
  const markerLabelY = markerReserve ? -(topScaleH + MARKER_ROW_H / 2) : 0;
55345
55420
  const eraLabelY = eraReserve ? -(topScaleH + markerReserve + ERA_ROW_H / 2) : 0;
55346
55421
  const innerWidth = width - margin.left - margin.right;
55347
- const availInnerHeight = height - margin.top - margin.bottom;
55348
- const rowH = Math.min(ctx.structural(28), availInnerHeight / sorted.length);
55422
+ const rowH = ctx.structural(28);
55349
55423
  const innerHeight = rowH * sorted.length;
55350
55424
  const usedHeight = margin.top + innerHeight + margin.bottom;
55351
55425
  const xScale = d3Scale2.scaleLinear().domain([minDate - datePadding, maxDate + datePadding]).range([0, innerWidth]);
@@ -57926,7 +58000,12 @@ async function renderForExport(content, theme, palette, viewState, options) {
57926
58000
  }
57927
58001
  }
57928
58002
  const mapResolved = resolveMap2(mapParsed, mapData);
57929
- const dims2 = mapExportDimensions2(mapResolved, mapData, EXPORT_WIDTH);
58003
+ const dims2 = mapExportDimensions2(
58004
+ mapResolved,
58005
+ mapData,
58006
+ EXPORT_WIDTH,
58007
+ options?.mapAspect
58008
+ );
57930
58009
  const container2 = createExportContainer(dims2.width, dims2.height);
57931
58010
  renderMapForExport2(
57932
58011
  container2,