@nowline/renderer 0.8.6 → 0.10.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.
Files changed (86) hide show
  1. package/dist/image-size.d.ts +9 -0
  2. package/dist/image-size.d.ts.map +1 -0
  3. package/dist/image-size.js +104 -0
  4. package/dist/image-size.js.map +1 -0
  5. package/dist/index.d.ts +4 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +4 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/logo.d.ts +24 -0
  10. package/dist/logo.d.ts.map +1 -0
  11. package/dist/logo.js +60 -0
  12. package/dist/logo.js.map +1 -0
  13. package/dist/stage.d.ts +78 -0
  14. package/dist/stage.d.ts.map +1 -0
  15. package/dist/stage.js +88 -0
  16. package/dist/stage.js.map +1 -0
  17. package/dist/svg/edges.d.ts +3 -0
  18. package/dist/svg/edges.d.ts.map +1 -0
  19. package/dist/svg/edges.js +144 -0
  20. package/dist/svg/edges.js.map +1 -0
  21. package/dist/svg/footnotes.d.ts +4 -0
  22. package/dist/svg/footnotes.d.ts.map +1 -0
  23. package/dist/svg/footnotes.js +81 -0
  24. package/dist/svg/footnotes.js.map +1 -0
  25. package/dist/svg/header.d.ts +6 -0
  26. package/dist/svg/header.d.ts.map +1 -0
  27. package/dist/svg/header.js +215 -0
  28. package/dist/svg/header.js.map +1 -0
  29. package/dist/svg/include-region.d.ts +4 -0
  30. package/dist/svg/include-region.d.ts.map +1 -0
  31. package/dist/svg/include-region.js +157 -0
  32. package/dist/svg/include-region.js.map +1 -0
  33. package/dist/svg/item.d.ts +36 -0
  34. package/dist/svg/item.d.ts.map +1 -0
  35. package/dist/svg/item.js +549 -0
  36. package/dist/svg/item.js.map +1 -0
  37. package/dist/svg/markers.d.ts +7 -0
  38. package/dist/svg/markers.d.ts.map +1 -0
  39. package/dist/svg/markers.js +151 -0
  40. package/dist/svg/markers.js.map +1 -0
  41. package/dist/svg/nowline.d.ts +4 -0
  42. package/dist/svg/nowline.d.ts.map +1 -0
  43. package/dist/svg/nowline.js +133 -0
  44. package/dist/svg/nowline.js.map +1 -0
  45. package/dist/svg/primitives.d.ts +7 -0
  46. package/dist/svg/primitives.d.ts.map +1 -0
  47. package/dist/svg/primitives.js +70 -0
  48. package/dist/svg/primitives.js.map +1 -0
  49. package/dist/svg/render.d.ts +13 -7
  50. package/dist/svg/render.d.ts.map +1 -1
  51. package/dist/svg/render.js +64 -1701
  52. package/dist/svg/render.js.map +1 -1
  53. package/dist/svg/sanitize.d.ts +10 -0
  54. package/dist/svg/sanitize.d.ts.map +1 -1
  55. package/dist/svg/sanitize.js +264 -16
  56. package/dist/svg/sanitize.js.map +1 -1
  57. package/dist/svg/swimlanes.d.ts +5 -0
  58. package/dist/svg/swimlanes.d.ts.map +1 -0
  59. package/dist/svg/swimlanes.js +228 -0
  60. package/dist/svg/swimlanes.js.map +1 -0
  61. package/dist/svg/timeline.d.ts +24 -0
  62. package/dist/svg/timeline.d.ts.map +1 -0
  63. package/dist/svg/timeline.js +284 -0
  64. package/dist/svg/timeline.js.map +1 -0
  65. package/dist/svg/waves.d.ts +62 -0
  66. package/dist/svg/waves.d.ts.map +1 -0
  67. package/dist/svg/waves.js +397 -0
  68. package/dist/svg/waves.js.map +1 -0
  69. package/package.json +6 -6
  70. package/src/image-size.ts +113 -0
  71. package/src/index.ts +10 -1
  72. package/src/logo.ts +82 -0
  73. package/src/stage.ts +191 -0
  74. package/src/svg/edges.ts +154 -0
  75. package/src/svg/footnotes.ts +115 -0
  76. package/src/svg/header.ts +285 -0
  77. package/src/svg/include-region.ts +201 -0
  78. package/src/svg/item.ts +672 -0
  79. package/src/svg/markers.ts +178 -0
  80. package/src/svg/nowline.ts +152 -0
  81. package/src/svg/primitives.ts +86 -0
  82. package/src/svg/render.ts +94 -2110
  83. package/src/svg/sanitize.ts +286 -16
  84. package/src/svg/swimlanes.ts +317 -0
  85. package/src/svg/timeline.ts +343 -0
  86. package/src/svg/waves.ts +502 -0
