@diagrammo/dgmo 0.39.0 → 0.40.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",
@@ -15339,8 +15342,61 @@ function parseEventLine(content, palette) {
15339
15342
  EVENT_LINE_DIAGNOSTIC_CODES.NO_EVENTS
15340
15343
  );
15341
15344
  }
15345
+ if (options.scale) {
15346
+ validateEraDateOrder(result.events, result.eras, pushWarning);
15347
+ }
15342
15348
  return result;
15343
15349
  }
15350
+ function validateEraDateOrder(events, eras, pushWarning) {
15351
+ if (eras.length < 2) return;
15352
+ const order = new Map(eras.map((e, i) => [e.name, i]));
15353
+ const dated = events.filter(
15354
+ (e) => e.dateValue !== null && e.era !== null && order.has(e.era)
15355
+ );
15356
+ if (dated.length === 0) return;
15357
+ const perEra = /* @__PURE__ */ new Map();
15358
+ for (const ev of dated) {
15359
+ const idx = order.get(ev.era);
15360
+ const cur = perEra.get(idx);
15361
+ if (!cur) {
15362
+ perEra.set(idx, { min: ev, max: ev });
15363
+ } else {
15364
+ if (ev.dateValue < cur.min.dateValue) cur.min = ev;
15365
+ if (ev.dateValue > cur.max.dateValue) cur.max = ev;
15366
+ }
15367
+ }
15368
+ const n = eras.length;
15369
+ const prefixMax = new Array(n).fill(null);
15370
+ const suffixMin = new Array(n).fill(null);
15371
+ for (let k = 1; k < n; k++) {
15372
+ const prev = perEra.get(k - 1)?.max ?? null;
15373
+ const carried = prefixMax[k - 1] ?? null;
15374
+ prefixMax[k] = prev && (!carried || prev.dateValue > carried.dateValue) ? prev : carried;
15375
+ }
15376
+ for (let k = n - 2; k >= 0; k--) {
15377
+ const next = perEra.get(k + 1)?.min ?? null;
15378
+ const carried = suffixMin[k + 1] ?? null;
15379
+ suffixMin[k] = next && (!carried || next.dateValue < carried.dateValue) ? next : carried;
15380
+ }
15381
+ for (const ev of dated) {
15382
+ const k = order.get(ev.era);
15383
+ const ahead = suffixMin[k];
15384
+ const behind = prefixMax[k];
15385
+ if (ahead && ev.dateValue > ahead.dateValue) {
15386
+ pushWarning(
15387
+ ev.lineNumber,
15388
+ `"${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.`,
15389
+ EVENT_LINE_DIAGNOSTIC_CODES.ERA_DATE_ORDER
15390
+ );
15391
+ } else if (behind && ev.dateValue < behind.dateValue) {
15392
+ pushWarning(
15393
+ ev.lineNumber,
15394
+ `"${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.`,
15395
+ EVENT_LINE_DIAGNOSTIC_CODES.ERA_DATE_ORDER
15396
+ );
15397
+ }
15398
+ }
15399
+ }
15344
15400
  function parseEventHeader(trimmed, lineNumber, era, aliasMap, diagnostics, pushWarning) {
15345
15401
  let date = null;
15346
15402
  let dateValue = null;
@@ -15407,7 +15463,8 @@ var init_parser13 = __esm({
15407
15463
  EVENT_LINE_DIAGNOSTIC_CODES = {
15408
15464
  NO_EVENTS: "E_EVENT_LINE_NO_EVENTS",
15409
15465
  BAD_DATE: "E_EVENT_LINE_BAD_DATE",
15410
- UNSUPPORTED: "E_EVENT_LINE_UNSUPPORTED"
15466
+ UNSUPPORTED: "E_EVENT_LINE_UNSUPPORTED",
15467
+ ERA_DATE_ORDER: "E_EVENT_LINE_ERA_DATE_ORDER"
15411
15468
  };
15412
15469
  NON_ISO_DATE_RE = /^\d{1,4}[/.]\d/;
15413
15470
  SIDE_RE = /^side\s+(above|below|alternate)\b/i;
@@ -26344,9 +26401,6 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26344
26401
  const hasExpandedEra = parsed.events.some(
26345
26402
  (e) => e.era && eraByName.has(e.era) && !collapsedSet.has(e.era)
26346
26403
  );
26347
- const anyCollapsed = parsed.events.some(
26348
- (e) => e.era && collapsedSet.has(e.era)
26349
- );
26350
26404
  const sideOpt = parsed.options.side;
26351
26405
  const alternate = sideOpt === "alternate";
26352
26406
  const eraSide = alternate ? "below" : sideOpt === "above" ? "below" : "above";
@@ -26413,7 +26467,8 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26413
26467
  x: 0,
26414
26468
  side,
26415
26469
  lane: 0,
26416
- left: 0
26470
+ left: 0,
26471
+ spanHalf: 0
26417
26472
  };
26418
26473
  }
26419
26474
  const era = slot.era;
@@ -26426,19 +26481,19 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26426
26481
  palette.textOnFillLight,
26427
26482
  palette.textOnFillDark
26428
26483
  );
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(
26484
+ let spanLo = null;
26485
+ let spanHi = null;
26486
+ for (const m of slot.members) {
26487
+ if (m.dateValue === null) continue;
26488
+ if (spanLo === null || m.dateValue < spanLo) spanLo = m.dateValue;
26489
+ if (spanHi === null || m.dateValue > spanHi) spanHi = m.dateValue;
26490
+ }
26491
+ const repDateValue = spanLo !== null && spanHi !== null ? (spanLo + spanHi) / 2 : null;
26492
+ const memberStrs = slot.members.map(
26432
26493
  (m) => `\u2022 ${m.date ? `${m.date} ` : ""}${m.label}`
26433
26494
  );
26434
- const bulletColors = shown.map(eventColor);
26495
+ const bulletColors = slot.members.map(eventColor);
26435
26496
  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
26497
  return {
26443
26498
  kind: "era",
26444
26499
  event: null,
@@ -26446,7 +26501,7 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26446
26501
  members: slot.members,
26447
26502
  label: era.name,
26448
26503
  date: null,
26449
- dateValue: null,
26504
+ dateValue: repDateValue,
26450
26505
  lineNumber: era.lineNumber,
26451
26506
  eraName: era.name,
26452
26507
  color: solid,
@@ -26458,87 +26513,189 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26458
26513
  x: 0,
26459
26514
  side,
26460
26515
  lane: 0,
26461
- left: 0
26516
+ left: 0,
26517
+ spanHalf: 0
26462
26518
  };
26463
26519
  });
