@diagrammo/dgmo 0.92.0 → 0.92.1

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.
@@ -979,6 +979,13 @@ declare function getSimpleChartLegendGroups(parsed: ParsedChart, colors: string[
979
979
  */
980
980
  declare function getExtendedChartLegendGroups(parsed: ParsedExtendedChart, colors: string[]): LegendGroupData[];
981
981
 
982
+ /** How the axis draws its tick labels, so they can be measured (#1426). */
983
+ interface TickLabelFit {
984
+ /** Font size the labels are drawn at. */
985
+ fontSize: number;
986
+ /** True when labels stack down a vertical axis rather than along a row. */
987
+ vertical?: boolean;
988
+ }
982
989
  /**
983
990
  * Generates adaptive tick marks along a time axis.
984
991
  * Picks the right granularity (years, months, weeks, days, hours, minutes)
@@ -988,7 +995,7 @@ declare function getExtendedChartLegendGroups(parsed: ParsedExtendedChart, color
988
995
  * - boundaryStart/boundaryEnd: numeric date values
989
996
  * - boundaryStartLabel/boundaryEndLabel: formatted labels for those dates
990
997
  */
991
- declare function computeTimeTicks(domainMin: number, domainMax: number, scale: d3Scale.ScaleLinear<number, number>, boundaryStart?: number, boundaryEnd?: number, boundaryStartLabel?: string, boundaryEndLabel?: string): {
998
+ declare function computeTimeTicks(domainMin: number, domainMax: number, scale: d3Scale.ScaleLinear<number, number>, boundaryStart?: number, boundaryEnd?: number, boundaryStartLabel?: string, boundaryEndLabel?: string, labelFit?: TickLabelFit): {
992
999
  pos: number;
993
1000
  label: string;
994
1001
  }[];
package/dist/advanced.js CHANGED
@@ -146,7 +146,7 @@ import {
146
146
  import {
147
147
  buildTagLaneRowList,
148
148
  renderGantt
149
- } from "./chunk-LMP6Y7QO.js";
149
+ } from "./chunk-4QLGGQNO.js";
150
150
  import {
151
151
  renderFamily,
152
152
  renderFamilyForExport
@@ -326,7 +326,7 @@ import {
326
326
  focusEventLine,
327
327
  renderEventLine,
328
328
  renderEventLineForExport
329
- } from "./chunk-62BP7AJU.js";
329
+ } from "./chunk-QQPNCXAO.js";
330
330
  import "./chunk-NPWVOFB3.js";
331
331
  import {
332
332
  parseInlineMarkdown,
@@ -350,7 +350,7 @@ import {
350
350
  renderSlopeChart,
351
351
  renderVenn,
352
352
  renderWordCloud
353
- } from "./chunk-RJ2JW3FG.js";
353
+ } from "./chunk-Y2V5AEVG.js";
354
354
  import {
355
355
  layoutBracket
356
356
  } from "./chunk-ZI6WC24V.js";
@@ -430,10 +430,10 @@ import "./chunk-2CW5ZTNH.js";
430
430
  import {
431
431
  formatDateLabel,
432
432
  renderTimeline
433
- } from "./chunk-F7326NVM.js";
433
+ } from "./chunk-O5WUZGPV.js";
434
434
  import {
435
435
  computeTimeTicks
436
- } from "./chunk-YRYKTYMQ.js";
436
+ } from "./chunk-VSOZZYLK.js";
437
437
  import {
438
438
  ScaleContext
439
439
  } from "./chunk-24YPR5EH.js";
package/dist/block.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-D3K4U3ZM.js";
5
5
  import {
6
6
  render
7
- } from "./chunk-RJ2JW3FG.js";
7
+ } from "./chunk-Y2V5AEVG.js";
8
8
  import "./chunk-ZI6WC24V.js";
9
9
  import {
10
10
  docsLink,
@@ -34,8 +34,8 @@ import "./chunk-2CW5ZTNH.js";
34
34
  import {
35
35
  escapeAttr,
36
36
  escapeHtml
37
- } from "./chunk-F7326NVM.js";
38
- import "./chunk-YRYKTYMQ.js";
37
+ } from "./chunk-O5WUZGPV.js";
38
+ import "./chunk-VSOZZYLK.js";
39
39
  import "./chunk-24YPR5EH.js";