@@ -0,0 +1,228 @@
1
+ // The swimlane layers: the lane background tint and tri-state utilization
2
+ // underline, the lane frame tab and badges, and the group and parallel
3
+ // containers that wrap a lane's items.
4
+ import { CONTAINER_BODY_FILL_OPACITY, CONTAINER_BRACKET_LINE_OFFSET_PX, CONTAINER_HEADER_TITLE_BASELINE_PX, CONTAINER_HEADER_TITLE_FONT_SIZE_PX, CONTAINER_MAX_FILLED_DEPTH, CONTAINER_NESTED_BODY_FILL_OPACITY, containerHeaderTitleX, FRAME_TAB_HEIGHT_PX, FRAME_TAB_LABEL_BASELINE_OFFSET_PX, FRAME_TAB_OWNER_GLYPH_PX, } from '@nowline/layout';
5
+ import { CAPACITY_ICON_SVG } from './icons.js';
6
+ import { hasAfterPin, renderCapacitySuffix, renderInlineDatePins, renderItem } from './item.js';
7
+ import { fontAttrs } from './primitives.js';
8
+ import { escAttr, num, tag, textTag } from './xml.js';
9
+ /**
10
+ * A group or parallel, drawn above and around its content
11
+ * (specs/true-width-layout.md §3.3): a body shaded with the container's `bg`
12
+ * spanning exactly its dates, a header bracket over those dates (solid for
13
+ * a group, dashed for a parallel by default; `bracket:` styles it), the
14
+ * title at the container's start with any inline-date glyphs beside it,
15
+ * then the children. A wave-only group draws only its children.
16
+ *
17
+ * `depth` is the container's nesting depth inside its lane, 1 for a
18
+ * top-level container. A nested body takes a lighter tint, and bodies deeper
19
+ * than `CONTAINER_MAX_FILLED_DEPTH` are outlined instead of filled, so tints
20
+ * never compound three deep (§8 O4).
21
+ */
22
+ function renderContainer(c, depth, options, idPrefix, palette, fonts) {
23
+ const parts = [];
24
+ const isDrawn = !(c.kind === 'group' && c.waveOnly);
25
+ if (isDrawn) {
26
+ const bodyY = c.box.y + c.headerHeight;
27
+ const bodyHeight = c.box.height - c.headerHeight;
28
+ if (c.style.bg !== 'none') {
29
+ const isFilled = depth <= CONTAINER_MAX_FILLED_DEPTH;
30
+ parts.push(tag('rect', {
31
+ x: num(c.box.x),
32
+ y: num(bodyY),
33
+ width: num(c.box.width),
34
+ height: num(bodyHeight),
35
+ fill: isFilled ? c.style.bg : 'none',
36
+ 'fill-opacity': isFilled
37
+ ? depth === 1
38
+ ? CONTAINER_BODY_FILL_OPACITY
39
+ : CONTAINER_NESTED_BODY_FILL_OPACITY
40
+ : null,
41
+ stroke: isFilled ? null : c.style.bg,
42
+ 'stroke-opacity': isFilled ? null : 0.6,
43
+ 'stroke-width': isFilled ? null : 1,
44
+ }));
45
+ }
46
+ // The title and the bracket share one ink, the header's: a group's
47
+ // `fg`, a parallel's bracket stroke. The title sits on the lane band,
48
+ // never on the body, so a `text:` colour chosen to read on `bg`
49
+ // (white on a filled chiclet, before the header) would vanish.
50
+ const ink = c.kind === 'group' ? c.style.fg : palette.parallel.bracketStroke;
51
+ if (c.headerHeight > 0 && c.style.bracket !== 'none') {
52
+ const lineY = c.box.y + CONTAINER_BRACKET_LINE_OFFSET_PX;
53
+ const x0 = c.box.x;
54
+ const x1 = c.box.x + c.box.width;
55
+ parts.push(tag('path', {
56
+ d: `M${num(x0)} ${num(bodyY)} V${num(lineY)} H${num(x1)} V${num(bodyY)}`,
57
+ fill: 'none',
58
+ stroke: ink,
59
+ 'stroke-width': 1.2,
60
+ 'stroke-dasharray': c.style.bracket === 'dashed' ? '3 2' : null,
61
+ }));
62
+ }
63
+ if (c.title && c.headerHeight > 0) {
64
+ parts.push(textTag({
65
+ // Past the `after:` glyph when there is one; layout
66
+ // clamps the `before:` glyph past this text.
67
+ x: num(containerHeaderTitleX(c.box.x, hasAfterPin(c.inlineDatePins))),
68
+ y: num(c.box.y + CONTAINER_HEADER_TITLE_BASELINE_PX),
69
+ ...fontAttrs(c.style, fonts, CONTAINER_HEADER_TITLE_FONT_SIZE_PX),
70
+ 'font-weight': 600,
71
+ fill: ink,
72
+ }, c.title));
73
+ }
74
+ // The glyphs sit in the header's title row, never on a child, so
75
+ // painting them before the children is safe.
76
+ parts.push(renderInlineDatePins(c.inlineDatePins, c.style.fg));
77
+ }
78
+ for (const child of c.children) {
79
+ parts.push(renderTrackChild(child, options, idPrefix, palette, fonts, depth));
80
+ }
81
+ return tag('g', { 'data-layer': c.kind, 'data-id': c.id ?? null }, parts.join(''));
82
+ }
83
+ function renderTrackChild(c, options, idPrefix, palette, fonts, parentDepth = 0) {
84
+ if (c.kind === 'item')
85
+ return renderItem(c, options, idPrefix, palette, fonts);
86
+ return renderContainer(c, parentDepth + 1, options, idPrefix, palette, fonts);
87
+ }
88
+ // Renders only the swimlane's background tint rect. Emitted before the
89
+ // chart-body grid lines so those lines visibly span the full chart width.
90
+ // The frame tab (chiclet at top-left) and item content are emitted later,
91
+ // in renderSwimlaneContent, so they appear on top of the grid.
92
+ // Tri-state lane utilization underline. Painted along the bottom edge of
93
+ // the band when the lane has `capacity:` AND at least one item contributing
94
+ // load AND has not opted out of every color band via `utilization-*-at:none`.
95
+ // Geometry per specs/rendering.md § Lane utilization underline:
96
+ // - height: 2px (matches the milestone-line stroke weight)
97
+ // - y: flush with the bottom edge of the band, fully inside it
98
+ // - x: aligned to the segment boundaries the layout already pre-coalesced
99
+ // One <rect> per coalesced segment; classification → palette token mapping
100
+ // is the only renderer-side decision.
101
+ const LANE_UTILIZATION_HEIGHT_PX = 2;
102
+ function utilizationColor(classification, palette) {
103
+ switch (classification) {
104
+ case 'green':
105
+ return palette.swimlane.utilizationOk;
106
+ case 'yellow':
107
+ return palette.swimlane.utilizationWarn;
108
+ case 'red':
109
+ return palette.swimlane.utilizationOver;
110
+ }
111
+ }
112
+ function renderLaneUtilization(s, palette) {
113
+ if (!s.utilization || s.utilization.segments.length === 0)
114
+ return '';
115
+ const y = s.box.y + s.box.height - LANE_UTILIZATION_HEIGHT_PX;
116
+ const rects = s.utilization.segments.map((seg) => {
117
+ const width = seg.endX - seg.startX;
118
+ if (width <= 0)
119
+ return '';
120
+ return tag('rect', {
121
+ x: num(seg.startX),
122
+ y: num(y),
123
+ width: num(width),
124
+ height: LANE_UTILIZATION_HEIGHT_PX,
125
+ fill: utilizationColor(seg.classification, palette),
126
+ 'data-utilization': seg.classification,
127
+ 'data-load': num(seg.load),
128
+ });
129
+ });
130
+ return tag('g', {
131
+ 'data-layer': 'lane-utilization',
132
+ 'data-id': s.id ?? null,
133
+ }, rects.join(''));
134
+ }
135
+ export function renderSwimlaneBg(s, palette) {
136
+ const tint = s.bandIndex % 2 === 0 ? palette.swimlane.rowTintEven : palette.swimlane.rowTintOdd;
137
+ const borderColor = palette.swimlane.border;
138
+ return tag('g', { 'data-layer': 'swimlane-bg', 'data-id': s.id ?? null }, tag('rect', {
139
+ x: num(s.box.x),
140
+ y: num(s.box.y),
141
+ width: num(s.box.width),
142
+ height: num(s.box.height),
143
+ fill: tint,
144
+ stroke: borderColor,
145
+ 'stroke-width': 1,
146
+ }));
147
+ }
148
+ export function renderSwimlane(s, options, idPrefix, palette, fonts) {
149
+ const tabFill = palette.swimlane.tabFill;
150
+ const tabStroke = palette.swimlane.tabStroke;
151
+ const tabText = palette.swimlane.tabText;
152
+ const ownerText = palette.swimlane.ownerText;
153
+ const footnoteColor = palette.swimlane.footnoteIndicator;
154
+ const parts = [];
155
+ // Frame-tab chiclet at the top-left of the band, auto-sized to fit
156
+ // title, owner, capacity badge and footnote indicators. Layout sized it
157
+ // (`PositionedSwimlane.tab`, the same rectangle it reserved for the
158
+ // row packer) and placed each element inside it; this only draws.
159
+ const tab = s.tab;
160
+ if (tab) {
161
+ const LANE_BADGE_FONT_SIZE_PX = 10;
162
+ const footnoteIndicatorText = s.footnoteIndicators.length > 0 ? s.footnoteIndicators.join(',') : '';
163
+ const tabH = FRAME_TAB_HEIGHT_PX;
164
+ const tabY = s.box.y + 10;
165
+ const labelY = tabY + FRAME_TAB_LABEL_BASELINE_OFFSET_PX;
166
+ parts.push(tag('rect', {
167
+ x: num(tab.tabX),
168
+ y: num(tabY),
169
+ width: num(tab.tabW),
170
+ height: num(tabH),
171
+ rx: 4,
172
+ ry: 4,
173
+ fill: tabFill,
174
+ stroke: tabStroke,
175
+ 'stroke-width': 1,
176
+ }));
177
+ parts.push(textTag({
178
+ x: num(tab.titleX),
179
+ y: num(labelY),
180
+ 'font-family': fonts[s.style.font],
181
+ 'font-size': 12,
182
+ 'font-weight': 600,
183
+ fill: tabText,
184
+ }, s.title));
185
+ if (s.owner) {
186
+ // A person or people glyph leads the owner name, lifted onto
187
+ // the text's x-height like the capacity badge's glyph.
188
+ const glyph = CAPACITY_ICON_SVG[s.ownerKind === 'person' ? 'person' : 'people'];
189
+ const glyphSize = FRAME_TAB_OWNER_GLYPH_PX;
190
+ parts.push(`<svg x="${num(tab.ownerX)}" y="${num(labelY - glyphSize * 0.85)}" width="${num(glyphSize)}" height="${num(glyphSize)}" viewBox="${glyph.viewBox}" style="color:${escAttr(ownerText)}" aria-hidden="true">${glyph.body}</svg>`);
191
+ parts.push(textTag({
192
+ x: num(tab.ownerTextX),
193
+ y: num(labelY),
194
+ 'font-family': fonts[s.style.font],
195
+ 'font-size': 10,
196
+ fill: ownerText,
197
+ }, s.owner));
198
+ }
199
+ if (s.capacity) {
200
+ // Re-uses the same `renderCapacitySuffix` helper that paints
201
+ // item-level suffixes (m6) so multiplier / built-in SVG /
202
+ // inline literal / dereferenced-custom-glyph paths stay
203
+ // consistent across both contexts.
204
+ parts.push(renderCapacitySuffix(s.capacity, tab.badgeX, labelY, LANE_BADGE_FONT_SIZE_PX, fonts[s.style.font], ownerText));
205
+ }
206
+ if (footnoteIndicatorText) {
207
+ parts.push(textTag({
208
+ x: num(tab.footnoteRightX),
209
+ y: num(tabY + 14),
210
+ 'font-family': fonts.sans,
211
+ 'font-size': LANE_BADGE_FONT_SIZE_PX,
212
+ 'font-weight': 700,
213
+ fill: footnoteColor,
214
+ 'text-anchor': 'end',
215
+ }, footnoteIndicatorText));
216
+ }
217
+ }
218
+ for (const c of s.children) {
219
+ parts.push(renderTrackChild(c, options, idPrefix, palette, fonts));
220
+ }
221
+ // m13: tri-state utilization underline along the band's bottom edge.
222
+ // Painted after items so it overlays any item that happens to extend
223
+ // to the band's bottom; under cut-lines / now-line which run as
224
+ // separate top-level passes.
225
+ parts.push(renderLaneUtilization(s, palette));
226
+ return tag('g', { 'data-layer': 'swimlane', 'data-id': s.id ?? null }, parts.join(''));
227
+ }
228
+ //# sourceMappingURL=swimlanes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"swimlanes.js","sourceRoot":"","sources":["../../src/svg/swimlanes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { BoundingBox, PositionedTimelineScale, Theme } from '@nowline/layout';
2
+ import type { FontFamilies } from './render.js';
3
+ export declare function renderGridLines(t: PositionedTimelineScale, swimlaneTopY: number, palette: Theme): string;
4
+ /**
5
+ * Non-working days under `show` (specs/working-calendar.md §7.3): a faint
6
+ * band over each run layout flagged `band`, on the seam / minor-grid span
7
+ * (from the top of the chart body to the timeline bottom), clipped to
8
+ * `clip` when given (an include region's painted rect). Empty under `hide`,
9
+ * where no run has `band`, so that view emits no bytes.
10
+ */
11
+ export declare function renderNonWorkingBands(t: PositionedTimelineScale, chartTopY: number, palette: Theme, clip?: BoundingBox): string;
12
+ /**
13
+ * Labels of the titled non-working runs and the working exceptions
14
+ * (specs/working-calendar.md §7.4): each one's text in the marker row,
15
+ * placed by the layout like an anchor label. Under `hide` a titled run
16
+ * also draws a dotted cut line at the seam from the label row's centre to
17
+ * the timeline bottom, since the zero-width seam has no band to name. A
18
+ * working exception is text only in either view: a working day removes no
19
+ * time, so it has no cut. Empty when nothing has a label, so a roadmap
20
+ * without one emits no bytes.
21
+ */
22
+ export declare function renderNonWorkingLabels(t: PositionedTimelineScale, palette: Theme, fonts: FontFamilies): string;
23
+ export declare function renderTimeline(t: PositionedTimelineScale, palette: Theme, fonts: FontFamilies): string;
24
+ //# sourceMappingURL=timeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeline.d.ts","sourceRoot":"","sources":["../../src/svg/timeline.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,uBAAuB,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAOnF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAWhD,wBAAgB,eAAe,CAC3B,CAAC,EAAE,uBAAuB,EAC1B,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,KAAK,GACf,MAAM,CAkFR;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACjC,CAAC,EAAE,uBAAuB,EAC1B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,KAAK,EACd,IAAI,CAAC,EAAE,WAAW,GACnB,MAAM,CA0BR;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAClC,CAAC,EAAE,uBAAuB,EAC1B,OAAO,EAAE,KAAK,EACd,KAAK,EAAE,YAAY,GACpB,MAAM,CAkCR;AAyBD,wBAAgB,cAAc,CAC1B,CAAC,EAAE,uBAAuB,EAC1B,OAAO,EAAE,KAAK,EACd,KAAK,EAAE,YAAY,GACpB,MAAM,CAoHR"}
@@ -0,0 +1,284 @@
1
+ // The timeline layer: the tick scale across the top, the vertical grid lines
2
+ // behind the lanes, and the shaded non-working bands with their labels.
3
+ import { NON_WORKING_FILL_OPACITY, TIMELINE_TICK_LABEL_FONT_SIZE_PX, timelineTickLabelBaselineOffsetPx, } from '@nowline/layout';
4
+ import { intersectBox } from './primitives.js';
5
+ import { num, tag, textTag } from './xml.js';
6
+ // Renders the chart-body vertical grid lines (major dotted at every
7
+ // labeled tick, plus optional faint minor lines at every tick when
8
+ // `minorGrid` is set). Emitted as its own layer drawn AFTER the
9
+ // swimlane backgrounds so the lines actually span the chart body
10
+ // instead of being occluded — without this layer the lines would only
11
+ // be visible inside the timeline header strip. Grid lines stay BEHIND
12
+ // items, edges, anchor/milestone cuts, and the now-line so item bars
13
+ // sit cleanly on top of the ruled-paper backdrop.
14
+ export function renderGridLines(t, swimlaneTopY, palette) {
15
+ const gridColor = palette.timeline.gridLine;
16
+ const minorGridColor = palette.timeline.minorGridLine;
17
+ // Major lines thread through the FULL timeline strip — they start
18
+ // at the top of the top date-label panel (when present) and run
19
+ // all the way through the bottom date-label panel (when one is
20
+ // mirrored at the chart bottom via `timeline-position:both` or
21
+ // `timeline-position:bottom`). This ties date labels at both ends
22
+ // to their column boundaries.
23
+ //
24
+ // Minor lines stay quieter: they start at the TOP OF THE TOPMOST
25
+ // SWIMLANE (i.e. below the top date panel AND below the marker
26
+ // row, so they don't streak through anchor/milestone diamonds in
27
+ // the header) and stop ABOVE the bottom date panel.
28
+ //
29
+ // Anchor diamonds, milestone markers, and date label text are
30
+ // rendered later in the orchestrator so they sit on top of any
31
+ // crossing line.
32
+ const bottomTickPanelHeight = t.bottomTickPanelHeight ?? 0;
33
+ const hasBottomTickPanel = t.bottomTickPanelY !== undefined && bottomTickPanelHeight > 0;
34
+ const majorTopY = t.tickPanelY;
35
+ const majorBottomY = hasBottomTickPanel
36
+ ? t.bottomTickPanelY + bottomTickPanelHeight
37
+ : t.box.y + t.box.height;
38
+ // Use the topmost swimlane's top edge directly — `markerRow.height`
39
+ // alone misses the small gap (`timelineHeightBudget` slack) between
40
+ // the marker row and the swimlane area, which would leave the minor
41
+ // lines short and floating in dead space above the swimlane.
42
+ const minorTopY = swimlaneTopY;
43
+ const minorBottomY = t.box.y + t.box.height;
44
+ const parts = [];
45
+ for (const tick of t.ticks) {
46
+ if (tick.major) {
47
+ // Solid major line at every labeled tick — the dominant
48
+ // column boundary, drawn in the stronger gridLine color.
49
+ parts.push(tag('line', {
50
+ x1: num(tick.x),
51
+ y1: num(majorTopY),
52
+ x2: num(tick.x),
53
+ y2: num(majorBottomY),
54
+ stroke: gridColor,
55
+ 'stroke-width': 1,
56
+ }));
57
+ }
58
+ else if (t.minorGrid) {
59
+ // Solid faint minor line at every non-major tick boundary.
60
+ // Hierarchy is established by color (lighter than the major)
61
+ // rather than by texture. Skip the very last tick since it
62
+ // has no following column — a line at the chart's right edge
63
+ // just doubles up the chart border.
64
+ if (tick.labelX === undefined)
65
+ continue;
66
+ parts.push(tag('line', {
67
+ x1: num(tick.x),
68
+ y1: num(minorTopY),
69
+ x2: num(tick.x),
70
+ y2: num(minorBottomY),
71
+ stroke: minorGridColor,
72
+ 'stroke-width': 1,
73
+ }));
74
+ }
75
+ }
76
+ // Seams where hidden non-working days collapse and no grid line
77
+ // already marks the join (specs/working-calendar.md §7.2): faint and
78
+ // dotted, on the minor line's span. Layout marks which runs get one.
79
+ for (const run of t.nonWorking ?? []) {
80
+ if (!run.seam)
81
+ continue;
82
+ parts.push(tag('line', {
83
+ x1: num(run.x),
84
+ y1: num(minorTopY),
85
+ x2: num(run.x),
86
+ y2: num(minorBottomY),
87
+ stroke: palette.timeline.nonWorkingSeam,
88
+ 'stroke-width': 1,
89
+ 'stroke-dasharray': '1 3',
90
+ }));
91
+ }
92
+ return tag('g', { 'data-layer': 'grid' }, parts.join(''));
93
+ }
94
+ /**
95
+ * Non-working days under `show` (specs/working-calendar.md §7.3): a faint
96
+ * band over each run layout flagged `band`, on the seam / minor-grid span
97
+ * (from the top of the chart body to the timeline bottom), clipped to
98
+ * `clip` when given (an include region's painted rect). Empty under `hide`,
99
+ * where no run has `band`, so that view emits no bytes.
100
+ */
101
+ export function renderNonWorkingBands(t, chartTopY, palette, clip) {
102
+ const bottomY = t.box.y + t.box.height;
103
+ const parts = [];
104
+ for (const run of t.nonWorking ?? []) {
105
+ if (!run.band)
106
+ continue;
107
+ const span = {
108
+ x: run.x,
109
+ y: chartTopY,
110
+ width: run.width,
111
+ height: bottomY - chartTopY,
112
+ };
113
+ const box = clip ? intersectBox(span, clip) : span;
114
+ if (!box || box.width <= 0 || box.height <= 0)
115
+ continue;
116
+ parts.push(tag('rect', {
117
+ x: num(box.x),
118
+ y: num(box.y),
119
+ width: num(box.width),
120
+ height: num(box.height),
121
+ fill: palette.timeline.nonWorkingFill,
122
+ 'fill-opacity': NON_WORKING_FILL_OPACITY,
123
+ }));
124
+ }
125
+ if (parts.length === 0)
126
+ return '';
127
+ return tag('g', { 'data-layer': 'non-working' }, parts.join(''));
128
+ }
129
+ /**
130
+ * Labels of the titled non-working runs and the working exceptions
131
+ * (specs/working-calendar.md §7.4): each one's text in the marker row,
132
+ * placed by the layout like an anchor label. Under `hide` a titled run
133
+ * also draws a dotted cut line at the seam from the label row's centre to
134
+ * the timeline bottom, since the zero-width seam has no band to name. A
135
+ * working exception is text only in either view: a working day removes no
136
+ * time, so it has no cut. Empty when nothing has a label, so a roadmap
137
+ * without one emits no bytes.
138
+ */
139
+ export function renderNonWorkingLabels(t, palette, fonts) {
140
+ const bottomY = t.box.y + t.box.height;
141
+ const parts = [];
142
+ for (const run of t.nonWorking ?? []) {
143
+ if (run.label === undefined || !run.labelBox)
144
+ continue;
145
+ if (run.cut) {
146
+ parts.push(tag('line', {
147
+ x1: num(run.x),
148
+ y1: num(run.labelBox.y + 4),
149
+ x2: num(run.x),
150
+ y2: num(bottomY),
151
+ stroke: palette.timeline.nonWorkingSeam,
152
+ 'stroke-width': 1,
153
+ 'stroke-dasharray': '1 3',
154
+ }));
155
+ }
156
+ parts.push(calendarLabelText(run.label, run.labelBox, run.labelSide, palette, fonts));
157
+ }
158
+ for (const exception of t.workingExceptions ?? []) {
159
+ if (!exception.labelBox)
160
+ continue;
161
+ parts.push(calendarLabelText(exception.label, exception.labelBox, exception.labelSide, palette, fonts));
162
+ }
163
+ if (parts.length === 0)
164
+ return '';
165
+ return tag('g', { 'data-layer': 'non-working-label' }, parts.join(''));
166
+ }
167
+ /** One calendar label's text in the marker row. */
168
+ function calendarLabelText(label, box, side, palette, fonts) {
169
+ // Left-side labels end-anchor at the box's right edge, as anchor
170
+ // labels do: the box width is a pessimistic estimate.
171
+ const isLeft = side === 'left';
172
+ const centerY = box.y + 4;
173
+ const labelAttrs = {
174
+ x: num(isLeft ? box.x + box.width : box.x),
175
+ y: num(centerY + 4),
176
+ 'font-family': fonts.sans,
177
+ 'font-size': 10,
178
+ fill: palette.timeline.labelText,
179
+ };
180
+ if (isLeft)
181
+ labelAttrs['text-anchor'] = 'end';
182
+ return textTag(labelAttrs, label);
183
+ }
184
+ export function renderTimeline(t, palette, fonts) {
185
+ const panelFill = palette.timeline.panelFill;
186
+ const borderColor = palette.timeline.border;
187
+ const labelColor = palette.timeline.labelText;
188
+ const parts = [];
189
+ // Header layout from top: now-pill row → tick-label panel → (wave
190
+ // strip) → marker row.
191
+ // The pill row owns its space (no panel rect); the now-line crosses it
192
+ // visually. Marker row is omitted entirely when empty. The top tick
193
+ // panel is also omitted when the roadmap requested
194
+ // `timeline-position:bottom` (height 0). The optional bottom tick
195
+ // panel mirrors the top panel directly above the footnote area.
196
+ const tickPanelY = t.tickPanelY;
197
+ const tickPanelHeight = t.tickPanelHeight;
198
+ const hasTopTickPanel = tickPanelHeight > 0;
199
+ const hasMarkerRow = t.markerRow.height > 0;
200
+ // The wave strip, when there is one, sits between the tick panel and
201
+ // the marker rows (specs/waves.md §8.8); its panel is the wave-strip
202
+ // layer's.
203
+ const markerRowY = t.waveStrip
204
+ ? t.waveStrip.y + t.waveStrip.height
205
+ : tickPanelY + tickPanelHeight;
206
+ const markerRowHeight = t.markerRow.height;
207
+ const bottomTickPanelY = t.bottomTickPanelY;
208
+ const bottomTickPanelHeight = t.bottomTickPanelHeight ?? 0;
209
+ const hasBottomTickPanel = bottomTickPanelY !== undefined && bottomTickPanelHeight > 0;
210
+ if (hasTopTickPanel) {
211
+ parts.push(tag('rect', {
212
+ x: num(t.box.x),
213
+ y: num(tickPanelY),
214
+ width: num(t.box.width),
215
+ height: num(tickPanelHeight),
216
+ rx: 4,
217
+ ry: 4,
218
+ fill: panelFill,
219
+ stroke: borderColor,
220
+ 'stroke-width': 1,
221
+ }));
222
+ }
223
+ if (hasMarkerRow) {
224
+ parts.push(tag('rect', {
225
+ x: num(t.box.x),
226
+ y: num(markerRowY),
227
+ width: num(t.box.width),
228
+ height: num(markerRowHeight),
229
+ rx: 4,
230
+ ry: 4,
231
+ fill: panelFill,
232
+ stroke: borderColor,
233
+ 'stroke-width': 1,
234
+ }));
235
+ }
236
+ if (hasBottomTickPanel) {
237
+ parts.push(tag('rect', {
238
+ x: num(t.box.x),
239
+ y: num(bottomTickPanelY),
240
+ width: num(t.box.width),
241
+ height: num(bottomTickPanelHeight),
242
+ rx: 4,
243
+ ry: 4,
244
+ fill: panelFill,
245
+ stroke: borderColor,
246
+ 'stroke-width': 1,
247
+ }));
248
+ }
249
+ // Header-only labels — the chart-body grid lines themselves are
250
+ // emitted by `renderGridLines` after the swimlane backgrounds so
251
+ // they actually span the chart body rather than being occluded.
252
+ for (const tick of t.ticks) {
253
+ if (!tick.major)
254
+ continue;
255
+ if (!tick.label || tick.labelX === undefined)
256
+ continue;
257
+ // Label sits at the COLUMN CENTER (tick.labelX), not at the
258
+ // tick boundary. The last tick has no following column → no
259
+ // label.
260
+ if (hasTopTickPanel) {
261
+ parts.push(textTag({
262
+ x: num(tick.labelX),
263
+ y: num(tickPanelY + timelineTickLabelBaselineOffsetPx(tickPanelHeight)),
264
+ 'font-family': fonts.sans,
265
+ 'font-size': TIMELINE_TICK_LABEL_FONT_SIZE_PX,
266
+ fill: labelColor,
267
+ 'text-anchor': 'middle',
268
+ }, tick.label));
269
+ }
270
+ if (hasBottomTickPanel) {
271
+ parts.push(textTag({
272
+ x: num(tick.labelX),
273
+ y: num(bottomTickPanelY +
274
+ timelineTickLabelBaselineOffsetPx(bottomTickPanelHeight)),
275
+ 'font-family': fonts.sans,
276
+ 'font-size': TIMELINE_TICK_LABEL_FONT_SIZE_PX,
277
+ fill: labelColor,
278
+ 'text-anchor': 'middle',
279
+ }, tick.label));
280
+ }
281
+ }
282
+ return tag('g', { 'data-layer': 'timeline' }, parts.join(''));
283
+ }
284
+ //# sourceMappingURL=timeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeline.js","sourceRoot":"","sources":["../../src/svg/timeline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ import type { BoundingBox, PositionedRoadmap, PositionedWave, PositionedWaveBoundary, PositionedWaveCrossing, PositionedWaveLegend, ResolvedStyle, Theme, WaveHatchKind } from '@nowline/layout';
2
+ import type { FontFamilies } from './render.js';
3
+ /**
4
+ * A hatch `<pattern>`: a `WAVE_HATCH_TILE_PX` tile rotated 45°, one
5
+ * `WAVE_HATCH_STROKE_PX` stroke down its middle (so the tile never clips
6
+ * it) at `WAVE_HATCH_OPACITY`. The id carries the SVG's prefix, like the
7
+ * shadow filters, so two SVGs on one page never share it.
8
+ *
9
+ * The line carries `opacity`, not `stroke-opacity`. Browsers and resvg draw
10
+ * the two the same (one stroke, nothing overlaps), but svg-to-pdfkit turns
11
+ * `stroke-opacity` into a stroke-only `CA` inside the pattern cell, which
12
+ * poppler ignores, so the PDF hatch drew opaque. With `opacity` it sets the
13
+ * fill alpha `ca` as well, which poppler and cairo both honour.
14
+ */
15
+ export declare function waveHatchPatternDef(idPrefix: string, kind: WaveHatchKind, palette: Theme): string;
16
+ /**
17
+ * The hatch patterns the SVG uses, in a fixed order: one per kind that a
18
+ * background bar (main lanes or isolated regions) or the legend's hatch
19
+ * swatch needs, as layout resolved them (`waveHatch`, `hatch`). Empty
20
+ * without waves.
21
+ */
22
+ export declare function usedWaveHatchKinds(model: PositionedRoadmap): WaveHatchKind[];
23
+ /**
24
+ * The hatch overlay over a background bar: the bar's rect inset by half its
25
+ * 1 px stroke, with the same corner radius, so it never covers the stroke.
26
+ * `kind` is the bar's hatch kind as layout resolved it.
27
+ */
28
+ export declare function hatchOverlay(box: BoundingBox, style: ResolvedStyle, kind: WaveHatchKind, idPrefix: string): string;
29
+ /**
30
+ * `wave-strip` (§9.1): the backing panel and one cell per non-empty wave,
31
+ * each with its tooltip. Drawn right after the timeline panels, so the
32
+ * major grid lines cross the cells as they cross the tick panel.
33
+ */
34
+ export declare function renderWaveStrip(model: PositionedRoadmap, palette: Theme): string;
35
+ /**
36
+ * `wave-bg` (§9.4): a column tint over each styled wave's `columnBox`,
37
+ * clipped to `clip` when given (an include region's painted rect).
38
+ */
39
+ export declare function renderWaveTints(waves: PositionedWave[], clip?: BoundingBox): string;
40
+ /**
41
+ * `wave-boundary` (§9.2): the 2 px boundary lines, from the strip top to the
42
+ * last lane. With `clip`, only the lines strictly inside it, cut to its
43
+ * vertical extent.
44
+ */
45
+ export declare function renderWaveBoundaries(boundaries: PositionedWaveBoundary[] | undefined, clip?: BoundingBox): string;
46
+ /**
47
+ * `wave-labels` (§9.1): strip labels on halos in their cell's fill,
48
+ * right-aligned footnote superscripts, empty-wave diamonds, gap floor
49
+ * labels, and the all-empty placeholder. Drawn after the boundaries.
50
+ */
51
+ export declare function renderWaveLabels(model: PositionedRoadmap, palette: Theme, fonts: FontFamilies): string;
52
+ /**
53
+ * `wave-cross` (§9.3): a 1 px dashed boundary over each background bar a
54
+ * boundary runs through, showing the barrier does not hold that work.
55
+ */
56
+ export declare function renderWaveCrossings(crossings: PositionedWaveCrossing[] | undefined): string;
57
+ /**
58
+ * `wave-legend` (§9.5): the hatched bar swatch for background work and its
59
+ * text, as the layout placed them.
60
+ */
61
+ export declare function renderWaveLegend(legend: PositionedWaveLegend | undefined, idPrefix: string, palette: Theme, fonts: FontFamilies): string;
62
+ //# sourceMappingURL=waves.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"waves.d.ts","sourceRoot":"","sources":["../../src/svg/waves.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACR,WAAW,EACX,iBAAiB,EAIjB,cAAc,EACd,sBAAsB,EACtB,sBAAsB,EACtB,oBAAoB,EACpB,aAAa,EACb,KAAK,EACL,aAAa,EAChB,MAAM,iBAAiB,CAAC;AAgBzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAmBhD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,KAAK,GAAG,MAAM,CAqBjG;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,iBAAiB,GAAG,aAAa,EAAE,CAwB5E;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CACxB,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,aAAa,EACpB,IAAI,EAAE,aAAa,EACnB,QAAQ,EAAE,MAAM,GACjB,MAAM,CAYR;AAyED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,iBAAiB,EAAE,OAAO,EAAE,KAAK,GAAG,MAAM,CA+BhF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,EAAE,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,MAAM,CAmBnF;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAChC,UAAU,EAAE,sBAAsB,EAAE,GAAG,SAAS,EAChD,IAAI,CAAC,EAAE,WAAW,GACnB,MAAM,CAyBR;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC5B,KAAK,EAAE,iBAAiB,EACxB,OAAO,EAAE,KAAK,EACd,KAAK,EAAE,YAAY,GACpB,MAAM,CA6FR;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,sBAAsB,EAAE,GAAG,SAAS,GAAG,MAAM,CAc3F;AAQD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC5B,MAAM,EAAE,oBAAoB,GAAG,SAAS,EACxC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,KAAK,EACd,KAAK,EAAE,YAAY,GACpB,MAAM,CAiDR"}