26464
- const scaled = parsed.options.scale && !anyCollapsed && placed.every((p) => p.dateValue !== null);
26520
+ const scaled = parsed.options.scale && placed.every((p) => p.dateValue !== null);
26465
26521
  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);
26522
+ const MIN_DOT_GAP = 16;
26523
+ const STRETCH_CAP = 8;
26524
+ const COLLAPSE_W = 64;
26525
+ const SEG_GAP = 16;
26526
+ const segments = [];
26527
+ let run = [];
26528
+ const flushRun = () => {
26529
+ if (!run.length) return;
26530
+ const ds = run.map((e) => e.dateValue);
26531
+ segments.push({
26532
+ kind: "run",
26533
+ events: run,
26534
+ lo: Math.min(...ds),
26535
+ hi: Math.max(...ds)
26472
26536
  });
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;
26537
+ run = [];
26538
+ };
26539
+ for (const p of placed) {
26540
+ if (p.kind === "era") {
26541
+ flushRun();
26542
+ segments.push({ kind: "capsule", p });
26543
+ } else {
26544
+ run.push(p);
26545
+ }
26546
+ }
26547
+ flushRun();
26548
+ const totalRunTime = segments.reduce(
26549
+ (s, seg) => s + (seg.kind === "run" ? seg.hi - seg.lo : 0),
26550
+ 0
26551
+ );
26552
+ const place = (stretch) => {
26553
+ if (scaled) {
26554
+ const pxPerUnit = totalRunTime > 0 ? innerW * stretch / totalRunTime : 0;
26555
+ let cursor = H_MARGIN;
26556
+ for (const seg of segments) {
26557
+ if (seg.kind === "capsule") {
26558
+ seg.p.x = cursor + COLLAPSE_W / 2;
26559
+ seg.p.spanHalf = COLLAPSE_W / 2;
26560
+ cursor += COLLAPSE_W + SEG_GAP;
26561
+ } else {
26562
+ for (const e of seg.events) {
26563
+ e.x = cursor + (e.dateValue - seg.lo) * pxPerUnit;
26564
+ e.spanHalf = 0;
26565
+ }
26566
+ cursor += (seg.hi - seg.lo) * pxPerUnit + SEG_GAP;
26567
+ }
26568
+ }
26569
+ let prevX = -Infinity;
26570
+ let prevVal = null;
26571
+ for (const p of [...placed].sort((a, b) => a.x - b.x)) {
26572
+ if (p.kind === "era") {
26573
+ prevX = p.x + p.spanHalf;
26574
+ prevVal = null;
26575
+ continue;
26576
+ }
26577
+ if (p.dateValue === prevVal) {
26578
+ p.x = prevX;
26579
+ continue;
26580
+ }
26581
+ if (p.x < prevX + MIN_DOT_GAP) p.x = prevX + MIN_DOT_GAP;
26582
+ prevX = p.x;
26583
+ prevVal = p.dateValue;
26584
+ }
26585
+ } else {
26586
+ placed.forEach((p) => {
26587
+ p.spanHalf = 0;
26588
+ });
26589
+ const n = placed.length;
26590
+ const spacing = n > 1 ? Math.max(MIN_SPACING, innerW / (n - 1)) : 0;
26591
+ placed.forEach((p, i) => {
26592
+ p.x = H_MARGIN + i * spacing;
26593
+ });
26594
+ }
26595
+ for (const side of ["above", "below"]) {
26596
+ const arr = placed.filter((p) => p.side === side).sort((a, b) => a.x - b.x);
26597
+ const laneRight = [];
26598
+ for (let i = 0; i < arr.length; i++) {
26599
+ const p = arr[i];
26600
+ const next = arr[i + 1];
26601
+ const crowdedRight = !!next && next.x - p.x < CARD_W + FAN_GAP;
26602
+ const preferred = crowdedRight ? p.x - CARD_W + CARD_INSET : p.x - CARD_W / 2;
26603
+ const maxLeft = p.x - CARD_INSET;
26604
+ const minLeft = p.x - CARD_W + CARD_INSET;
26605
+ let lane = laneRight.length;
26606
+ let left = Math.max(preferred, minLeft);
26607
+ for (let l = 0; l < laneRight.length; l++) {
26608
+ const want = Math.max(preferred, laneRight[l] + FAN_GAP, minLeft);
26609
+ if (want <= maxLeft) {
26610
+ lane = l;
26611
+ left = want;
26612
+ break;
26613
+ }
26614
+ }
26615
+ p.lane = lane;
26616
+ p.left = Math.max(6, left);
26617
+ laneRight[lane] = p.left + CARD_W;
26618
+ }
26619
+ }
26620
+ };
26621
+ const derive = () => {
26622
+ const contentW2 = Math.max(
26623
+ width,
26624
+ Math.max(...placed.map((p) => p.x + p.spanHalf)) + H_MARGIN,
26625
+ Math.max(...placed.map((p) => p.left + CARD_W)) + 6
26626
+ );
26627
+ const laneOffset = { above: [], below: [] };
26628
+ for (const side of ["above", "below"]) {
26629
+ const laneH = [];
26630
+ for (const p of placed) {
26631
+ if (p.side !== side) continue;
26632
+ laneH[p.lane] = Math.max(laneH[p.lane] ?? 0, p.cardH);
26480
26633
  }
26481
- if (p.x < prevX + MIN_DOT_GAP) p.x = prevX + MIN_DOT_GAP;
26482
- prevX = p.x;
26483
- prevVal = p.dateValue;
26634
+ const off = [];
26635
+ let acc = 0;
26636
+ for (let l = 0; l < laneH.length; l++) {
26637
+ off[l] = acc;
26638
+ acc += (laneH[l] ?? 0) + LANE_GAP;
26639
+ }
26640
+ laneOffset[side] = off;
26484
26641
  }
26642
+ const laneNear2 = (p) => (p.side === "above" ? LEADER_ABOVE : LEADER_BELOW) + (laneOffset[p.side][p.lane] ?? 0);
26643
+ const ext = (side) => Math.max(
26644
+ 0,
26645
+ ...placed.filter((p) => p.side === side).map((p) => laneNear2(p) + p.cardH)
26646
+ );
26647
+ const dateAbove = placed.some((p) => p.side === "below" && p.date);
26648
+ const dateBelow = placed.some((p) => p.side === "above" && p.date);
26649
+ const contentAbove = Math.max(
26650
+ ext("above"),
26651
+ dateAbove ? DATE_OFFSET + 10 : 0
26652
+ );
26653
+ const contentBelow = Math.max(
26654
+ ext("below"),
26655
+ dateBelow ? DATE_OFFSET + 10 : 0
26656
+ );
26657
+ const aboveExt = contentAbove + (hasExpandedEra && eraSide === "above" ? ERA_BLOCK : 0);
26658
+ const belowExt = contentBelow + (hasExpandedEra && eraSide === "below" ? ERA_BLOCK : 0);
26659
+ const TOP_PAD = 14;
26660
+ const BOT_PAD = 14;
26661
+ const spineY2 = topUsed + TOP_PAD + aboveExt;
26662
+ const eraBaseY2 = eraSide === "above" ? spineY2 - (contentAbove + 14) : spineY2 + (contentBelow + 14);
26663
+ const contentH = spineY2 + belowExt + BOT_PAD;
26664
+ const totalH2 = Math.max(heightHint, contentH);
26665
+ return { contentW: contentW2, contentH, totalH: totalH2, spineY: spineY2, eraBaseY: eraBaseY2, laneNear: laneNear2 };
26666
+ };
26667
+ const envStretch = Number(
26668
+ globalThis.process?.env?.["DGMO_EVT_STRETCH"]
26669
+ );
26670
+ let layout;
26671
+ if (Number.isFinite(envStretch) && envStretch > 0) {
26672
+ place(envStretch);
26673
+ layout = derive();
26485
26674
  } 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;