40
40
  import "./chunk-NMS5XSLX.js";
41
41
  import "./chunk-XCKNFFKM.js";
@@ -1,7 +1,9 @@
1
1
  import {
2
2
  MONTH_ABBR,
3
- computeTimeTicks
4
- } from "./chunk-YRYKTYMQ.js";
3
+ capLabelColumn,
4
+ computeTimeTicks,
5
+ showFullOnHover
6
+ } from "./chunk-VSOZZYLK.js";
5
7
  import {
6
8
  ScaleContext
7
9
  } from "./chunk-24YPR5EH.js";
@@ -130,7 +132,7 @@ function renderLabelBand(svg, y, leftMargin, color, palette, isDark, cssPrefix,
130
132
  function appendTaskIcon(textEl, label, isMilestone, iconColor, textColor) {
131
133
  const icon = isMilestone ? "\u25C6" : "\u25CF";
132
134
  textEl.append("tspan").attr("fill", iconColor).text(icon);
133
- textEl.append("tspan").attr("fill", textColor).text(" " + label);
135
+ return textEl.append("tspan").attr("fill", textColor).text(" " + label);
134
136
  }
135
137
  function renderGantt(container, resolved, palette, isDark, options, exportDims) {
136
138
  keepSwimlaneToggleFocus(
@@ -182,7 +184,14 @@ function drawGantt(container, resolved, palette, isDark, options, exportDims) {
182
184
  ...labelWidths,
183
185
  measureText("0000000000", LABEL_FONT)
184
186
  );
185
- const leftMargin = Math.max(MIN_LEFT_MARGIN, maxLabelWidth + 30);
187
+ const containerWidth = exportDims?.width ?? (container.clientWidth || 800);
188
+ const labelColumn = capLabelColumn(
189
+ Math.max(MIN_LEFT_MARGIN, maxLabelWidth + 30),
190
+ containerWidth,
191
+ MIN_LEFT_MARGIN
192
+ );
193
+ const leftMargin = labelColumn.width;
194
+ const fitLabel = labelColumn.fit;
186
195
  const totalRows = rows.length;
187
196
  const hasCriticalPath = resolved.options.criticalPath && resolved.tasks.some((t) => t.isCriticalPath);
188
197
  const hasDependencies = resolved.options.dependencies && resolved.tasks.some((t) => t.task.dependencies.length > 0);
@@ -196,7 +205,6 @@ function drawGantt(container, resolved, palette, isDark, options, exportDims) {
196
205
  const title = resolved.options.title;
197
206
  const showTitle = !!title && !resolved.options.noTitle;
198
207
  const titleHeight = showTitle ? 50 : 20;
199
- const containerWidth = exportDims?.width ?? (container.clientWidth || 800);
200
208
  const hasTagLegend = !resolved.options.noLegend && (resolved.tagGroups.length > 0 || hasCriticalPath || hasDependencies);
201
209
  const tagLegendReserve = hasTagLegend ? getMaxLegendReservedHeight(
202
210
  {
@@ -492,8 +500,14 @@ function drawGantt(container, resolved, palette, isDark, options, exportDims) {
492
500
  restoreHighlight();
493
501
  hideGanttDateIndicators(g);
494
502
  });
495
- labelG.append("text").attr("x", labelX).attr("y", sMarginTop + yOffset + sBarH / 2).attr("dy", "0.35em").attr("text-anchor", "start").attr("font-size", sFont11).attr("font-weight", "bold").attr("fill", laneColor).text(
496
- toggleIcon + " " + row.laneName + (row.aggregateProgress !== null ? ` ${Math.round(row.aggregateProgress)}%` : "")
503
+ const laneFullText = toggleIcon + " " + row.laneName + (row.aggregateProgress !== null ? ` ${Math.round(row.aggregateProgress)}%` : "");
504
+ const laneText = labelG.append("text").attr("x", labelX).attr("y", sMarginTop + yOffset + sBarH / 2).attr("dy", "0.35em").attr("text-anchor", "start").attr("font-size", sFont11).attr("font-weight", "bold").attr("fill", laneColor);
505
+ showFullOnHover(
506
+ labelG,
507
+ laneText,
508
+ laneText,
509
+ laneFullText,
510
+ fitLabel(laneFullText, labelX, sFont11, true)
497
511
  );
498
512
  if (laneBarWidth > 0) {
499
513
  const barFill = shapeFill(palette, laneColor, isDark, {
@@ -567,8 +581,14 @@ function drawGantt(container, resolved, palette, isDark, options, exportDims) {
567
581
  });
568
582
  const groupIndent = group.depth <= 2 ? group.depth * 14 : 2 * 14 + (group.depth - 2) * 8;
569
583
  const labelX = 10 + groupIndent;
570
- labelG.append("text").attr("x", labelX).attr("y", sMarginTop + yOffset + sBarH / 2).attr("dy", "0.35em").attr("text-anchor", "start").attr("font-size", sFont11).attr("font-weight", "bold").attr("fill", palette.text).text(
571
- toggleIcon + " " + group.name + (group.progress !== null ? ` ${Math.round(group.progress)}%` : "")
584
+ const groupFullText = toggleIcon + " " + group.name + (group.progress !== null ? ` ${Math.round(group.progress)}%` : "");
585
+ const groupText = labelG.append("text").attr("x", labelX).attr("y", sMarginTop + yOffset + sBarH / 2).attr("dy", "0.35em").attr("text-anchor", "start").attr("font-size", sFont11).attr("font-weight", "bold").attr("fill", palette.text);
586
+ showFullOnHover(
587
+ labelG,
588
+ groupText,
589
+ groupText,
590
+ groupFullText,
591
+ fitLabel(groupFullText, labelX, sFont11, true)
572
592
  );
573
593
  const gStart = dateToFractionalYear(group.startDate);
574
594
  const gEnd = dateToFractionalYear(group.endDate);
@@ -696,13 +716,25 @@ function drawGantt(container, resolved, palette, isDark, options, exportDims) {
696
716
  }).on("mouseleave", () => {
697
717
  resetHighlight(g, svg);
698
718
  });
699
- appendTaskIcon(
719
+ const taskLabelText = appendTaskIcon(
700
720
  taskLabel,
701
721
  task.label,
702
722
  rt.isMilestone,
703
723
  barColor,
704
724
  palette.text
705
725
  );
726
+ const taskIconW = measureText(
727
+ (rt.isMilestone ? "\u25C6" : "\u25CF") + " ",
728
+ sFont11
729
+ );
730
+ showFullOnHover(
731
+ taskLabel,
732
+ taskLabel,
733
+ taskLabelText,
734
+ task.label,
735
+ fitLabel(task.label, taskLabelX + taskIconW, sFont11),
736
+ " "
737
+ );
706
738
  for (const [key, value] of Object.entries(rt.effectiveMetadata)) {
707
739
  taskLabel.attr(`data-tag-${tagAttrKey(key)}`, value.toLowerCase());
708
740
  }
@@ -1,7 +1,9 @@
1
1
  import {
2
2
  MONTH_ABBR,
3
- computeTimeTicks
4
- } from "./chunk-YRYKTYMQ.js";
3
+ capLabelColumn,
4
+ computeTimeTicks,
5
+ showFullOnHover
6
+ } from "./chunk-VSOZZYLK.js";
5
7
  import {
6
8
  ScaleContext
7
9
  } from "./chunk-24YPR5EH.js";
@@ -48,6 +50,9 @@ import {
48
50
  measureLegendText,
49
51
  truncateLegendText
50
52
  } from "./chunk-UOCWYLYT.js";
53
+ import {
54
+ measureText
55
+ } from "./chunk-KAO7JL4I.js";
51
56
  import {
52
57
  getEraColors,
53
58
  getSeriesColors,
@@ -252,7 +257,8 @@ function renderTimeScale(g, scale, isVertical, innerWidth, innerHeight, textColo
252
257
  boundaryStart,
253
258
  boundaryEnd,
254
259
  boundaryStartLabel,
255
- boundaryEndLabel
260
+ boundaryEndLabel,
261
+ { fontSize: 10, vertical: isVertical }
256
262
  );
257
263
  if (ticks.length < 2) return;
258
264
  const tickLen = 6;
@@ -1054,10 +1060,12 @@ function renderTimelineHorizontalGrouped(container, parsed, palette, isDark, set
1054
1060
  ...lanes.flatMap((l) => l.events.map((ev) => ev.label.length + 2))
1055
1061
  );
1056
1062
  const maxLeftLabelLen = Math.max(maxGroupNameLen, maxEventLabelLen);
1057
- const dynamicLeftMargin = Math.max(
1058
- ctx.aesthetic(140),
1059
- maxLeftLabelLen * sCharW + ctx.aesthetic(30)
1063
+ const labelColumn = capLabelColumn(
1064
+ Math.max(ctx.aesthetic(140), maxLeftLabelLen * sCharW + ctx.aesthetic(30)),
1065
+ width,
1066
+ ctx.aesthetic(140)
1060
1067
  );
1068
+ const dynamicLeftMargin = labelColumn.width;
1061
1069
  const baseTopMargin = title ? 50 : ctx.aesthetic(20);
1062
1070
  const margin = {
1063
1071
  top: baseTopMargin + topScaleH + eraReserve + markerReserve + tagLegendReserve,
@@ -1150,7 +1158,20 @@ function renderTimelineHorizontalGrouped(container, parsed, palette, isDark, set
1150
1158
  g.append("rect").attr("class", "tl-group-header-bg").attr("data-group", lane.name).attr("x", bandX).attr("y", bandY).attr("width", bandW).attr("height", bandH).attr("rx", sBandRx).attr("fill", shapeFill(palette, laneColor, isDark, { mode: fillMode })).style("pointer-events", "none");
1151
1159
  g.append("rect").attr("class", "tl-group-header-accent").attr("data-group", lane.name).attr("x", bandX).attr("y", bandY).attr("width", sBandAccentW).attr("height", bandH).attr("fill", laneColor).attr("clip-path", `url(#${clipId})`).style("pointer-events", "none");
1152
1160
  const headerG = g.append("g").attr("class", "tl-group-header").attr("data-group", lane.name).style("cursor", "pointer").on("mouseenter", () => fadeToGroup(g, lane.name)).on("mouseleave", () => fadeReset(g)).on("click", () => toggleGroup(lane.name));
1153
- headerG.append("text").attr("x", bandX + 5).attr("y", curY + sBarH / 2).attr("dy", "0.35em").attr("text-anchor", "start").attr("fill", textColor).attr("font-size", sLaneHeaderFont).attr("font-weight", "bold").text(`${toggleIcon} ${lane.name}`);
1161
+ const headerText = headerG.append("text").attr("x", bandX + 5).attr("y", curY + sBarH / 2).attr("dy", "0.35em").attr("text-anchor", "start").attr("fill", textColor).attr("font-size", sLaneHeaderFont).attr("font-weight", "bold");
1162
+ const headerFull = `${toggleIcon} ${lane.name}`;
1163
+ showFullOnHover(
1164
+ headerG,
1165
+ headerText,
1166
+ headerText,
1167
+ headerFull,
1168
+ labelColumn.fit(
1169
+ headerFull,
1170
+ bandX + 5 + margin.left,
1171
+ sLaneHeaderFont,
1172
+ true
1173
+ )
1174
+ );
1154
1175
  if (lane.events.length > 0) {
1155
1176
  const evDates = lane.events.map((ev) => parseTimelineDate(ev.date));
1156
1177
  const evEndDates = lane.events.filter((ev) => ev.endDate).map((ev) => parseTimelineDate(ev.endDate));
@@ -1206,7 +1227,19 @@ function renderTimelineHorizontalGrouped(container, parsed, palette, isDark, set
1206
1227
  const icon = isPoint ? "\u25C6" : "\u25CF";
1207
1228
  const labelEl = evG.append("text").attr("class", "tl-event-label").attr("x", -margin.left + ctx.aesthetic(20)).attr("y", y).attr("dy", "0.35em").attr("text-anchor", "start").attr("font-size", sEventFont).attr("fill", textColor);
1208
1229
  labelEl.append("tspan").attr("fill", evColor).text(icon);
1209
- labelEl.append("tspan").attr("fill", textColor).text(" " + ev.label);
1230
+ const labelTextEl = labelEl.append("tspan").attr("fill", textColor);
1231
+ showFullOnHover(
1232
+ evG,
1233
+ labelEl,
1234
+ labelTextEl,
1235
+ ev.label,
1236
+ labelColumn.fit(
1237
+ ev.label,
1238
+ ctx.aesthetic(20) + measureText(icon + " ", sEventFont),
1239
+ sEventFont
1240
+ ),
1241
+ " "
1242
+ );
1210
1243
  if (ev.endDate) {
1211
1244
  const x2 = xScale(parseTimelineDate(ev.endDate));
1212
1245
  const rectW = Math.max(x2 - x, 4);
@@ -9,7 +9,7 @@ import {
9
9
  } from "./chunk-R2K5L4UA.js";
10
10
  import {
11
11
  formatDateLabel
12
- } from "./chunk-F7326NVM.js";
12
+ } from "./chunk-O5WUZGPV.js";
13
13
  import {
14
14
  renderIntegratedLegend
15
15
  } from "./chunk-NMS5XSLX.js";
@@ -1,4 +1,10 @@
1
+ import {
2
+ measureText,
3
+ truncateText
4
+ } from "./chunk-KAO7JL4I.js";
5
+
1
6
  // src/utils/time-ticks.ts
7
+ var TICK_LABEL_GAP = 8;
2
8
  var MONTH_ABBR = [
3
9
  "Jan",
4
10
  "Feb",
@@ -35,11 +41,12 @@ function dateToFractionalYear(d) {
35
41
  function formatYearTick(y) {
36
42
  return y < 0 ? `${-y} BCE` : String(y);
37
43
  }
38
- function computeTimeTicks(domainMin, domainMax, scale, boundaryStart, boundaryEnd, boundaryStartLabel, boundaryEndLabel) {
44
+ function computeTimeTicks(domainMin, domainMax, scale, boundaryStart, boundaryEnd, boundaryStartLabel, boundaryEndLabel, labelFit = { fontSize: 10 }) {
39
45
  const minYear = Math.floor(domainMin);
40
46
  const maxYear = Math.floor(domainMax);
41
47
  const span = domainMax - domainMin;
42
48
  let ticks = [];
49
+ let strides = [];
43
50
  const firstYear = Math.ceil(domainMin);
44
51
  const lastYear = Math.floor(domainMax);
45
52
  if (lastYear >= firstYear + 1) {
@@ -52,9 +59,12 @@ function computeTimeTicks(domainMin, domainMax, scale, boundaryStart, boundaryEn
52
59
  else if (yearSpan > 40) step = 10;
53
60
  else if (yearSpan > 20) step = 5;
54
61
  else if (yearSpan > 10) step = 2;
62
+ strides = [1, 2, 5, 10, 20, 50, 100, 200, 500, 1e3, 2e3, 5e3].filter(
63
+ (c) => c % step === 0
64
+ );
55
65
  const alignedFirst = Math.ceil(firstYear / step) * step;
56
66
  for (let y = alignedFirst; y <= lastYear; y += step) {
57
- ticks.push({ pos: scale(y), label: formatYearTick(y) });
67
+ ticks.push({ pos: scale(y), label: formatYearTick(y), slot: y });
58
68
  }
59
69
  } else if (span > 0.25) {
60
70
  const crossesYear = maxYear > minYear;
@@ -66,7 +76,9 @@ function computeTimeTicks(domainMin, domainMax, scale, boundaryStart, boundaryEn
66
76
  const abbr = MONTH_ABBR[m - 1];
67
77
  ticks.push({
68
78
  pos: scale(val),
69
- label: crossesYear ? `${abbr} '${String(y).slice(-2)}` : abbr
79
+ label: crossesYear ? `${abbr} '${String(y).slice(-2)}` : abbr,
80
+ // Month index, so a stride of 3 lands on Jan, Apr, Jul, Oct.
81
+ slot: y * 12 + (m - 1)
70
82
  });
71
83
  }
72
84
  }
@@ -89,7 +101,11 @@ function computeTimeTicks(domainMin, domainMax, scale, boundaryStart, boundaryEn
89
101
  const hh = String(startDate.getHours()).padStart(2, "0");
90
102
  const mm = String(startDate.getMinutes()).padStart(2, "0");
91
103
  const ss = String(startDate.getSeconds()).padStart(2, "0");
92
- ticks.push({ pos: scale(val), label: `${hh}:${mm}:${ss}` });
104
+ ticks.push({
105
+ pos: scale(val),
106
+ label: `${hh}:${mm}:${ss}`,
107
+ slot: ticks.length
108
+ });
93
109
  }
94
110
  startDate.setSeconds(startDate.getSeconds() + stepSec);
95
111
  }
@@ -111,7 +127,11 @@ function computeTimeTicks(domainMin, domainMax, scale, boundaryStart, boundaryEn
111
127
  if (val >= domainMin) {
112
128
  const hh = String(startDate.getHours()).padStart(2, "0");
113
129
  const mm = String(startDate.getMinutes()).padStart(2, "0");
114
- ticks.push({ pos: scale(val), label: `${hh}:${mm}` });
130
+ ticks.push({
131
+ pos: scale(val),
132
+ label: `${hh}:${mm}`,
133
+ slot: ticks.length
134
+ });
115
135
  }
116
136
  startDate.setMinutes(startDate.getMinutes() + stepMin);
117
137
  }
@@ -136,11 +156,19 @@ function computeTimeTicks(domainMin, domainMax, scale, boundaryStart, boundaryEn
136
156
  const hh = String(startDate.getHours()).padStart(2, "0");
137
157
  const mm = String(startDate.getMinutes()).padStart(2, "0");
138
158
  if (singleDay) {
139
- ticks.push({ pos: scale(val), label: `${hh}:${mm}` });
159
+ ticks.push({
160
+ pos: scale(val),
161
+ label: `${hh}:${mm}`,
162
+ slot: ticks.length
163
+ });
140
164
  } else {
141
165
  const mon = MONTH_ABBR[startDate.getMonth()];
142
166
  const d = startDate.getDate();
143
- ticks.push({ pos: scale(val), label: `${mon} ${d} ${hh}:${mm}` });
167
+ ticks.push({
168
+ pos: scale(val),
169
+ label: `${mon} ${d} ${hh}:${mm}`,
170
+ slot: ticks.length
171
+ });
144
172
  }
145
173
  }
146
174
  startDate.setHours(startDate.getHours() + stepHour);
@@ -154,32 +182,98 @@ function computeTimeTicks(domainMin, domainMax, scale, boundaryStart, boundaryEn
154
182
  if (val >= domainMin) {
155
183
  ticks.push({
156
184
  pos: scale(val),
157
- label: `${MONTH_ABBR[m - 1]} ${d}`
185
+ label: `${MONTH_ABBR[m - 1]} ${d}`,
186
+ slot: ticks.length
158
187
  });
159
188
  }
160
189
  }
161
190
  }
162
191
  }
163
192
  }
193
+ if (strides.length === 0) strides = [1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48];
164
194
  const collisionThreshold = 40;
195
+ let startBoundary = null;
196
+ let endBoundary = null;
165
197
  if (boundaryStart !== void 0 && boundaryStartLabel) {
166
198
  const boundaryPos = scale(boundaryStart);
167
199
  ticks = ticks.filter(
168
200
  (t) => Math.abs(t.pos - boundaryPos) >= collisionThreshold
169
201
  );
170
- ticks.unshift({ pos: boundaryPos, label: boundaryStartLabel });
202
+ startBoundary = { pos: boundaryPos, label: boundaryStartLabel };
171
203
  }
172
204
  if (boundaryEnd !== void 0 && boundaryEndLabel) {
173
205
  const boundaryPos = scale(boundaryEnd);
174
206
  ticks = ticks.filter(
175
207
  (t) => Math.abs(t.pos - boundaryPos) >= collisionThreshold
176
208
  );
177
- ticks.push({ pos: boundaryPos, label: boundaryEndLabel });
209
+ endBoundary = { pos: boundaryPos, label: boundaryEndLabel };
178
210
  }
179
- return ticks;
211
+ return fitTickLabels(ticks, startBoundary, endBoundary, strides, labelFit);
212
+ }
213
+ function fitTickLabels(standard, startBoundary, endBoundary, strides, fit) {
214
+ const extent = (label) => fit.vertical ? fit.fontSize * 1.2 : measureText(label, fit.fontSize);
215
+ const span = (t) => {
216
+ const half = extent(t.label) / 2;
217
+ return { pos: t.pos, label: t.label, lo: t.pos - half, hi: t.pos + half };
218
+ };
219
+ const apart = (a, b) => a.hi + TICK_LABEL_GAP <= b.lo || b.hi + TICK_LABEL_GAP <= a.lo;
220
+ const fitsInOrder = (spans) => {
221
+ const sorted = [...spans].sort((a, b) => a.lo - b.lo);
222
+ for (let i = 1; i < sorted.length; i++) {
223
+ if (!apart(sorted[i - 1], sorted[i])) return false;
224
+ }
225
+ return true;
226
+ };
227
+ const start = startBoundary ? span(startBoundary) : null;
228
+ let end = endBoundary ? span(endBoundary) : null;
229
+ if (start && end && !apart(start, end)) end = null;
230
+ const pinned = [start, end].filter((p) => p !== null);
231
+ const withPinned = (kept) => [
232
+ ...start ? [start] : [],
233
+ ...kept,
234
+ ...end ? [end] : []
235
+ ];
236
+ const strip = (s) => ({
237
+ pos: s.pos,
238
+ label: s.label
239
+ });
240
+ for (const stride of strides) {
241
+ const kept = standard.filter((t) => t.slot % stride === 0).map(span).filter((s) => pinned.every((p) => apart(p, s)));
242
+ if (kept.length === 0 && pinned.length === 0) continue;
243
+ if (fitsInOrder(kept)) {
244
+ return withPinned(kept).map(strip);
245
+ }
246
+ }
247
+ return withPinned([]).map(strip);
248
+ }
249
+
250
+ // src/utils/label-column.ts
251
+ var LABEL_COLUMN_MAX_SHARE = 1 / 3;
252
+ var LABEL_COLUMN_END_PAD = 12;
253
+ function capLabelColumn(natural, chartWidth, floor) {
254
+ const cap = Math.max(floor, Math.floor(chartWidth * LABEL_COLUMN_MAX_SHARE));
255
+ const width = Math.min(natural, cap);
256
+ const capped = natural > cap;
257
+ return {
258
+ width,
259
+ fit: (text, labelX, fontSize, bold = false) => capped ? truncateText(
260
+ text,
261
+ fontSize,
262
+ Math.max(0, width - labelX - LABEL_COLUMN_END_PAD),
263
+ { bold }
264
+ ) : text
265
+ };
266
+ }
267
+ function showFullOnHover(hoverEl, labelEl, drawEl, full, shown, prefix = "") {
268
+ drawEl.text(prefix + shown);
269
+ if (shown === full) return;
270
+ labelEl.attr("data-full-label", full);
271
+ hoverEl.on("mouseenter.reveal", () => drawEl.text(prefix + full)).on("mouseleave.reveal", () => drawEl.text(prefix + shown));
180
272
  }
181
273
 
182
274
  export {
183
275
  MONTH_ABBR,
184
- computeTimeTicks
276
+ computeTimeTicks,
277
+ capLabelColumn,
278
+ showFullOnHover
185
279
  };
@@ -46,7 +46,7 @@ import {
46
46
  } from "./chunk-H6BT45SN.js";
47
47
  import {
48
48
  renderTimeline
49
- } from "./chunk-F7326NVM.js";
49
+ } from "./chunk-O5WUZGPV.js";
50
50
  import {
51
51
  ScaleContext
52
52
  } from "./chunk-24YPR5EH.js";
@@ -2123,7 +2123,7 @@ function ctxTagOverride(ctx) {
2123
2123
  async function exportEventLine(ctx) {
2124
2124
  const { content, theme, palette } = ctx;
2125
2125
  const { parseEventLine: parseEventLine2 } = await import("./parser-YG625J5H.js");
2126
- const { renderEventLineForExport } = await import("./renderer-J67LMOD5.js");
2126
+ const { renderEventLineForExport } = await import("./renderer-DVNMCYHZ.js");
2127
2127
  const effectivePalette = await resolveExportPalette(theme, palette);
2128
2128
  const parsed = parseEventLine2(content, effectivePalette);
2129
2129
  if (parsed.error || parsed.events.length === 0) return "";
@@ -2786,7 +2786,7 @@ async function exportGantt(ctx) {
2786
2786
  const { content, theme, palette, viewState, exportMode } = ctx;
2787
2787
  const { parseGantt: parseGantt2 } = await import("./parser-2NWH5JUS.js");
2788
2788
  const { calculateSchedule } = await import("./calculator-BNH3GSJE.js");
2789
- const { renderGantt } = await import("./renderer-NIE5IW24.js");
2789
+ const { renderGantt } = await import("./renderer-RUSFL7WU.js");
2790
2790
  const effectivePalette = await resolveExportPalette(theme, palette);
2791
2791
  const ganttParsed = parseGantt2(content, effectivePalette);
2792
2792
  const resolved = calculateSchedule(ganttParsed);
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  } from "./chunk-Z6DXWULM.js";
15
15
  import {
16
16
  render
17
- } from "./chunk-RJ2JW3FG.js";
17
+ } from "./chunk-Y2V5AEVG.js";
18
18
  import "./chunk-ZI6WC24V.js";
19
19
  import {
20
20
  renderErrorCard
@@ -44,8 +44,8 @@ import "./chunk-KMU65FYM.js";
44
44
  import "./chunk-EPDBWATV.js";
45
45
  import "./chunk-H6BT45SN.js";
46
46
  import "./chunk-2CW5ZTNH.js";
47
- import "./chunk-F7326NVM.js";
48
- import "./chunk-YRYKTYMQ.js";
47
+ import "./chunk-O5WUZGPV.js";
48
+ import "./chunk-VSOZZYLK.js";
49
49
  import "./chunk-24YPR5EH.js";
50
50
  import "./chunk-NMS5XSLX.js";
51
51
  import {
@@ -3,13 +3,13 @@ import {
3
3
  focusEventLine,
4
4
  renderEventLine,
5
5
  renderEventLineForExport
6
- } from "./chunk-62BP7AJU.js";
6
+ } from "./chunk-QQPNCXAO.js";
7
7
  import "./chunk-NPWVOFB3.js";
8
8
  import "./chunk-S7W75MIM.js";
9
9
  import "./chunk-R2K5L4UA.js";
10
10
  import "./chunk-Z6DXWULM.js";
11
- import "./chunk-F7326NVM.js";
12
- import "./chunk-YRYKTYMQ.js";
11
+ import "./chunk-O5WUZGPV.js";
12
+ import "./chunk-VSOZZYLK.js";
13
13
  import "./chunk-24YPR5EH.js";
14
14
  import "./chunk-NMS5XSLX.js";
15
15
  import "./chunk-XCKNFFKM.js";
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  buildTagLaneRowList,
3
3
  renderGantt
4
- } from "./chunk-LMP6Y7QO.js";
5
- import "./chunk-YRYKTYMQ.js";
4
+ } from "./chunk-4QLGGQNO.js";
5
+ import "./chunk-VSOZZYLK.js";
6
6
  import "./chunk-24YPR5EH.js";
7
7
  import "./chunk-NMS5XSLX.js";
8
8
  import "./chunk-XCKNFFKM.js";
@@ -1,12 +1,12 @@
1
1
  map Sister Ports of the Spanish Main
2
2
  flow-width Shared cargo runs
3
3
 
4
- poi Tortuga
5
- poi Port Royal
6
- poi Nassau
4
+ poi 20.05 -72.82 as Tortuga label: Tortuga
5
+ poi 17.94 -76.84 as royal label: Port Royal
6
+ poi 25.06 -77.35 as Nassau label: Nassau
7
7
  poi Havana
8
8
 
9
- Tortuga -ferry- Port Royal width: 18
10
- Port Royal ~cable~ Nassau
9
+ Tortuga -ferry- royal width: 18
10
+ royal ~cable~ Nassau
11
11
  Nassau -- Havana
12
12
  Havana -ships-> Tortuga width: 30
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diagrammo/dgmo",
3
- "version": "0.92.0",
3
+ "version": "0.92.1",
4
4
  "description": "DGMO diagram markup language — parser, renderer, and color system",
5
5
  "license": "MIT",
6
6
  "author": "Diagrammo LLC",
@@ -98,6 +98,8 @@
98
98
  "bench:bl-options": "vitest run --config vitest.bench.config.ts tests/bl-options.ts",
99
99
  "bench:bl-overlap": "vitest run --config vitest.bench.config.ts tests/bl-overlap-debug.ts",
100
100
  "bl-gallery": "vitest run --config vitest.bench.config.ts tests/bl-gallery.ts",
101
+ "stress:render": "TZ=UTC vitest run --config vitest.bench.config.ts tests/stress-render.ts",
102
+ "stress:sheet": "node scripts/stress-sheet.mjs",
101
103
  "test:watch": "TZ=UTC vitest",
102
104
  "test:auto": "pnpm build && TZ=UTC vitest run tests/auto.test.ts tests/safe-href.test.ts",
103
105
  "sri": "node scripts/sri.mjs",