@nowline/renderer 0.8.6 → 0.9.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/image-size.d.ts +9 -0
- package/dist/image-size.d.ts.map +1 -0
- package/dist/image-size.js +104 -0
- package/dist/image-size.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/logo.d.ts +24 -0
- package/dist/logo.d.ts.map +1 -0
- package/dist/logo.js +60 -0
- package/dist/logo.js.map +1 -0
- package/dist/stage.d.ts +78 -0
- package/dist/stage.d.ts.map +1 -0
- package/dist/stage.js +88 -0
- package/dist/stage.js.map +1 -0
- package/dist/svg/edges.d.ts +3 -0
- package/dist/svg/edges.d.ts.map +1 -0
- package/dist/svg/edges.js +144 -0
- package/dist/svg/edges.js.map +1 -0
- package/dist/svg/footnotes.d.ts +4 -0
- package/dist/svg/footnotes.d.ts.map +1 -0
- package/dist/svg/footnotes.js +81 -0
- package/dist/svg/footnotes.js.map +1 -0
- package/dist/svg/header.d.ts +6 -0
- package/dist/svg/header.d.ts.map +1 -0
- package/dist/svg/header.js +215 -0
- package/dist/svg/header.js.map +1 -0
- package/dist/svg/include-region.d.ts +4 -0
- package/dist/svg/include-region.d.ts.map +1 -0
- package/dist/svg/include-region.js +157 -0
- package/dist/svg/include-region.js.map +1 -0
- package/dist/svg/item.d.ts +36 -0
- package/dist/svg/item.d.ts.map +1 -0
- package/dist/svg/item.js +549 -0
- package/dist/svg/item.js.map +1 -0
- package/dist/svg/markers.d.ts +7 -0
- package/dist/svg/markers.d.ts.map +1 -0
- package/dist/svg/markers.js +151 -0
- package/dist/svg/markers.js.map +1 -0
- package/dist/svg/nowline.d.ts +4 -0
- package/dist/svg/nowline.d.ts.map +1 -0
- package/dist/svg/nowline.js +133 -0
- package/dist/svg/nowline.js.map +1 -0
- package/dist/svg/primitives.d.ts +7 -0
- package/dist/svg/primitives.d.ts.map +1 -0
- package/dist/svg/primitives.js +70 -0
- package/dist/svg/primitives.js.map +1 -0
- package/dist/svg/render.d.ts +13 -7
- package/dist/svg/render.d.ts.map +1 -1
- package/dist/svg/render.js +64 -1701
- package/dist/svg/render.js.map +1 -1
- package/dist/svg/sanitize.d.ts +10 -0
- package/dist/svg/sanitize.d.ts.map +1 -1
- package/dist/svg/sanitize.js +264 -16
- package/dist/svg/sanitize.js.map +1 -1
- package/dist/svg/swimlanes.d.ts +5 -0
- package/dist/svg/swimlanes.d.ts.map +1 -0
- package/dist/svg/swimlanes.js +228 -0
- package/dist/svg/swimlanes.js.map +1 -0
- package/dist/svg/timeline.d.ts +24 -0
- package/dist/svg/timeline.d.ts.map +1 -0
- package/dist/svg/timeline.js +284 -0
- package/dist/svg/timeline.js.map +1 -0
- package/dist/svg/waves.d.ts +62 -0
- package/dist/svg/waves.d.ts.map +1 -0
- package/dist/svg/waves.js +397 -0
- package/dist/svg/waves.js.map +1 -0
- package/package.json +6 -6
- package/src/image-size.ts +113 -0
- package/src/index.ts +10 -1
- package/src/logo.ts +82 -0
- package/src/stage.ts +191 -0
- package/src/svg/edges.ts +154 -0
- package/src/svg/footnotes.ts +115 -0
- package/src/svg/header.ts +285 -0
- package/src/svg/include-region.ts +201 -0
- package/src/svg/item.ts +672 -0
- package/src/svg/markers.ts +178 -0
- package/src/svg/nowline.ts +152 -0
- package/src/svg/primitives.ts +86 -0
- package/src/svg/render.ts +94 -2110
- package/src/svg/sanitize.ts +286 -16
- package/src/svg/swimlanes.ts +317 -0
- package/src/svg/timeline.ts +343 -0
- package/src/svg/waves.ts +502 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// The marker layer: anchors, milestones and the cut lines they drop through the
|
|
2
|
+
// chart body, with the diamonds and labels that sit in the marker row.
|
|
3
|
+
import { ACCENT_DASH_PATTERN } from '@nowline/layout';
|
|
4
|
+
import { escText, num, tag, textTag } from './xml.js';
|
|
5
|
+
export function renderAnchor(a, palette, fonts) {
|
|
6
|
+
const size = a.radius;
|
|
7
|
+
const cx = a.center.x;
|
|
8
|
+
const cy = a.center.y;
|
|
9
|
+
const fill = palette.anchorDiamond.fill;
|
|
10
|
+
const stroke = palette.anchorDiamond.stroke;
|
|
11
|
+
const diamond = tag('path', {
|
|
12
|
+
d: `M${num(cx)} ${num(cy - size)} L${num(cx + size)} ${num(cy)} L${num(cx)} ${num(cy + size)} L${num(cx - size)} ${num(cy)} Z`,
|
|
13
|
+
fill,
|
|
14
|
+
stroke,
|
|
15
|
+
'stroke-width': 1.25,
|
|
16
|
+
});
|
|
17
|
+
const labelColor = palette.anchorDiamond.label;
|
|
18
|
+
// For left-flipped labels, anchor the text at its RIGHT edge using
|
|
19
|
+
// `text-anchor: end`. The layout's `labelBox.width` is intentionally
|
|
20
|
+
// pessimistic (0.58 em/char) so positioning by the box's left edge
|
|
21
|
+
// would leave a visible gap between the actual text right edge and
|
|
22
|
+
// the diamond. End-anchoring lets the browser size the glyph run
|
|
23
|
+
// exactly and put the rightmost glyph 6 px from the diamond — same
|
|
24
|
+
// rhythm the right-side labels already get from start-anchoring at
|
|
25
|
+
// `diamondRight + 6`.
|
|
26
|
+
const labelX = a.labelSide === 'left' ? a.labelBox.x + a.labelBox.width : a.labelBox.x;
|
|
27
|
+
const labelAttrs = {
|
|
28
|
+
x: num(labelX),
|
|
29
|
+
y: num(cy + 4),
|
|
30
|
+
'font-family': fonts.sans,
|
|
31
|
+
'font-size': 10,
|
|
32
|
+
fill: labelColor,
|
|
33
|
+
};
|
|
34
|
+
if (a.labelSide === 'left')
|
|
35
|
+
labelAttrs['text-anchor'] = 'end';
|
|
36
|
+
const label = a.title ? textTag(labelAttrs, a.title) : '';
|
|
37
|
+
return tag('g', { 'data-layer': 'anchor', 'data-id': a.id ?? null }, hiddenDateTitle(a.hiddenDate) + diamond + label);
|
|
38
|
+
}
|
|
39
|
+
export function renderAnchorCutLine(a, palette) {
|
|
40
|
+
const stroke = palette.anchorDiamond.cutLine;
|
|
41
|
+
return tag('line', {
|
|
42
|
+
x1: num(a.center.x),
|
|
43
|
+
y1: num(a.center.y + a.radius + 1),
|
|
44
|
+
x2: num(a.center.x),
|
|
45
|
+
y2: num(a.cutBottomY),
|
|
46
|
+
stroke,
|
|
47
|
+
'stroke-width': 1,
|
|
48
|
+
'stroke-dasharray': '1 3',
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export function renderMilestone(m, palette, fonts) {
|
|
52
|
+
const cx = m.center.x;
|
|
53
|
+
const cy = m.center.y;
|
|
54
|
+
const r = m.radius;
|
|
55
|
+
const overrun = palette.milestoneDiamond.cutLineOverrun;
|
|
56
|
+
const diamond = (x, fill) => tag('path', {
|
|
57
|
+
d: `M${num(x)} ${num(cy - r)} L${num(x + r)} ${num(cy)} L${num(x)} ${num(cy + r)} L${num(x - r)} ${num(cy)} Z`,
|
|
58
|
+
fill,
|
|
59
|
+
stroke: fill,
|
|
60
|
+
'stroke-width': 1,
|
|
61
|
+
});
|
|
62
|
+
// An overrun dated milestone paints its diamond in the overrun colour
|
|
63
|
+
// (specs/rendering.md, Milestones). When it slipped, that diamond marks
|
|
64
|
+
// the missed date and a dotted span runs on to a second diamond, in the
|
|
65
|
+
// normal colour, where it lands; the label sits there. When it did not
|
|
66
|
+
// slip (the two diamonds would meet), the label is red too.
|
|
67
|
+
const slipX = m.slip?.x;
|
|
68
|
+
let flag;
|
|
69
|
+
if (slipX !== undefined) {
|
|
70
|
+
flag =
|
|
71
|
+
diamond(cx, overrun) +
|
|
72
|
+
tag('path', {
|
|
73
|
+
d: `M${num(cx + r)} ${num(cy)} H${num(slipX - r)}`,
|
|
74
|
+
fill: 'none',
|
|
75
|
+
stroke: overrun,
|
|
76
|
+
'stroke-width': 1.1,
|
|
77
|
+
'stroke-dasharray': '3 3',
|
|
78
|
+
'stroke-linecap': 'round',
|
|
79
|
+
}) +
|
|
80
|
+
diamond(slipX, palette.milestoneDiamond.fill);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
flag = diamond(cx, m.isOverrun ? overrun : palette.milestoneDiamond.fill);
|
|
84
|
+
}
|
|
85
|
+
const labelColor = m.isOverrun && slipX === undefined ? overrun : palette.milestoneDiamond.label;
|
|
86
|
+
// See renderAnchor — left-flipped labels use `text-anchor: end` so
|
|
87
|
+
// the visual right edge sits at `diamondLeft - 6`, matching the
|
|
88
|
+
// 6 px rhythm of right-side labels.
|
|
89
|
+
const labelX = m.labelSide === 'left' ? m.labelBox.x + m.labelBox.width : m.labelBox.x;
|
|
90
|
+
const labelAttrs = {
|
|
91
|
+
x: num(labelX),
|
|
92
|
+
y: num(cy + 4),
|
|
93
|
+
'font-family': fonts.sans,
|
|
94
|
+
'font-size': 10,
|
|
95
|
+
'font-weight': 600,
|
|
96
|
+
fill: labelColor,
|
|
97
|
+
};
|
|
98
|
+
if (m.labelSide === 'left')
|
|
99
|
+
labelAttrs['text-anchor'] = 'end';
|
|
100
|
+
const label = m.title ? textTag(labelAttrs, m.title) : '';
|
|
101
|
+
return tag('g', { 'data-layer': 'milestone', 'data-id': m.id ?? null }, hiddenDateTitle(m.hiddenDate) + flag + label);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* A marker dated on a day the axis hides sits at the next working day's
|
|
105
|
+
* x, so its own date rides along as a native tooltip, the way
|
|
106
|
+
* `renderInlineDatePin` titles its glyph (specs/working-calendar.md
|
|
107
|
+
* §7.4). Empty for a working-day date.
|
|
108
|
+
*/
|
|
109
|
+
function hiddenDateTitle(hiddenDate) {
|
|
110
|
+
return hiddenDate === undefined ? '' : `<title>${escText(hiddenDate)}</title>`;
|
|
111
|
+
}
|
|
112
|
+
export function renderMilestoneCutLine(m, idPrefix, palette) {
|
|
113
|
+
// A slipped milestone's cut line drops from where it lands, in the
|
|
114
|
+
// normal colour; one that overran without slipping keeps the red line.
|
|
115
|
+
const cutX = m.slip?.x ?? m.center.x;
|
|
116
|
+
const stroke = m.isOverrun && m.slip === undefined
|
|
117
|
+
? palette.milestoneDiamond.cutLineOverrun
|
|
118
|
+
: palette.milestoneDiamond.cutLineNormal;
|
|
119
|
+
const parts = [];
|
|
120
|
+
// A milestone on a wave boundary draws no cut line: the boundary
|
|
121
|
+
// already marks that instant (specs/waves.md §9.2).
|
|
122
|
+
if (!m.onWaveBoundary) {
|
|
123
|
+
parts.push(tag('line', {
|
|
124
|
+
x1: num(cutX),
|
|
125
|
+
y1: num(m.center.y + m.radius + 1),
|
|
126
|
+
x2: num(cutX),
|
|
127
|
+
y2: num(m.cutBottomY),
|
|
128
|
+
stroke,
|
|
129
|
+
'stroke-width': 2,
|
|
130
|
+
'stroke-dasharray': ACCENT_DASH_PATTERN,
|
|
131
|
+
'stroke-linecap': 'round',
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
if (m.slackArrows && m.slackArrows.length > 0) {
|
|
135
|
+
// Only a floating milestone draws slack arrows, in ink; a dated one
|
|
136
|
+
// slips instead.
|
|
137
|
+
for (const arrow of m.slackArrows) {
|
|
138
|
+
parts.push(tag('path', {
|
|
139
|
+
d: `M${num(arrow.x)} ${num(arrow.y)} H${num(m.center.x - 6)}`,
|
|
140
|
+
fill: 'none',
|
|
141
|
+
stroke: palette.milestoneDiamond.slack,
|
|
142
|
+
'stroke-width': 1.1,
|
|
143
|
+
'stroke-dasharray': '3 3',
|
|
144
|
+
'stroke-linecap': 'round',
|
|
145
|
+
'marker-end': `url(#${idPrefix}-arrow-dark)`,
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return parts.join('');
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=markers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markers.js","sourceRoot":"","sources":["../../src/svg/markers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nowline.d.ts","sourceRoot":"","sources":["../../src/svg/nowline.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAShE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,wBAAgB,aAAa,CACzB,CAAC,EAAE,iBAAiB,GAAG,IAAI,EAC3B,OAAO,EAAE,KAAK,EACd,KAAK,EAAE,YAAY,GACpB,MAAM,CA0BR"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// The now-line layer: the vertical line marking the current date and the pill
|
|
2
|
+
// at its top that carries the date label.
|
|
3
|
+
import { NOW_PILL_CORNER_RADIUS_PX, NOW_PILL_HEIGHT_PX, NOW_PILL_LABEL_BASELINE_OFFSET_PX, NOW_PILL_LABEL_FONT_SIZE_PX, NOW_PILL_LABEL_INSET_X_PX, NOWLINE_STROKE_WIDTH_PX, } from '@nowline/layout';
|
|
4
|
+
import { num, tag, textTag } from './xml.js';
|
|
5
|
+
export function renderNowline(n, palette, fonts) {
|
|
6
|
+
if (!n)
|
|
7
|
+
return '';
|
|
8
|
+
const color = palette.nowline.stroke;
|
|
9
|
+
const labelTextColor = palette.nowline.labelText;
|
|
10
|
+
// Line drops from `topY` (just below the pill / top of date headers)
|
|
11
|
+
// through the headers into the chart, ending at `bottomY`.
|
|
12
|
+
const line = tag('line', {
|
|
13
|
+
x1: num(n.x),
|
|
14
|
+
y1: num(n.topY),
|
|
15
|
+
x2: num(n.x),
|
|
16
|
+
y2: num(n.bottomY),
|
|
17
|
+
stroke: color,
|
|
18
|
+
'stroke-width': NOWLINE_STROKE_WIDTH_PX,
|
|
19
|
+
});
|
|
20
|
+
// Pill — sits above the date headers at `pillTopY`. Three modes
|
|
21
|
+
// (decided by layout in `buildNowline`):
|
|
22
|
+
// - center → rounded rect centered on the line, label `middle`
|
|
23
|
+
// - flag-right → squared LEFT, rounded RIGHT, line at left edge,
|
|
24
|
+
// label `start` past the line
|
|
25
|
+
// - flag-left → rounded LEFT, squared RIGHT, line at right edge,
|
|
26
|
+
// label `end` before the line
|
|
27
|
+
// The squared edge IS the line; the rounded edge points into the
|
|
28
|
+
// chart, so the pill always hugs the line and never overflows.
|
|
29
|
+
const pillBg = renderNowPillBg(n, color);
|
|
30
|
+
const label = renderNowPillLabel(n, labelTextColor, fonts);
|
|
31
|
+
return tag('g', { 'data-layer': 'nowline' }, line + pillBg + label);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* X coordinate of the pill's squared edge in flag modes. SVG strokes
|
|
35
|
+
* are centered on their geometry, so a 2.25 px line at `n.x` actually
|
|
36
|
+
* paints from `n.x - 1.125` to `n.x + 1.125`. To make the pill's
|
|
37
|
+
* squared edge line up with the OUTER edge of the line stroke (so
|
|
38
|
+
* the line and the pill share a single continuous edge instead of
|
|
39
|
+
* the line peeking past the pill by half-stroke), we offset by
|
|
40
|
+
* `NOWLINE_STROKE_WIDTH_PX / 2` on the side the line is on.
|
|
41
|
+
*
|
|
42
|
+
* flag-right: line on the LEFT → squared edge at n.x - half-stroke
|
|
43
|
+
* flag-left: line on the RIGHT → squared edge at n.x + half-stroke
|
|
44
|
+
*
|
|
45
|
+
* Center mode doesn't apply — the line passes through the pill's
|
|
46
|
+
* vertical center, so a half-stroke offset would make things worse.
|
|
47
|
+
*/
|
|
48
|
+
function squaredEdgeX(n) {
|
|
49
|
+
const halfStroke = NOWLINE_STROKE_WIDTH_PX / 2;
|
|
50
|
+
if (n.pillMode === 'flag-right')
|
|
51
|
+
return n.x - halfStroke;
|
|
52
|
+
if (n.pillMode === 'flag-left')
|
|
53
|
+
return n.x + halfStroke;
|
|
54
|
+
return n.x;
|
|
55
|
+
}
|
|
56
|
+
function renderNowPillBg(n, color) {
|
|
57
|
+
const top = n.pillTopY;
|
|
58
|
+
const bottom = n.pillTopY + NOW_PILL_HEIGHT_PX;
|
|
59
|
+
const r = NOW_PILL_CORNER_RADIUS_PX;
|
|
60
|
+
// Pill width comes from the layout (`n.pillWidth`) — locale-aware,
|
|
61
|
+
// floored at `NOW_PILL_WIDTH_PX` so en-US output stays byte-stable
|
|
62
|
+
// and longer locale strings (e.g. fr `'maint.'`) grow the pill
|
|
63
|
+
// instead of clipping. See `buildNowline` in `@nowline/layout`.
|
|
64
|
+
const width = n.pillWidth;
|
|
65
|
+
if (n.pillMode === 'center') {
|
|
66
|
+
return tag('rect', {
|
|
67
|
+
x: num(n.x - width / 2),
|
|
68
|
+
y: num(top),
|
|
69
|
+
width: num(width),
|
|
70
|
+
height: num(NOW_PILL_HEIGHT_PX),
|
|
71
|
+
rx: r,
|
|
72
|
+
ry: r,
|
|
73
|
+
fill: color,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const edgeX = squaredEdgeX(n);
|
|
77
|
+
if (n.pillMode === 'flag-right') {
|
|
78
|
+
// Squared LEFT edge aligns with line's left outer stroke edge,
|
|
79
|
+
// rounded corners on the RIGHT.
|
|
80
|
+
const right = edgeX + width;
|
|
81
|
+
const d = [
|
|
82
|
+
`M ${num(edgeX)} ${num(top)}`,
|
|
83
|
+
`L ${num(right - r)} ${num(top)}`,
|
|
84
|
+
`A ${r} ${r} 0 0 1 ${num(right)} ${num(top + r)}`,
|
|
85
|
+
`L ${num(right)} ${num(bottom - r)}`,
|
|
86
|
+
`A ${r} ${r} 0 0 1 ${num(right - r)} ${num(bottom)}`,
|
|
87
|
+
`L ${num(edgeX)} ${num(bottom)}`,
|
|
88
|
+
'Z',
|
|
89
|
+
].join(' ');
|
|
90
|
+
return tag('path', { d, fill: color });
|
|
91
|
+
}
|
|
92
|
+
// flag-left: squared RIGHT edge aligns with line's right outer
|
|
93
|
+
// stroke edge, rounded corners on the LEFT.
|
|
94
|
+
const left = edgeX - width;
|
|
95
|
+
const d = [
|
|
96
|
+
`M ${num(edgeX)} ${num(top)}`,
|
|
97
|
+
`L ${num(left + r)} ${num(top)}`,
|
|
98
|
+
`A ${r} ${r} 0 0 0 ${num(left)} ${num(top + r)}`,
|
|
99
|
+
`L ${num(left)} ${num(bottom - r)}`,
|
|
100
|
+
`A ${r} ${r} 0 0 0 ${num(left + r)} ${num(bottom)}`,
|
|
101
|
+
`L ${num(edgeX)} ${num(bottom)}`,
|
|
102
|
+
'Z',
|
|
103
|
+
].join(' ');
|
|
104
|
+
return tag('path', { d, fill: color });
|
|
105
|
+
}
|
|
106
|
+
function renderNowPillLabel(n, labelTextColor, fonts) {
|
|
107
|
+
const baselineY = n.pillTopY + NOW_PILL_LABEL_BASELINE_OFFSET_PX;
|
|
108
|
+
const edgeX = squaredEdgeX(n);
|
|
109
|
+
let labelX;
|
|
110
|
+
let textAnchor;
|
|
111
|
+
if (n.pillMode === 'center') {
|
|
112
|
+
labelX = n.x;
|
|
113
|
+
textAnchor = 'middle';
|
|
114
|
+
}
|
|
115
|
+
else if (n.pillMode === 'flag-right') {
|
|
116
|
+
labelX = edgeX + NOW_PILL_LABEL_INSET_X_PX;
|
|
117
|
+
textAnchor = 'start';
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
labelX = edgeX - NOW_PILL_LABEL_INSET_X_PX;
|
|
121
|
+
textAnchor = 'end';
|
|
122
|
+
}
|
|
123
|
+
return textTag({
|
|
124
|
+
x: num(labelX),
|
|
125
|
+
y: num(baselineY),
|
|
126
|
+
'font-family': fonts.sans,
|
|
127
|
+
'font-size': NOW_PILL_LABEL_FONT_SIZE_PX,
|
|
128
|
+
'font-weight': 700,
|
|
129
|
+
fill: labelTextColor,
|
|
130
|
+
'text-anchor': textAnchor,
|
|
131
|
+
}, n.label);
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=nowline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nowline.js","sourceRoot":"","sources":["../../src/svg/nowline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BoundingBox, ResolvedStyle } from '@nowline/layout';
|
|
2
|
+
import type { FontFamilies } from './render.js';
|
|
3
|
+
export declare function fontAttrs(style: ResolvedStyle, fonts: FontFamilies, overrideSize?: number): Record<string, string | number>;
|
|
4
|
+
export declare function rectFrame(x: number, y: number, w: number, h: number, style: ResolvedStyle, extra?: Record<string, string | number | undefined | null | boolean>): string;
|
|
5
|
+
/** `a ∩ b`, or undefined when they do not overlap. */
|
|
6
|
+
export declare function intersectBox(a: BoundingBox, b: BoundingBox): BoundingBox | undefined;
|
|
7
|
+
//# sourceMappingURL=primitives.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../../src/svg/primitives.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAElE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAwBhD,wBAAgB,SAAS,CACrB,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,YAAY,EACnB,YAAY,CAAC,EAAE,MAAM,GACtB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAQjC;AAQD,wBAAgB,SAAS,CACrB,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,KAAK,EAAE,aAAa,EACpB,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAAM,GACzE,MAAM,CAiBR;AAED,sDAAsD;AACtD,wBAAgB,YAAY,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,GAAG,WAAW,GAAG,SAAS,CAOpF"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Shared drawing primitives: the typography and stroke helpers every layer of
|
|
2
|
+
// the SVG renderer draws with, plus the rectangle frame and the box
|
|
3
|
+
// intersection the item, wave and non-working-band layers share. Nothing here
|
|
4
|
+
// knows about a particular roadmap element.
|
|
5
|
+
import { CORNER_RADIUS_PX, TEXT_SIZE_PX } from '@nowline/layout';
|
|
6
|
+
import { num, tag } from './xml.js';
|
|
7
|
+
// `TEXT_SIZE_PX`, `CORNER_RADIUS_PX`, `FONT_STACK` come from
|
|
8
|
+
// `@nowline/layout` (themes/shared) so a typography or radius change
|
|
9
|
+
// flows from one place to both layout and renderer.
|
|
10
|
+
//
|
|
11
|
+
// `WEIGHT_NUM` lives here only because no DSL `weight` table exists in
|
|
12
|
+
// shared yet. If/when it does, hoist this alongside `FONT_STACK`.
|
|
13
|
+
const WEIGHT_NUM = {
|
|
14
|
+
thin: 100,
|
|
15
|
+
light: 300,
|
|
16
|
+
normal: 400,
|
|
17
|
+
bold: 700,
|
|
18
|
+
};
|
|
19
|
+
// `style.textSize` is `SizeBucket` which includes `'full'`; the shared
|
|
20
|
+
// `TEXT_SIZE_PX` table only carries the size buckets (no `'full'` —
|
|
21
|
+
// that's a corner-radius-only value). Widen the lookup so a stray
|
|
22
|
+
// `'full'` falls through to the `?? 14` fallback instead of compiling.
|
|
23
|
+
function textSizePx(bucket) {
|
|
24
|
+
return TEXT_SIZE_PX[bucket] ?? 14;
|
|
25
|
+
}
|
|
26
|
+
export function fontAttrs(style, fonts, overrideSize) {
|
|
27
|
+
return {
|
|
28
|
+
'font-family': fonts[style.font],
|
|
29
|
+
'font-size': overrideSize ?? textSizePx(style.textSize),
|
|
30
|
+
'font-weight': WEIGHT_NUM[style.weight] ?? 400,
|
|
31
|
+
'font-style': style.italic ? 'italic' : 'normal',
|
|
32
|
+
fill: style.text,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function strokeDash(style) {
|
|
36
|
+
if (style.border === 'dashed')
|
|
37
|
+
return '4 3';
|
|
38
|
+
if (style.border === 'dotted')
|
|
39
|
+
return '1 3';
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
export function rectFrame(x, y, w, h, style, extra = {}) {
|
|
43
|
+
// The radius is a maximum: a frame narrower than two radii, such as a
|
|
44
|
+
// short bar drawn at its true width, rounds into a pill, then a line.
|
|
45
|
+
const rx = Math.min(CORNER_RADIUS_PX[style.cornerRadius] ?? 4, h / 2, w / 2);
|
|
46
|
+
return tag('rect', {
|
|
47
|
+
x: num(x),
|
|
48
|
+
y: num(y),
|
|
49
|
+
width: num(w),
|
|
50
|
+
height: num(h),
|
|
51
|
+
rx: num(rx),
|
|
52
|
+
ry: num(rx),
|
|
53
|
+
fill: style.bg === 'none' ? 'transparent' : style.bg,
|
|
54
|
+
stroke: style.fg,
|
|
55
|
+
'stroke-width': 1,
|
|
56
|
+
'stroke-dasharray': strokeDash(style) ?? null,
|
|
57
|
+
...extra,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/** `a ∩ b`, or undefined when they do not overlap. */
|
|
61
|
+
export function intersectBox(a, b) {
|
|
62
|
+
const x1 = Math.max(a.x, b.x);
|
|
63
|
+
const y1 = Math.max(a.y, b.y);
|
|
64
|
+
const x2 = Math.min(a.x + a.width, b.x + b.width);
|
|
65
|
+
const y2 = Math.min(a.y + a.height, b.y + b.height);
|
|
66
|
+
if (x2 <= x1 || y2 <= y1)
|
|
67
|
+
return undefined;
|
|
68
|
+
return { x: x1, y: y1, width: x2 - x1, height: y2 - y1 };
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=primitives.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"primitives.js","sourceRoot":"","sources":["../../src/svg/primitives.ts"],"names":[],"mappings":"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"}
|
package/dist/svg/render.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PositionedRoadmap } from '@nowline/layout';
|
|
2
|
+
import type { LoadedLogo } from '../logo.js';
|
|
3
|
+
import { renderEdge } from './edges.js';
|
|
4
|
+
import { renderHeader } from './header.js';
|
|
5
|
+
import { renderItem } from './item.js';
|
|
6
|
+
import { renderSwimlane } from './swimlanes.js';
|
|
7
|
+
import { renderTimeline } from './timeline.js';
|
|
2
8
|
export interface AssetBytes {
|
|
3
9
|
bytes: Uint8Array;
|
|
4
10
|
mime: string;
|
|
@@ -15,6 +21,12 @@ export type AssetResolver = (ref: string) => Promise<AssetBytes>;
|
|
|
15
21
|
export type FontFamilies = Record<'sans' | 'serif' | 'mono', string>;
|
|
16
22
|
export interface RenderOptions {
|
|
17
23
|
assetResolver?: AssetResolver;
|
|
24
|
+
/**
|
|
25
|
+
* The logo `loadLogo` already read. Its bytes are embedded as they are,
|
|
26
|
+
* so the asset is not resolved a second time. Omitted: `renderSvg`
|
|
27
|
+
* resolves the logo through `assetResolver`.
|
|
28
|
+
*/
|
|
29
|
+
logo?: LoadedLogo;
|
|
18
30
|
noLinks?: boolean;
|
|
19
31
|
strict?: boolean;
|
|
20
32
|
warn?: (message: string) => void;
|
|
@@ -25,11 +37,6 @@ export interface RenderOptions {
|
|
|
25
37
|
*/
|
|
26
38
|
fontFamilies?: FontFamilies;
|
|
27
39
|
}
|
|
28
|
-
declare function renderHeader(h: PositionedHeader, idPrefix: string, palette: Theme, fonts: FontFamilies): string;
|
|
29
|
-
declare function renderTimeline(t: PositionedTimelineScale, palette: Theme, fonts: FontFamilies): string;
|
|
30
|
-
declare function renderItem(i: PositionedItem, options: RenderOptions, idPrefix: string, palette: Theme, fonts: FontFamilies): string;
|
|
31
|
-
declare function renderSwimlane(s: PositionedSwimlane, options: RenderOptions, idPrefix: string, palette: Theme, fonts: FontFamilies): string;
|
|
32
|
-
declare function renderEdge(e: PositionedDependencyEdge, palette: Theme): string;
|
|
33
40
|
export declare function renderSvg(model: PositionedRoadmap, options?: RenderOptions): Promise<string>;
|
|
34
41
|
export declare const __internal: {
|
|
35
42
|
renderItem: typeof renderItem;
|
|
@@ -38,5 +45,4 @@ export declare const __internal: {
|
|
|
38
45
|
renderHeader: typeof renderHeader;
|
|
39
46
|
renderEdge: typeof renderEdge;
|
|
40
47
|
};
|
|
41
|
-
export {};
|
|
42
48
|
//# sourceMappingURL=render.d.ts.map
|
package/dist/svg/render.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/svg/render.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/svg/render.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,OAAO,EAAoC,YAAY,EAAE,MAAM,aAAa,CAAC;AAG7E,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AASvC,OAAO,EAAE,cAAc,EAAoB,MAAM,gBAAgB,CAAC;AAClE,OAAO,EAIH,cAAc,EACjB,MAAM,eAAe,CAAC;AAevB,MAAM,WAAW,UAAU;IACvB,KAAK,EAAE,UAAU,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;AAEjE;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,MAAM,CAAC,CAAC;AAErE,MAAM,WAAW,aAAa;IAC1B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;;;;OAIG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAEjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;CAC/B;AAED,wBAAsB,SAAS,CAC3B,KAAK,EAAE,iBAAiB,EACxB,OAAO,GAAE,aAAkB,GAC5B,OAAO,CAAC,MAAM,CAAC,CAoIjB;AAGD,eAAO,MAAM,UAAU;;;;;;CAMtB,CAAC"}
|