26675
+ place(1);
26676
+ layout = derive();
26677
+ const panelAspect = width / heightHint;
26678
+ const aspectOf = (l) => l.contentW / l.contentH;
26679
+ if (scaled && layout.contentH > heightHint && aspectOf(layout) < panelAspect) {
26680
+ place(STRETCH_CAP);
26681
+ const capped = derive();
26682
+ if (aspectOf(capped) <= panelAspect) {
26683
+ layout = capped;
26684
+ } else {
26685
+ let loF = 1;
26686
+ let hiF = STRETCH_CAP;
26687
+ for (let it = 0; it < 8; it++) {
26688
+ const mid = (loF + hiF) / 2;
26689
+ place(mid);
26690
+ if (aspectOf(derive()) >= panelAspect) hiF = mid;
26691
+ else loF = mid;
26510
26692
  }
26693
+ place(hiF);
26694
+ layout = derive();
26511
26695
  }
26512
- p.lane = lane;
26513
- p.left = Math.max(6, left);
26514
- laneRight[lane] = p.left + CARD_W;
26515
26696
  }
26516
26697
  }
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);
26698
+ const { contentW, totalH, spineY, eraBaseY, laneNear } = layout;
26542
26699
  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
26700
  svg.append("rect").attr("width", contentW).attr("height", totalH).attr("fill", palette.bg);
