@diagrammo/dgmo 0.39.0 → 0.41.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
@@ -103,7 +103,7 @@ function vennAliasKeywordRemovedMessage(args) {
103
103
  return `Venn 'alias' keyword was removed. Use 'as' instead \u2014 '${args.name} as ${args.alias}'.`;
104
104
  }
105
105
  function pipeOperatorRemovedMessage() {
106
- return `'|' removed \u2014 'Node | c: red' \u2192 'Node c: red'`;
106
+ return `'|' is no longer metadata syntax \u2014 write metadata inline as 'key: value' (e.g. "Name color: red"), or remove the '|'.`;
107
107
  }
108
108
  function ganttBarePercentRemovedMessage(percent) {
109
109
  return `Bare-percent progress shorthand '| ${percent}' was removed. Use 'progress: ${percent.replace(/%$/, "")}' (integer 0\u2013100, no '%').`;
@@ -13678,7 +13678,8 @@ function buildScatterOption(parsed, palette, isDark, textColor, axisLineColor, g
13678
13678
  solid: parsed.solidFill === true
13679
13679
  }),
13680
13680
  borderColor: catColor,
13681
- borderWidth: CHART_BORDER_WIDTH
13681
+ borderWidth: CHART_BORDER_WIDTH,
13682
+ opacity: SCATTER_FILL_OPACITY
13682
13683
  },
13683
13684
  label: labelConfig,
13684
13685
  emphasis: emphasisConfig,