26544
26701
  if (showTitle) {
@@ -26566,12 +26723,11 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26566
26723
  activeGroup
26567
26724
  });
26568
26725
  }
26569
- const xs = placed.map((p) => p.x);
26570
- const x0 = Math.min(...xs);
26571
- const x1 = Math.max(...xs);
26726
+ const x0 = Math.min(...placed.map((p) => p.x - p.spanHalf));
26727
+ const x1 = Math.max(...placed.map((p) => p.x + p.spanHalf));
26572
26728
  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
26729
  const geo = placed.map((p) => {
26574
- const near = p.side === "above" ? spineY - LEADER_ABOVE - p.lane * rowGapA : spineY + LEADER_BELOW + p.lane * rowGapB;
26730
+ const near = p.side === "above" ? spineY - laneNear(p) : spineY + laneNear(p);
26575
26731
  const top = p.side === "above" ? near - p.cardH : near;
26576
26732
  return { p, near, top };
26577
26733
  });
@@ -26683,7 +26839,7 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26683
26839
  for (const p of placed) {
26684
26840
  if (p.kind === "era") {
26685
26841
  const col = p.color;
26686
- const half = Math.min(46, 16 + (p.members.length - 1) * 9);
26842
+ const half = Math.max(16, p.spanHalf);
26687
26843
  const spineLeft = x0 - 20;
26688
26844
  const spineRight = x1 + 20;
26689
26845
  const bx0 = Math.max(spineLeft, p.x - half);
@@ -26694,6 +26850,11 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26694
26850
  "d",
26695
26851
  `M${bx0},${spineY} L${bx0},${barY} L${bx1},${barY} L${bx1},${spineY}`
26696
26852
  ).attr("fill", "none").attr("stroke", col).attr("stroke-width", 1.5).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
26853
+ const breakCx = (bx0 + bx1) / 2;
26854
+ 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");
26855
+ for (const d of axisBreakPaths(breakCx, spineY)) {
26856
+ 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);
26857
+ }
26697
26858
  if (onClickItem) {
26698
26859
  const ln = p.lineNumber;
26699
26860
  eg.style("cursor", "pointer").on("click", () => onClickItem(ln));
@@ -26727,13 +26888,23 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26727
26888
  }
26728
26889
  const clampX = (v) => Math.max(4, Math.min(contentW - 4, v));
26729
26890
  const cap = eraSide === "above" ? 1 : -1;
26730
- for (const r of runs) {
26731
- if (r.collapsed) continue;
26891
+ const drawn = runs.filter((r) => !r.collapsed).map((r) => ({
26892
+ run: r,
26893
+ x0: clampX(r.firstX - ERA_BRACKET_PAD),
26894
+ x1: clampX(r.lastX + ERA_BRACKET_PAD)
26895
+ })).sort((a, b) => a.run.firstX - b.run.firstX);
26896
+ for (let i = 1; i < drawn.length; i++) {
26897
+ const prev = drawn[i - 1];
26898
+ const cur = drawn[i];
26899
+ if (prev.x1 <= cur.x0) continue;
26900
+ const mid = (prev.run.lastX + cur.run.firstX) / 2;
26901
+ prev.x1 = Math.max(prev.run.lastX, Math.min(prev.x1, mid - ERA_SEAM_GAP));
26902
+ cur.x0 = Math.min(cur.run.firstX, Math.max(cur.x0, mid + ERA_SEAM_GAP));
26903
+ }
26904
+ for (const { run: r, x0: x02, x1: x12 } of drawn) {
26732
26905
  const neutral = !r.era.color;
26733
26906
  const col = neutral ? palette.text : resolveColor(r.era.color, palette) ?? palette.text;
26734
26907
  const op = neutral ? 0.5 : 0.85;
26735
- const x02 = clampX(r.firstX - 14);
26736
- const x12 = clampX(r.lastX + 14);
26737
26908
  const y = eraBaseY;
26738
26909
  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
26910
  eg.append("path").attr(
@@ -26759,6 +26930,22 @@ function renderEventLineForExport(container, parsed, palette, isDark, exportDims
26759
26930
  tagOverride
26760
26931
  );
26761
26932
  }
26933
+ function axisBreakPaths(cx, cy) {
26934
+ const HUMPS = 2;
26935
+ const STEPS = 14;
26936
+ const wave = (x0) => {
26937
+ let d = "";
26938
+ for (let i = 0; i <= STEPS; i++) {
26939
+ const t = i / STEPS;
26940
+ const y = cy - BREAK_HALF_H + t * 2 * BREAK_HALF_H;
26941
+ const x = x0 + BREAK_AMP * Math.sin(t * Math.PI * HUMPS);
26942
+ d += `${i === 0 ? "M" : "L"}${x.toFixed(2)},${y.toFixed(2)}`;
26943
+ if (i < STEPS) d += " ";
26944
+ }
26945
+ return d;
26946
+ };
26947
+ return [wave(cx - BREAK_GAP / 2), wave(cx + BREAK_GAP / 2)];
26948
+ }
26762
26949
  function wrapDescription(lines, charsPerLine) {
26763
26950
  const out = [];
26764
26951
  for (const line11 of lines) {
@@ -26788,7 +26975,7 @@ function renderBody(cardG, lines, bodyColor, palette, startBaseline = CARD_BODY_
26788
26975
  y += DESC_LINE_H;
26789
26976
  }
26790
26977
  }
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;
26978
+ 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
26979
  var init_renderer = __esm({
26793
26980
  "src/event-line/renderer.ts"() {
26794
26981
  "use strict";
@@ -26822,9 +27009,13 @@ var init_renderer = __esm({
26822
27009
  DATE_OFFSET = 28;
26823
27010
  ERA_BLOCK = 30;
26824
27011
  ERA_BRACKET_CAP = 8;
27012
+ ERA_BRACKET_PAD = 14;
27013
+ ERA_SEAM_GAP = 3;
27014
+ BREAK_HALF_H = 6;
27015
+ BREAK_AMP = 2.2;
27016
+ BREAK_GAP = 5;
26825
27017
  ERA_LEG = 13;
26826
27018
  ERA_LABEL_FONT = 11.5;
26827
- ERA_MEMBER_MAX = 6;
26828
27019
  }
26829
27020
  });
26830
27021
 
@@ -53297,6 +53488,7 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
53297
53488
  y,
53298
53489
  score,
53299
53490
  palette,
53491
+ isDark,
53300
53492
  SCORE_LABEL_R
53301
53493
  );
53302
53494
  if (!exportDims) {
@@ -53328,7 +53520,14 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
53328
53520
  const allSteps = parsed.phases.length > 0 ? parsed.phases.flatMap((p) => p.steps) : parsed.steps;
53329
53521
  for (const pt of layout.curvePoints) {
53330
53522
  const step = allSteps[pt.stepIndex];
53331
- const faceG = renderScoreFace(curveG, pt.x, pt.y, pt.score, palette);
53523
+ const faceG = renderScoreFace(
53524
+ curveG,
53525
+ pt.x,
53526
+ pt.y,
53527
+ pt.score,
53528
+ palette,
53529
+ isDark
53530
+ );
53332
53531
  addEmotionLabel(faceG, pt, palette);
53333
53532
  if (step) {
53334
53533
  faceG.attr("data-line-number", step.lineNumber);
@@ -53354,7 +53553,14 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
53354
53553
  const pt = layout.curvePoints[0];
53355
53554
  const allSteps = parsed.phases.length > 0 ? parsed.phases.flatMap((p) => p.steps) : parsed.steps;
53356
53555
  const step = allSteps[pt.stepIndex];
53357
- const faceG = renderScoreFace(curveG, pt.x, pt.y, pt.score, palette);
53556
+ const faceG = renderScoreFace(
53557
+ curveG,
53558
+ pt.x,
53559
+ pt.y,
53560
+ pt.score,
53561
+ palette,
53562
+ isDark
53563
+ );
53358
53564
  addEmotionLabel(faceG, pt, palette);
53359
53565
  if (step) {
53360
53566
  faceG.attr("data-line-number", step.lineNumber);
@@ -53451,6 +53657,7 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
53451
53657
  faceCy,
53452
53658
  step.score,
53453
53659
  palette,
53660
+ isDark,
53454
53661
  COLLAPSED_FACE_R
53455
53662
  );
53456
53663
  faceG.attr("data-line-number", step.lineNumber);
@@ -53554,7 +53761,7 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
53554
53761
  const fcy = parseFloat(el.attr("data-cy") ?? "0");
53555
53762
  el.attr(
53556
53763
  "transform",
53557
- `translate(${fcx},${fcy}) scale(1.5) translate(${-fcx},${-fcy})`
53764
+ `translate(${fcx},${fcy}) scale(${FACE_HOVER_SCALE}) translate(${-fcx},${-fcy})`
53558
53765
  );
53559
53766
  } else {
53560
53767
  el.attr("transform", null);
@@ -53600,12 +53807,12 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
53600
53807
  PADDING2,
53601
53808
  Math.min(fcx - bw / 2, layout.totalWidth - PADDING2 - bw)
53602
53809
  );
53603
- const by = Math.max(PADDING2, fcy - FACE_RADIUS - THOUGHT_GAP - bh);
53810
+ const by = Math.max(PADDING2, fcy - FACE_HOVER_R - THOUGHT_GAP - bh);
53604
53811
  const scoreColor = scoreToColor(score, palette);
53605
53812
  const tintedBg = mix(scoreColor, palette.surface, 20);
53606
53813
  const g = overlayG.append("g").attr("class", "journey-thought-hover");
53607
53814
  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);
53815
+ 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
53816
  const centerX = bx + bw / 2;
53610
53817
  for (let i = 0; i < lines.length; i++) {
53611
53818
  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 +54055,23 @@ function renderPersonaSilhouette(parent, cx, cy, color, palette, scale = 1) {
53848
54055
  `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
54056
  ).attr("fill", fill2).attr("stroke", stroke2).attr("stroke-width", 1.2);
53850
54057
  }
53851
- function renderScoreFace(parent, cx, cy, score, palette, radius) {
54058
+ function renderScoreFace(parent, cx, cy, score, palette, isDark, radius) {
53852
54059
  const r = radius ?? FACE_RADIUS;
53853
54060
  const color = scoreToColor(score, palette);
53854
54061
  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
- );
54062
+ const faceFill = shapeFill(palette, color, isDark);
54063
+ g.append("circle").attr("cx", cx).attr("cy", cy).attr("r", r + 1).attr("fill", palette.bg);
54064
+ g.append("circle").attr("cx", cx).attr("cy", cy).attr("r", r).attr("fill", faceFill).attr("stroke", color).attr("stroke-width", 2);
54065
+ const eyeColor = color;
53861
54066
  const eyeY = cy - r * 0.15;
53862
54067
  const eyeSpacing = r * 0.32;
53863
54068
  const eyeR = r * 0.12;
53864
54069
  g.append("circle").attr("cx", cx - eyeSpacing).attr("cy", eyeY).attr("r", eyeR).attr("fill", eyeColor);
53865
54070
  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;
54071
+ const mouthW = r * 0.46;
54072
+ const curve = (score - 3) / 2 * r * 0.42;
54073
+ const apexY = cy + r * 0.32;
54074
+ const mouthY = apexY - curve / 2;
53869
54075
  g.append("path").attr(
53870
54076
  "d",
53871
54077
  `M ${cx - mouthW} ${mouthY} Q ${cx} ${mouthY + curve} ${cx + mouthW} ${mouthY}`
@@ -53936,7 +54142,7 @@ function renderJourneyMapForExport(content, theme, palette) {
53936
54142
  }
53937
54143
  return svgEl.outerHTML;
53938
54144
  }
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;
54145
+ 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
54146
  var init_renderer17 = __esm({
53941
54147
  "src/journey-map/renderer.ts"() {
53942
54148
  "use strict";
@@ -53970,6 +54176,8 @@ var init_renderer17 = __esm({
53970
54176
  GRID_LINE_OPACITY = 0.15;
53971
54177
  CURVE_STROKE_WIDTH = 2.5;
53972
54178
  FACE_RADIUS = 14;
54179
+ FACE_HOVER_SCALE = 1.5;
54180
+ FACE_HOVER_R = (FACE_RADIUS + 1) * FACE_HOVER_SCALE;
53973
54181
  DIM_HOVER = 0.25;
53974
54182
  TITLE_LINE_HEIGHT = 16;
53975
54183
  EMOTION_LABEL_MAX_WIDTH = 180;