@@ -13705,6 +13706,7 @@ function buildScatterOption(parsed, palette, isDark, textColor, axisLineColor, g
13705
13706
  {
13706
13707
  type: "scatter",
13707
13708
  data,
13709
+ itemStyle: { opacity: SCATTER_FILL_OPACITY },
13708
13710
  label: labelConfig,
13709
13711
  emphasis: emphasisConfig,
13710
13712
  blur: blurConfig
@@ -15010,7 +15012,7 @@ async function renderExtendedChartForExport(content, theme, palette) {
15010
15012
  chart.dispose();
15011
15013
  }
15012
15014
  }
15013
- var EMPHASIS_SELF, EMPHASIS_SERIES, BLUR_DIM, EMPHASIS_LINE, CHART_BASE, CHART_BORDER_WIDTH, VALID_EXTENDED_TYPES, KNOWN_EXTENDED_OPTIONS, EXTENDED_CHART_DOORS, FIT_PADDING, OFFSCREEN, fitStackedLabelToSegment, ECHART_EXPORT_WIDTH, ECHART_EXPORT_HEIGHT, STANDARD_CHART_TYPES;
15015
+ var EMPHASIS_SELF, EMPHASIS_SERIES, BLUR_DIM, EMPHASIS_LINE, CHART_BASE, CHART_BORDER_WIDTH, SCATTER_FILL_OPACITY, VALID_EXTENDED_TYPES, KNOWN_EXTENDED_OPTIONS, EXTENDED_CHART_DOORS, FIT_PADDING, OFFSCREEN, fitStackedLabelToSegment, ECHART_EXPORT_WIDTH, ECHART_EXPORT_HEIGHT, STANDARD_CHART_TYPES;
15014
15016
  var init_echarts = __esm({
15015
15017
  "src/echarts.ts"() {
15016
15018
  "use strict";
@@ -15064,6 +15066,7 @@ var init_echarts = __esm({
15064
15066
  tooltip: { show: false }
15065
15067
  };
15066
15068
  CHART_BORDER_WIDTH = 2;
15069
+ SCATTER_FILL_OPACITY = 0.65;
15067
15070
  VALID_EXTENDED_TYPES = /* @__PURE__ */ new Set([
15068
15071
  "sankey",
15069
15072
  "chord",
@@ -15161,6 +15164,8 @@ function parseEventLine(content, palette) {
15161
15164
  let currentTagGroup = null;
15162
15165
  let currentEvent = null;
15163
15166
  let currentEra = null;
15167
+ let eraIndent = -1;
15168
+ let currentEventIndent = 0;
15164
15169
  const aliasMap = /* @__PURE__ */ new Map();
15165
15170
  for (let i = 0; i < lines.length; i++) {
15166
15171
  const line11 = lines[i];
@@ -15235,52 +15240,15 @@ function parseEventLine(content, palette) {
15235
15240
  });
15236
15241
  continue;
15237
15242
  }
15238
- if (indent > 0) {
15239
- if (!currentEvent) {
15240
- pushWarning(
15241
- lineNumber,
15242
- `Indented description "${trimmed}" has no event above it \u2014 add an event line first. (\xA728)`
15243
- );
15244
- continue;
15245
- }
15243
+ if (currentEvent && indent > currentEventIndent) {
15246
15244
  const descLine = trimmed.startsWith("- ") ? `\u2022 ${trimmed.substring(2)}` : trimmed;
15247
15245
  currentEvent.description.push(descLine);
15248
15246
  continue;
15249
15247
  }
15250
- currentTagGroup = null;
15251
- if (trimmed.toLowerCase() === "no-scale") {
15252
- options.scale = false;
15253
- continue;
15254
- }
15255
- const sideMatch = trimmed.match(SIDE_RE);
15256
- if (sideMatch) {
15257
- options.side = sideMatch[1].toLowerCase();
15258
- continue;
15259
- }
15260
- if (trimmed.toLowerCase() === "no-box") {
15261
- options.noBox = true;
15262
- continue;
15263
- }
15264
- if (trimmed.toLowerCase() === "no-legend") {
15265
- options.noLegend = true;
15266
- continue;
15267
- }
15268
- const dirMatch = trimmed.match(DIRECTION_RE);
15269
- if (dirMatch) {
15270
- const dir = dirMatch[1].toUpperCase();
15271
- if (dir !== "LR") {
15272
- pushWarning(
15273
- lineNumber,
15274
- `event-line is horizontal-only in v1; \`direction ${dir}\` (vertical orientation) is a fast-follow.`,
15275
- EVENT_LINE_DIAGNOSTIC_CODES.UNSUPPORTED
15276
- );
15277
- }
15278
- continue;
15279
- }
15280
- if (tryParseSharedOption(trimmed, sharedOptions)) continue;
15281
15248
  const eraMatch = trimmed.match(ERA_RE);
15282
15249
  if (eraMatch) {
15283
15250
  contentStarted = true;
15251
+ currentTagGroup = null;
15284
15252
  currentEvent = null;
15285
15253
  const name = eraMatch[1].trim();
15286
15254
  let rest = (eraMatch[2] ?? "").trim();
@@ -15304,16 +15272,52 @@ function parseEventLine(content, palette) {
15304
15272
  const era = { name, color, collapsed, lineNumber };
15305
15273
  result.eras.push(era);
15306
15274
  currentEra = name;
15275
+ eraIndent = indent;
15307
15276
  continue;
15308
15277
  }
15309
- if (SECTION_SEAM_RE.test(trimmed)) {
15310
- pushWarning(
15311
- lineNumber,
15312
- "Group events with `[Name]` era brackets (\xA728.6a), not `section`.",
15313
- EVENT_LINE_DIAGNOSTIC_CODES.UNSUPPORTED
15314
- );
15315
- continue;
15278
+ if (indent === 0) {
15279
+ currentTagGroup = null;
15280
+ if (trimmed.toLowerCase() === "no-scale") {
15281
+ options.scale = false;
15282
+ continue;
15283
+ }
15284
+ const sideMatch = trimmed.match(SIDE_RE);
15285
+ if (sideMatch) {
15286
+ options.side = sideMatch[1].toLowerCase();
15287
+ continue;
15288
+ }
15289
+ if (trimmed.toLowerCase() === "no-box") {
15290
+ options.noBox = true;
15291
+ continue;
15292
+ }
15293
+ if (trimmed.toLowerCase() === "no-legend") {
15294
+ options.noLegend = true;
15295
+ continue;
15296
+ }
15297
+ const dirMatch = trimmed.match(DIRECTION_RE);
15298
+ if (dirMatch) {
15299
+ const dir = dirMatch[1].toUpperCase();
15300
+ if (dir !== "LR") {
15301
+ pushWarning(
15302
+ lineNumber,
15303
+ `event-line is horizontal-only in v1; \`direction ${dir}\` (vertical orientation) is a fast-follow.`,
15304
+ EVENT_LINE_DIAGNOSTIC_CODES.UNSUPPORTED
15305
+ );
15306
+ }
15307
+ continue;
15308
+ }
15309
+ if (tryParseSharedOption(trimmed, sharedOptions)) continue;
15310
+ if (SECTION_SEAM_RE.test(trimmed)) {
15311
+ pushWarning(
15312
+ lineNumber,
15313
+ "Group events with `[Name]` era brackets (\xA728.6a), not `section`.",
15314
+ EVENT_LINE_DIAGNOSTIC_CODES.UNSUPPORTED
15315
+ );
15316
+ continue;
15317
+ }
15316
15318
  }
15319
+ const inEra = currentEra !== null && indent > eraIndent;
15320
+ if (!inEra) currentEra = null;
15317
15321
  contentStarted = true;
15318
15322
  const event = parseEventHeader(
15319
15323
  trimmed,
@@ -15325,6 +15329,7 @@ function parseEventLine(content, palette) {
15325
15329
  );
15326
15330
  result.events.push(event);
15327
15331
  currentEvent = event;
15332
+ currentEventIndent = indent;
15328
15333
  }
15329
15334
  options.noTitle = sharedOptions["no-title"] === "on";
15330
15335
  finalizeAutoTagColors(result.tagGroups, palette);
@@ -15339,8 +15344,61 @@ function parseEventLine(content, palette) {
15339
15344
  EVENT_LINE_DIAGNOSTIC_CODES.NO_EVENTS
15340
15345
  );
15341
15346
  }
15347
+ if (options.scale) {
15348
+ validateEraDateOrder(result.events, result.eras, pushWarning);
15349
+ }
15342
15350
  return result;
15343
15351
  }
15352
+ function validateEraDateOrder(events, eras, pushWarning) {
15353
+ if (eras.length < 2) return;
15354
+ const order = new Map(eras.map((e, i) => [e.name, i]));
15355
+ const dated = events.filter(
15356
+ (e) => e.dateValue !== null && e.era !== null && order.has(e.era)
15357
+ );
15358
+ if (dated.length === 0) return;
15359
+ const perEra = /* @__PURE__ */ new Map();
15360
+ for (const ev of dated) {
15361
+ const idx = order.get(ev.era);
15362
+ const cur = perEra.get(idx);
15363
+ if (!cur) {
15364
+ perEra.set(idx, { min: ev, max: ev });
15365
+ } else {
15366
+ if (ev.dateValue < cur.min.dateValue) cur.min = ev;
15367
+ if (ev.dateValue > cur.max.dateValue) cur.max = ev;
15368
+ }
15369
+ }
15370
+ const n = eras.length;
15371
+ const prefixMax = new Array(n).fill(null);
15372
+ const suffixMin = new Array(n).fill(null);
15373
+ for (let k = 1; k < n; k++) {
15374
+ const prev = perEra.get(k - 1)?.max ?? null;
15375
+ const carried = prefixMax[k - 1] ?? null;
15376
+ prefixMax[k] = prev && (!carried || prev.dateValue > carried.dateValue) ? prev : carried;
15377
+ }
15378
+ for (let k = n - 2; k >= 0; k--) {
15379
+ const next = perEra.get(k + 1)?.min ?? null;
15380
+ const carried = suffixMin[k + 1] ?? null;
15381
+ suffixMin[k] = next && (!carried || next.dateValue < carried.dateValue) ? next : carried;
15382
+ }
15383
+ for (const ev of dated) {
15384
+ const k = order.get(ev.era);
15385
+ const ahead = suffixMin[k];
15386
+ const behind = prefixMax[k];
15387
+ if (ahead && ev.dateValue > ahead.dateValue) {
15388
+ pushWarning(
15389
+ ev.lineNumber,
15390
+ `"${ev.label}" (${ev.date}) is in era "${ev.era}" but dated after era "${ahead.era}" begins (${ahead.date}). event-line eras run left-to-right by date \u2014 fix the date or move it to the right era, or their brackets will overlap.`,
15391
+ EVENT_LINE_DIAGNOSTIC_CODES.ERA_DATE_ORDER
15392
+ );
15393
+ } else if (behind && ev.dateValue < behind.dateValue) {
15394
+ pushWarning(
15395
+ ev.lineNumber,
15396
+ `"${ev.label}" (${ev.date}) is in era "${ev.era}" but dated before era "${behind.era}" ends (${behind.date}). event-line eras run left-to-right by date \u2014 fix the date or move it to the right era, or their brackets will overlap.`,
15397
+ EVENT_LINE_DIAGNOSTIC_CODES.ERA_DATE_ORDER
15398
+ );
15399
+ }
15400
+ }
15401
+ }
15344
15402
  function parseEventHeader(trimmed, lineNumber, era, aliasMap, diagnostics, pushWarning) {
15345
15403
  let date = null;
15346
15404
  let dateValue = null;
@@ -15407,7 +15465,8 @@ var init_parser13 = __esm({
15407
15465
  EVENT_LINE_DIAGNOSTIC_CODES = {
15408
15466
  NO_EVENTS: "E_EVENT_LINE_NO_EVENTS",
15409
15467
  BAD_DATE: "E_EVENT_LINE_BAD_DATE",
15410
- UNSUPPORTED: "E_EVENT_LINE_UNSUPPORTED"
15468
+ UNSUPPORTED: "E_EVENT_LINE_UNSUPPORTED",
15469
+ ERA_DATE_ORDER: "E_EVENT_LINE_ERA_DATE_ORDER"
15411
15470
  };
15412
15471
  NON_ISO_DATE_RE = /^\d{1,4}[/.]\d/;
15413
15472
  SIDE_RE = /^side\s+(above|below|alternate)\b/i;
@@ -26344,9 +26403,6 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26344
26403
  const hasExpandedEra = parsed.events.some(
26345
26404
  (e) => e.era && eraByName.has(e.era) && !collapsedSet.has(e.era)
26346
26405
  );
26347
- const anyCollapsed = parsed.events.some(
26348
- (e) => e.era && collapsedSet.has(e.era)
26349
- );
26350
26406
  const sideOpt = parsed.options.side;
26351
26407
  const alternate = sideOpt === "alternate";
26352
26408
  const eraSide = alternate ? "below" : sideOpt === "above" ? "below" : "above";
@@ -26413,7 +26469,8 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26413
26469
  x: 0,
26414
26470
  side,
26415
26471
  lane: 0,
26416
- left: 0
26472
+ left: 0,
26473
+ spanHalf: 0
26417
26474
  };
26418
26475
  }
26419
26476
  const era = slot.era;
@@ -26426,19 +26483,19 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26426
26483
  palette.textOnFillLight,
26427
26484
  palette.textOnFillDark
26428
26485
  );
26429
- const overflow = slot.members.length > ERA_MEMBER_MAX;
26430
- const shown = overflow ? slot.members.slice(0, ERA_MEMBER_MAX - 1) : slot.members;
26431
- const memberStrs = shown.map(
26486
+ let spanLo = null;
26487
+ let spanHi = null;
26488
+ for (const m of slot.members) {
26489
+ if (m.dateValue === null) continue;
26490
+ if (spanLo === null || m.dateValue < spanLo) spanLo = m.dateValue;
26491
+ if (spanHi === null || m.dateValue > spanHi) spanHi = m.dateValue;
26492
+ }
26493
+ const repDateValue = spanLo !== null && spanHi !== null ? (spanLo + spanHi) / 2 : null;
26494
+ const memberStrs = slot.members.map(
26432
26495
  (m) => `\u2022 ${m.date ? `${m.date} ` : ""}${m.label}`
26433
26496
  );
26434
- const bulletColors = shown.map(eventColor);
26497
+ const bulletColors = slot.members.map(eventColor);
26435
26498
  const lines = wrapDescription(memberStrs, charsPerLine);
26436
- if (overflow) {
26437
- lines.push({
26438
- text: `+${slot.members.length - (ERA_MEMBER_MAX - 1)} more`,
26439
- kind: "plain"
26440
- });
26441
- }
26442
26499
  return {
26443
26500
  kind: "era",
26444
26501
  event: null,
@@ -26446,7 +26503,7 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26446
26503
  members: slot.members,
26447
26504
  label: era.name,
26448
26505
  date: null,
26449
- dateValue: null,
26506
+ dateValue: repDateValue,
26450
26507
  lineNumber: era.lineNumber,
26451
26508
  eraName: era.name,
26452
26509
  color: solid,
@@ -26458,87 +26515,189 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26458
26515
  x: 0,
26459
26516
  side,
26460
26517
  lane: 0,
26461
- left: 0
26518
+ left: 0,
26519
+ spanHalf: 0
26462
26520
  };
26463
26521
  });
26464
- const scaled = parsed.options.scale && !anyCollapsed && placed.every((p) => p.dateValue !== null);
26522
+ const scaled = parsed.options.scale && placed.every((p) => p.dateValue !== null);
26465
26523
  const innerW = width - H_MARGIN * 2;
26466
- if (scaled) {
26467
- const vals = placed.map((p) => p.dateValue);
26468
- const lo = Math.min(...vals);
26469
- const hi = Math.max(...vals);
26470
- placed.forEach((p) => {
26471
- p.x = H_MARGIN + (hi > lo ? (p.dateValue - lo) / (hi - lo) * innerW : innerW / 2);
26524
+ const MIN_DOT_GAP = 16;
26525
+ const STRETCH_CAP = 8;
26526
+ const COLLAPSE_W = 64;
26527
+ const SEG_GAP = 16;
26528
+ const segments = [];
26529
+ let run = [];
26530
+ const flushRun = () => {
26531
+ if (!run.length) return;
26532
+ const ds = run.map((e) => e.dateValue);
26533
+ segments.push({
26534
+ kind: "run",
26535
+ events: run,
26536
+ lo: Math.min(...ds),
26537
+ hi: Math.max(...ds)
26472
26538
  });
26473
- const MIN_DOT_GAP = 16;
26474
- let prevX = -Infinity;
26475
- let prevVal = null;
26476
- for (const p of [...placed].sort((a, b) => a.x - b.x)) {
26477
- if (p.dateValue === prevVal) {
26478
- p.x = prevX;
26479
- continue;
26539
+ run = [];
26540
+ };
26541
+ for (const p of placed) {
26542
+ if (p.kind === "era") {
26543
+ flushRun();
26544
+ segments.push({ kind: "capsule", p });
26545
+ } else {
26546
+ run.push(p);
26547
+ }
26548
+ }
26549
+ flushRun();
26550
+ const totalRunTime = segments.reduce(
26551
+ (s, seg) => s + (seg.kind === "run" ? seg.hi - seg.lo : 0),
26552
+ 0
26553
+ );
26554
+ const place = (stretch) => {
26555
+ if (scaled) {
26556
+ const pxPerUnit = totalRunTime > 0 ? innerW * stretch / totalRunTime : 0;
26557
+ let cursor = H_MARGIN;
26558
+ for (const seg of segments) {
26559
+ if (seg.kind === "capsule") {
26560
+ seg.p.x = cursor + COLLAPSE_W / 2;
26561
+ seg.p.spanHalf = COLLAPSE_W / 2;
26562
+ cursor += COLLAPSE_W + SEG_GAP;
26563
+ } else {
26564
+ for (const e of seg.events) {
26565
+ e.x = cursor + (e.dateValue - seg.lo) * pxPerUnit;
26566
+ e.spanHalf = 0;
26567
+ }
26568
+ cursor += (seg.hi - seg.lo) * pxPerUnit + SEG_GAP;
26569
+ }
26570
+ }
26571
+ let prevX = -Infinity;
26572
+ let prevVal = null;
26573
+ for (const p of [...placed].sort((a, b) => a.x - b.x)) {
26574
+ if (p.kind === "era") {
26575
+ prevX = p.x + p.spanHalf;
26576
+ prevVal = null;
26577
+ continue;
26578
+ }
26579
+ if (p.dateValue === prevVal) {
26580
+ p.x = prevX;
26581
+ continue;
26582
+ }
26583
+ if (p.x < prevX + MIN_DOT_GAP) p.x = prevX + MIN_DOT_GAP;
26584
+ prevX = p.x;
26585
+ prevVal = p.dateValue;
26586
+ }
26587
+ } else {
26588
+ placed.forEach((p) => {
26589
+ p.spanHalf = 0;
26590
+ });
26591
+ const n = placed.length;
26592
+ const spacing = n > 1 ? Math.max(MIN_SPACING, innerW / (n - 1)) : 0;
26593
+ placed.forEach((p, i) => {
26594
+ p.x = H_MARGIN + i * spacing;
26595
+ });
26596
+ }
26597
+ for (const side of ["above", "below"]) {
26598
+ const arr = placed.filter((p) => p.side === side).sort((a, b) => a.x - b.x);
26599
+ const laneRight = [];
26600
+ for (let i = 0; i < arr.length; i++) {
26601
+ const p = arr[i];
26602
+ const next = arr[i + 1];
26603
+ const crowdedRight = !!next && next.x - p.x < CARD_W + FAN_GAP;
26604
+ const preferred = crowdedRight ? p.x - CARD_W + CARD_INSET : p.x - CARD_W / 2;
26605
+ const maxLeft = p.x - CARD_INSET;
26606
+ const minLeft = p.x - CARD_W + CARD_INSET;
26607
+ let lane = laneRight.length;
26608
+ let left = Math.max(preferred, minLeft);
26609
+ for (let l = 0; l < laneRight.length; l++) {
26610
+ const want = Math.max(preferred, laneRight[l] + FAN_GAP, minLeft);
26611
+ if (want <= maxLeft) {
26612
+ lane = l;
26613
+ left = want;
26614
+ break;
26615
+ }
26616
+ }
26617
+ p.lane = lane;
26618
+ p.left = Math.max(6, left);
26619
+ laneRight[lane] = p.left + CARD_W;
26480
26620
  }
26481
- if (p.x < prevX + MIN_DOT_GAP) p.x = prevX + MIN_DOT_GAP;
26482
- prevX = p.x;
26483
- prevVal = p.dateValue;
26484
26621
  }
26622
+ };
26623
+ const derive = () => {
26624
+ const contentW2 = Math.max(
26625
+ width,
26626
+ Math.max(...placed.map((p) => p.x + p.spanHalf)) + H_MARGIN,
26627
+ Math.max(...placed.map((p) => p.left + CARD_W)) + 6
26628
+ );
26629
+ const laneOffset = { above: [], below: [] };
26630
+ for (const side of ["above", "below"]) {
26631
+ const laneH = [];
26632
+ for (const p of placed) {
26633
+ if (p.side !== side) continue;
26634
+ laneH[p.lane] = Math.max(laneH[p.lane] ?? 0, p.cardH);
26635
+ }
26636
+ const off = [];
26637
+ let acc = 0;
26638
+ for (let l = 0; l < laneH.length; l++) {
26639
+ off[l] = acc;
26640
+ acc += (laneH[l] ?? 0) + LANE_GAP;
26641
+ }
26642
+ laneOffset[side] = off;
26643
+ }
26644
+ const laneNear2 = (p) => (p.side === "above" ? LEADER_ABOVE : LEADER_BELOW) + (laneOffset[p.side][p.lane] ?? 0);
26645
+ const ext = (side) => Math.max(
26646
+ 0,
26647
+ ...placed.filter((p) => p.side === side).map((p) => laneNear2(p) + p.cardH)
26648
+ );
26649
+ const dateAbove = placed.some((p) => p.side === "below" && p.date);
26650
+ const dateBelow = placed.some((p) => p.side === "above" && p.date);
26651
+ const contentAbove = Math.max(
26652
+ ext("above"),
26653
+ dateAbove ? DATE_OFFSET + 10 : 0
26654
+ );
26655
+ const contentBelow = Math.max(
26656
+ ext("below"),
26657
+ dateBelow ? DATE_OFFSET + 10 : 0
26658
+ );
26659
+ const aboveExt = contentAbove + (hasExpandedEra && eraSide === "above" ? ERA_BLOCK : 0);
26660
+ const belowExt = contentBelow + (hasExpandedEra && eraSide === "below" ? ERA_BLOCK : 0);
26661
+ const TOP_PAD = 14;
26662
+ const BOT_PAD = 14;
26663
+ const spineY2 = topUsed + TOP_PAD + aboveExt;
26664
+ const eraBaseY2 = eraSide === "above" ? spineY2 - (contentAbove + 14) : spineY2 + (contentBelow + 14);
26665
+ const contentH = spineY2 + belowExt + BOT_PAD;
26666
+ const totalH2 = Math.max(heightHint, contentH);
26667
+ return { contentW: contentW2, contentH, totalH: totalH2, spineY: spineY2, eraBaseY: eraBaseY2, laneNear: laneNear2 };
26668
+ };
26669
+ const envStretch = Number(
26670
+ globalThis.process?.env?.["DGMO_EVT_STRETCH"]
26671
+ );
26672
+ let layout;
26673
+ if (Number.isFinite(envStretch) && envStretch > 0) {
26674
+ place(envStretch);
26675
+ layout = derive();
26485
26676
  } else {
26486
- const n = placed.length;
26487
- const spacing = n > 1 ? Math.max(MIN_SPACING, innerW / (n - 1)) : 0;
26488
- placed.forEach((p, i) => {
26489
- p.x = H_MARGIN + i * spacing;
26490
- });
26491
- }
26492
- for (const side of ["above", "below"]) {
26493
- const arr = placed.filter((p) => p.side === side).sort((a, b) => a.x - b.x);
26494
- const laneRight = [];
26495
- for (let i = 0; i < arr.length; i++) {
26496
- const p = arr[i];
26497
- const next = arr[i + 1];
26498
- const crowdedRight = !!next && next.x - p.x < CARD_W + FAN_GAP;
26499
- const preferred = crowdedRight ? p.x - CARD_W + CARD_INSET : p.x - CARD_W / 2;
26500
- const maxLeft = p.x - CARD_INSET;
26501
- const minLeft = p.x - CARD_W + CARD_INSET;
26502
- let lane = laneRight.length;
26503
- let left = Math.max(preferred, minLeft);
26504
- for (let l = 0; l < laneRight.length; l++) {
26505
- const want = Math.max(preferred, laneRight[l] + FAN_GAP, minLeft);
26506
- if (want <= maxLeft) {
26507
- lane = l;
26508
- left = want;
26509
- break;
26677
+ place(1);
26678
+ layout = derive();
26679
+ const panelAspect = width / heightHint;
26680
+ const aspectOf = (l) => l.contentW / l.contentH;
26681
+ if (scaled && layout.contentH > heightHint && aspectOf(layout) < panelAspect) {
26682
+ place(STRETCH_CAP);
26683
+ const capped = derive();
26684
+ if (aspectOf(capped) <= panelAspect) {
26685
+ layout = capped;
26686
+ } else {
26687
+ let loF = 1;
26688
+ let hiF = STRETCH_CAP;
26689
+ for (let it = 0; it < 8; it++) {
26690
+ const mid = (loF + hiF) / 2;
26691
+ place(mid);
26692
+ if (aspectOf(derive()) >= panelAspect) hiF = mid;
26693
+ else loF = mid;
26510
26694
  }
26695
+ place(hiF);
26696
+ layout = derive();
26511
26697
  }
26512
- p.lane = lane;
26513
- p.left = Math.max(6, left);
26514
- laneRight[lane] = p.left + CARD_W;
26515
26698
  }
26516
26699
  }
26517
- const contentW = Math.max(
26518
- width,
26519
- Math.max(...placed.map((p) => p.x)) + H_MARGIN,
26520
- Math.max(...placed.map((p) => p.left + CARD_W)) + 6
26521
- );
26522
- const rowGap = (side) => Math.max(0, ...placed.filter((p) => p.side === side).map((p) => p.cardH)) + LANE_GAP;
26523
- const rowGapA = rowGap("above");
26524
- const rowGapB = rowGap("below");
26525
- const ext = (side) => Math.max(
26526
- 0,
26527
- ...placed.filter((p) => p.side === side).map(
26528
- (p) => (side === "above" ? LEADER_ABOVE : LEADER_BELOW) + p.lane * (side === "above" ? rowGapA : rowGapB) + p.cardH
26529
- )
26530
- );
26531
- const dateAbove = placed.some((p) => p.side === "below" && p.date);
26532
- const dateBelow = placed.some((p) => p.side === "above" && p.date);
26533
- const contentAbove = Math.max(ext("above"), dateAbove ? DATE_OFFSET + 10 : 0);
26534
- const contentBelow = Math.max(ext("below"), dateBelow ? DATE_OFFSET + 10 : 0);
26535
- const aboveExt = contentAbove + (hasExpandedEra && eraSide === "above" ? ERA_BLOCK : 0);
26536
- const belowExt = contentBelow + (hasExpandedEra && eraSide === "below" ? ERA_BLOCK : 0);
26537
- const TOP_PAD = 14;
26538
- const BOT_PAD = 14;
26539
- const spineY = topUsed + TOP_PAD + aboveExt;
26540
- const eraBaseY = eraSide === "above" ? spineY - (contentAbove + 14) : spineY + (contentBelow + 14);
26541
- const totalH = Math.max(heightHint, spineY + belowExt + BOT_PAD);
26700
+ const { contentW, totalH, spineY, eraBaseY, laneNear } = layout;
26542
26701
  const svg = d3Selection6.select(container).append("svg").attr("width", contentW).attr("height", totalH).attr("viewBox", `0 0 ${contentW} ${totalH}`).attr("preserveAspectRatio", "xMidYMin meet").attr("xmlns", "http://www.w3.org/2000/svg").style("font-family", FONT_FAMILY);
26543
26702
  svg.append("rect").attr("width", contentW).attr("height", totalH).attr("fill", palette.bg);
26544
26703
  if (showTitle) {
@@ -26566,12 +26725,11 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26566
26725
  activeGroup
26567
26726
  });
26568
26727
  }
26569
- const xs = placed.map((p) => p.x);
26570
- const x0 = Math.min(...xs);
26571
- const x1 = Math.max(...xs);
26728
+ const x0 = Math.min(...placed.map((p) => p.x - p.spanHalf));
26729
+ const x1 = Math.max(...placed.map((p) => p.x + p.spanHalf));
26572
26730
  svg.append("line").attr("x1", x0 - 20).attr("y1", spineY).attr("x2", x1 + 20).attr("y2", spineY).attr("stroke", palette.text).attr("stroke-width", 2.5).attr("stroke-linecap", "round");
26573
26731
  const geo = placed.map((p) => {
26574
- const near = p.side === "above" ? spineY - LEADER_ABOVE - p.lane * rowGapA : spineY + LEADER_BELOW + p.lane * rowGapB;
26732
+ const near = p.side === "above" ? spineY - laneNear(p) : spineY + laneNear(p);
26575
26733
  const top = p.side === "above" ? near - p.cardH : near;
26576
26734
  return { p, near, top };
26577
26735
  });
@@ -26683,7 +26841,7 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26683
26841
  for (const p of placed) {
26684
26842
  if (p.kind === "era") {
26685
26843
  const col = p.color;
26686
- const half = Math.min(46, 16 + (p.members.length - 1) * 9);
26844
+ const half = Math.max(16, p.spanHalf);
26687
26845
  const spineLeft = x0 - 20;
26688
26846
  const spineRight = x1 + 20;
26689
26847
  const bx0 = Math.max(spineLeft, p.x - half);
@@ -26694,6 +26852,11 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26694
26852
  "d",
26695
26853
  `M${bx0},${spineY} L${bx0},${barY} L${bx1},${barY} L${bx1},${spineY}`
26696
26854
  ).attr("fill", "none").attr("stroke", col).attr("stroke-width", 1.5).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
26855
+ const breakCx = (bx0 + bx1) / 2;
26856
+ eg.append("line").attr("x1", breakCx - BREAK_GAP / 2).attr("y1", spineY).attr("x2", breakCx + BREAK_GAP / 2).attr("y2", spineY).attr("stroke", palette.bg).attr("stroke-width", 4).attr("stroke-linecap", "butt");
26857
+ for (const d of axisBreakPaths(breakCx, spineY)) {
26858
+ eg.append("path").attr("d", d).attr("fill", "none").attr("stroke", col).attr("stroke-width", 1.25).attr("stroke-linecap", "round").attr("stroke-linejoin", "round").attr("stroke-opacity", 0.85);
26859
+ }
26697
26860
  if (onClickItem) {
26698
26861
  const ln = p.lineNumber;
26699
26862
  eg.style("cursor", "pointer").on("click", () => onClickItem(ln));
@@ -26727,13 +26890,23 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26727
26890
  }
26728
26891
  const clampX = (v) => Math.max(4, Math.min(contentW - 4, v));
26729
26892
  const cap = eraSide === "above" ? 1 : -1;
26730
- for (const r of runs) {
26731
- if (r.collapsed) continue;
26893
+ const drawn = runs.filter((r) => !r.collapsed).map((r) => ({
26894
+ run: r,
26895
+ x0: clampX(r.firstX - ERA_BRACKET_PAD),
26896
+ x1: clampX(r.lastX + ERA_BRACKET_PAD)
26897
+ })).sort((a, b) => a.run.firstX - b.run.firstX);
26898
+ for (let i = 1; i < drawn.length; i++) {
26899
+ const prev = drawn[i - 1];
26900
+ const cur = drawn[i];
26901
+ if (prev.x1 <= cur.x0) continue;
26902
+ const mid = (prev.run.lastX + cur.run.firstX) / 2;
26903
+ prev.x1 = Math.max(prev.run.lastX, Math.min(prev.x1, mid - ERA_SEAM_GAP));
26904
+ cur.x0 = Math.min(cur.run.firstX, Math.max(cur.x0, mid + ERA_SEAM_GAP));
26905
+ }
26906
+ for (const { run: r, x0: x02, x1: x12 } of drawn) {
26732
26907
  const neutral = !r.era.color;
26733
26908
  const col = neutral ? palette.text : resolveColor(r.era.color, palette) ?? palette.text;
26734
26909
  const op = neutral ? 0.5 : 0.85;
26735
- const x02 = clampX(r.firstX - 14);
26736
- const x12 = clampX(r.lastX + 14);
26737
26910
  const y = eraBaseY;
26738
26911
  const eg = svg.append("g").attr("data-era", r.era.name).attr("data-era-collapsed", String(r.collapsed)).attr("data-line-number", r.era.lineNumber);
26739
26912
  eg.append("path").attr(
@@ -26759,6 +26932,22 @@ function renderEventLineForExport(container, parsed, palette, isDark, exportDims
26759
26932
  tagOverride
26760
26933
  );
26761
26934
  }
26935
+ function axisBreakPaths(cx, cy) {
26936
+ const HUMPS = 2;
26937
+ const STEPS = 14;
26938
+ const wave = (x0) => {
26939
+ let d = "";
26940
+ for (let i = 0; i <= STEPS; i++) {
26941
+ const t = i / STEPS;
26942
+ const y = cy - BREAK_HALF_H + t * 2 * BREAK_HALF_H;
26943
+ const x = x0 + BREAK_AMP * Math.sin(t * Math.PI * HUMPS);
26944
+ d += `${i === 0 ? "M" : "L"}${x.toFixed(2)},${y.toFixed(2)}`;
26945
+ if (i < STEPS) d += " ";
26946
+ }
26947
+ return d;
26948
+ };
26949
+ return [wave(cx - BREAK_GAP / 2), wave(cx + BREAK_GAP / 2)];
26950
+ }
26762
26951
  function wrapDescription(lines, charsPerLine) {
26763
26952
  const out = [];
26764
26953
  for (const line11 of lines) {
@@ -26788,7 +26977,7 @@ function renderBody(cardG, lines, bodyColor, palette, startBaseline = CARD_BODY_
26788
26977
  y += DESC_LINE_H;
26789
26978
  }
26790
26979
  }
26791
- var CARD_W, H_MARGIN, TITLE_AREA, LEGEND_BAND, LEADER_ABOVE, LEADER_BELOW, DESC_FONT, DESC_LINE_H, CARD_PAD, CARD_BODY_TOP, DOT_R, MIN_SPACING, LANE_GAP, NEUTRAL_TAG, CARD_INSET, FAN_GAP, DATE_OFFSET, ERA_BLOCK, ERA_BRACKET_CAP, ERA_LEG, ERA_LABEL_FONT, ERA_MEMBER_MAX;
26980
+ var CARD_W, H_MARGIN, TITLE_AREA, LEGEND_BAND, LEADER_ABOVE, LEADER_BELOW, DESC_FONT, DESC_LINE_H, CARD_PAD, CARD_BODY_TOP, DOT_R, MIN_SPACING, LANE_GAP, NEUTRAL_TAG, CARD_INSET, FAN_GAP, DATE_OFFSET, ERA_BLOCK, ERA_BRACKET_CAP, ERA_BRACKET_PAD, ERA_SEAM_GAP, BREAK_HALF_H, BREAK_AMP, BREAK_GAP, ERA_LEG, ERA_LABEL_FONT;
26792
26981
  var init_renderer = __esm({
26793
26982
  "src/event-line/renderer.ts"() {
26794
26983
  "use strict";
@@ -26822,9 +27011,13 @@ var init_renderer = __esm({
26822
27011
  DATE_OFFSET = 28;
26823
27012
  ERA_BLOCK = 30;
26824
27013
  ERA_BRACKET_CAP = 8;
27014
+ ERA_BRACKET_PAD = 14;
27015
+ ERA_SEAM_GAP = 3;
27016
+ BREAK_HALF_H = 6;
27017
+ BREAK_AMP = 2.2;
27018
+ BREAK_GAP = 5;
26825
27019
  ERA_LEG = 13;
26826
27020
  ERA_LABEL_FONT = 11.5;
26827
- ERA_MEMBER_MAX = 6;
26828
27021
  }
26829
27022
  });
26830
27023
 
@@ -53297,6 +53490,7 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
53297
53490
  y,
53298
53491
  score,
53299
53492
  palette,
53493
+ isDark,
53300
53494
  SCORE_LABEL_R
53301
53495
  );
53302
53496
  if (!exportDims) {
@@ -53328,7 +53522,14 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
53328
53522
  const allSteps = parsed.phases.length > 0 ? parsed.phases.flatMap((p) => p.steps) : parsed.steps;
53329
53523
  for (const pt of layout.curvePoints) {
53330
53524
  const step = allSteps[pt.stepIndex];
53331
- const faceG = renderScoreFace(curveG, pt.x, pt.y, pt.score, palette);
53525
+ const faceG = renderScoreFace(
53526
+ curveG,
53527
+ pt.x,
53528
+ pt.y,
53529
+ pt.score,
53530
+ palette,
53531
+ isDark
53532
+ );
53332
53533
  addEmotionLabel(faceG, pt, palette);
53333
53534
  if (step) {
53334
53535
  faceG.attr("data-line-number", step.lineNumber);
@@ -53354,7 +53555,14 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
53354
53555
  const pt = layout.curvePoints[0];
53355
53556
  const allSteps = parsed.phases.length > 0 ? parsed.phases.flatMap((p) => p.steps) : parsed.steps;
53356
53557
  const step = allSteps[pt.stepIndex];
53357
- const faceG = renderScoreFace(curveG, pt.x, pt.y, pt.score, palette);
53558
+ const faceG = renderScoreFace(
53559
+ curveG,
53560
+ pt.x,
53561
+ pt.y,
53562
+ pt.score,
53563
+ palette,
53564
+ isDark
53565
+ );
53358
53566
  addEmotionLabel(faceG, pt, palette);
53359
53567
  if (step) {
53360
53568
  faceG.attr("data-line-number", step.lineNumber);
@@ -53451,6 +53659,7 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
53451
53659
  faceCy,
53452
53660
  step.score,
53453
53661
  palette,
53662
+ isDark,
53454
53663
  COLLAPSED_FACE_R
53455
53664
  );
53456
53665
  faceG.attr("data-line-number", step.lineNumber);
@@ -53554,7 +53763,7 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
53554
53763
  const fcy = parseFloat(el.attr("data-cy") ?? "0");
53555
53764
  el.attr(
53556
53765
  "transform",
53557
- `translate(${fcx},${fcy}) scale(1.5) translate(${-fcx},${-fcy})`
53766
+ `translate(${fcx},${fcy}) scale(${FACE_HOVER_SCALE}) translate(${-fcx},${-fcy})`
53558
53767
  );
53559
53768
  } else {
53560
53769
  el.attr("transform", null);
@@ -53600,12 +53809,12 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
53600
53809
  PADDING2,
53601
53810
  Math.min(fcx - bw / 2, layout.totalWidth - PADDING2 - bw)
53602
53811
  );
53603
- const by = Math.max(PADDING2, fcy - FACE_RADIUS - THOUGHT_GAP - bh);
53812
+ const by = Math.max(PADDING2, fcy - FACE_HOVER_R - THOUGHT_GAP - bh);
53604
53813
  const scoreColor = scoreToColor(score, palette);
53605
53814
  const tintedBg = mix(scoreColor, palette.surface, 20);
53606
53815
  const g = overlayG.append("g").attr("class", "journey-thought-hover");
53607
53816
  g.append("rect").attr("x", bx).attr("y", by).attr("width", bw).attr("height", bh).attr("rx", CARD_RADIUS).attr("fill", tintedBg).attr("stroke", scoreColor).attr("stroke-width", CARD_STROKE_WIDTH2);
53608
- g.append("line").attr("x1", fcx).attr("y1", by + bh).attr("x2", fcx).attr("y2", fcy - FACE_RADIUS - 1).attr("stroke", scoreColor).attr("stroke-width", CARD_STROKE_WIDTH2);
53817
+ g.append("line").attr("x1", fcx).attr("y1", by + bh).attr("x2", fcx).attr("y2", fcy - FACE_HOVER_R - 1).attr("stroke", scoreColor).attr("stroke-width", CARD_STROKE_WIDTH2);
53609
53818
  const centerX = bx + bw / 2;
53610
53819
  for (let i = 0; i < lines.length; i++) {
53611
53820
  g.append("text").attr("x", centerX).attr("y", by + THOUGHT_PAD_Y + (i + 1) * THOUGHT_LINE_H - 2).attr("text-anchor", "middle").attr("font-size", THOUGHT_FONT).attr("font-style", "italic").attr("fill", palette.textMuted).text(lines[i]);
@@ -53848,24 +54057,23 @@ function renderPersonaSilhouette(parent, cx, cy, color, palette, scale = 1) {
53848
54057
  `M ${cx} ${cy - 12 * s} C ${cx + 10 * s} ${cy - 12 * s}, ${cx + 9 * s} ${cy + 2 * s}, ${cx + 6 * s} ${cy + 6 * s} Q ${cx} ${cy + 9 * s}, ${cx - 6 * s} ${cy + 6 * s} C ${cx - 9 * s} ${cy + 2 * s}, ${cx - 10 * s} ${cy - 12 * s}, ${cx} ${cy - 12 * s} Z`
53849
54058
  ).attr("fill", fill2).attr("stroke", stroke2).attr("stroke-width", 1.2);
53850
54059
  }
53851
- function renderScoreFace(parent, cx, cy, score, palette, radius) {
54060
+ function renderScoreFace(parent, cx, cy, score, palette, isDark, radius) {
53852
54061
  const r = radius ?? FACE_RADIUS;
53853
54062
  const color = scoreToColor(score, palette);
53854
54063
  const g = parent.append("g").attr("class", "journey-face").attr("data-cx", cx).attr("data-cy", cy);
53855
- g.append("circle").attr("cx", cx).attr("cy", cy).attr("r", r).attr("fill", color).attr("stroke", palette.bg).attr("stroke-width", 1.5);
53856
- const eyeColor = contrastText(
53857
- color,
53858
- palette.textOnFillLight,
53859
- palette.textOnFillDark
53860
- );
54064
+ const faceFill = shapeFill(palette, color, isDark);
54065
+ g.append("circle").attr("cx", cx).attr("cy", cy).attr("r", r + 1).attr("fill", palette.bg);
54066
+ g.append("circle").attr("cx", cx).attr("cy", cy).attr("r", r).attr("fill", faceFill).attr("stroke", color).attr("stroke-width", 2);
54067
+ const eyeColor = color;
53861
54068
  const eyeY = cy - r * 0.15;
53862
54069
  const eyeSpacing = r * 0.32;
53863
54070
  const eyeR = r * 0.12;
53864
54071
  g.append("circle").attr("cx", cx - eyeSpacing).attr("cy", eyeY).attr("r", eyeR).attr("fill", eyeColor);
53865
54072
  g.append("circle").attr("cx", cx + eyeSpacing).attr("cy", eyeY).attr("r", eyeR).attr("fill", eyeColor);
53866
- const mouthY = cy + r * 0.25;
53867
- const mouthW = r * 0.45;
53868
- const curve = (score - 3) / 2 * r * 0.35;
54073
+ const mouthW = r * 0.46;
54074
+ const curve = (score - 3) / 2 * r * 0.42;
54075
+ const apexY = cy + r * 0.32;
54076
+ const mouthY = apexY - curve / 2;
53869
54077
  g.append("path").attr(
53870
54078
  "d",
53871
54079
  `M ${cx - mouthW} ${mouthY} Q ${cx} ${mouthY + curve} ${cx + mouthW} ${mouthY}`
@@ -53936,7 +54144,7 @@ function renderJourneyMapForExport(content, theme, palette) {
53936
54144
  }
53937
54145
  return svgEl.outerHTML;
53938
54146
  }
53939
- var DIAGRAM_PADDING13, PADDING2, CARD_PADDING_X3, CARD_PADDING_Y3, CARD_HEADER_HEIGHT3, CARD_STROKE_WIDTH2, CARD_META_LINE_HEIGHT3, CARD_GAP_INTERNAL, COLUMN_RADIUS2, COLUMN_HEADER_HEIGHT2, COLUMN_PADDING3, FONT_SIZE_TITLE, FONT_SIZE_PHASE, FONT_SIZE_STEP, FONT_SIZE_META, GRID_LINE_OPACITY, CURVE_STROKE_WIDTH, FACE_RADIUS, DIM_HOVER, TITLE_LINE_HEIGHT, EMOTION_LABEL_MAX_WIDTH, EMOTION_LABEL_FONT_SIZE, ICON_THUMBS_DOWN, ICON_THUMBS_UP, ICON_THOUGHT;
54147
+ var DIAGRAM_PADDING13, PADDING2, CARD_PADDING_X3, CARD_PADDING_Y3, CARD_HEADER_HEIGHT3, CARD_STROKE_WIDTH2, CARD_META_LINE_HEIGHT3, CARD_GAP_INTERNAL, COLUMN_RADIUS2, COLUMN_HEADER_HEIGHT2, COLUMN_PADDING3, FONT_SIZE_TITLE, FONT_SIZE_PHASE, FONT_SIZE_STEP, FONT_SIZE_META, GRID_LINE_OPACITY, CURVE_STROKE_WIDTH, FACE_RADIUS, FACE_HOVER_SCALE, FACE_HOVER_R, DIM_HOVER, TITLE_LINE_HEIGHT, EMOTION_LABEL_MAX_WIDTH, EMOTION_LABEL_FONT_SIZE, ICON_THUMBS_DOWN, ICON_THUMBS_UP, ICON_THOUGHT;
53940
54148
  var init_renderer17 = __esm({
53941
54149
  "src/journey-map/renderer.ts"() {
53942
54150
  "use strict";
@@ -53970,6 +54178,8 @@ var init_renderer17 = __esm({
53970
54178
  GRID_LINE_OPACITY = 0.15;
53971
54179
  CURVE_STROKE_WIDTH = 2.5;
53972
54180
  FACE_RADIUS = 14;
54181
+ FACE_HOVER_SCALE = 1.5;
54182
+ FACE_HOVER_R = (FACE_RADIUS + 1) * FACE_HOVER_SCALE;
53973
54183
  DIM_HOVER = 0.25;
53974
54184
  TITLE_LINE_HEIGHT = 16;
53975
54185
  EMOTION_LABEL_MAX_WIDTH = 180;