@transitrix/diagrams 1.0.1 → 1.1.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/activities/cpm.js +11 -2
- package/dist/activities/gantt.js +6 -3
- package/dist/activities/types.d.ts +1 -0
- package/dist/activities/validate.js +3 -3
- package/dist/activity-card/__tests__/resolver.test.js +22 -1
- package/dist/activity-card/resolver.js +11 -5
- package/dist/compliance/__tests__/impact.test.js +198 -0
- package/dist/compliance/html.js +1 -7
- package/dist/compliance/impact.d.ts +24 -1
- package/dist/compliance/impact.js +97 -21
- package/dist/factor/__tests__/validate.test.js +3 -3
- package/dist/factor/types.d.ts +1 -1
- package/dist/factor/validate.js +2 -2
- package/dist/fgca/__tests__/chain-table.test.js +2 -2
- package/dist/fgca/__tests__/layout.test.js +11 -11
- package/dist/fgca/__tests__/preview-layout.test.js +7 -7
- package/dist/fgca/__tests__/resolver.test.js +2 -2
- package/dist/fgca/chain-table.d.ts +1 -1
- package/dist/fgca/chain-table.js +4 -4
- package/dist/fgca/index.d.ts +2 -2
- package/dist/fgca/index.js +1 -1
- package/dist/fgca/layout.d.ts +2 -2
- package/dist/fgca/layout.js +9 -9
- package/dist/fgca/nodeTypes.d.ts +1 -1
- package/dist/fgca/nodeTypes.js +2 -2
- package/dist/fgca/nodes/FGCAFactorNode.d.ts +3 -3
- package/dist/fgca/nodes/FGCAFactorNode.js +5 -5
- package/dist/fgca/parse-canonical.js +15 -10
- package/dist/fgca/preview-layout.d.ts +1 -1
- package/dist/fgca/preview-layout.js +4 -4
- package/dist/fgca/resolver.js +3 -1
- package/dist/fgca/types.d.ts +11 -11
- package/dist/fgca/types.js +2 -2
- package/dist/fgca/validate.d.ts +2 -2
- package/dist/fgca/validate.js +17 -14
- package/dist/process-blueprint/index.d.ts +1 -1
- package/dist/process-blueprint/layout.js +64 -37
- package/dist/process-blueprint/types.d.ts +14 -0
- package/dist/scenarios/index.d.ts +1 -1
- package/dist/scenarios/types.d.ts +4 -4
- package/dist/theme/themes.js +2 -0
- package/dist/theme/tokens.d.ts +7 -1
- package/dist/theme/tokens.js +3 -1
- package/dist/webview/__tests__/entry-notations.test.js +2 -0
- package/dist/webview/__tests__/render-process.test.d.ts +1 -0
- package/dist/webview/__tests__/render-process.test.js +463 -0
- package/dist/webview/__tests__/render-util.test.d.ts +1 -0
- package/dist/webview/__tests__/render-util.test.js +54 -0
- package/dist/webview/entry.d.ts +1 -1
- package/dist/webview/entry.js +9 -5
- package/dist/webview/render-activities.d.ts +21 -1
- package/dist/webview/render-activities.js +91 -56
- package/dist/webview/render-activity-card.d.ts +11 -1
- package/dist/webview/render-activity-card.js +11 -2
- package/dist/webview/render-blocks.d.ts +24 -1
- package/dist/webview/render-blocks.js +58 -24
- package/dist/webview/render-fgca.d.ts +27 -0
- package/dist/webview/render-fgca.js +29 -15
- package/dist/webview/render-goals.d.ts +26 -1
- package/dist/webview/render-goals.js +30 -20
- package/dist/webview/render-process-blueprint.d.ts +11 -1
- package/dist/webview/render-process-blueprint.js +118 -53
- package/dist/webview/render-process.d.ts +95 -0
- package/dist/webview/render-process.js +392 -0
- package/dist/webview/render-scenarios.js +2 -2
- package/dist/webview/render-util.d.ts +19 -6
- package/dist/webview/render-util.js +23 -15
- package/package.json +15 -1
- package/src/activities/cpm.ts +14 -2
- package/src/activities/gantt.ts +6 -5
- package/src/activities/types.ts +1 -0
- package/src/activities/validate.ts +3 -3
- package/src/activity-card/__tests__/resolver.test.ts +24 -1
- package/src/activity-card/resolver.ts +12 -5
- package/src/compliance/__tests__/impact.test.ts +219 -0
- package/src/compliance/html.ts +1 -8
- package/src/compliance/impact.ts +141 -23
- package/src/factor/__tests__/validate.test.ts +3 -3
- package/src/factor/types.ts +1 -1
- package/src/factor/validate.ts +2 -2
- package/src/fgca/__tests__/chain-table.test.ts +2 -2
- package/src/fgca/__tests__/layout.test.ts +13 -13
- package/src/fgca/__tests__/preview-layout.test.ts +7 -7
- package/src/fgca/__tests__/resolver.test.ts +2 -2
- package/src/fgca/chain-table.ts +5 -5
- package/src/fgca/index.ts +3 -3
- package/src/fgca/layout.ts +11 -11
- package/src/fgca/nodeTypes.ts +3 -3
- package/src/fgca/nodes/FGCAFactorNode.tsx +6 -6
- package/src/fgca/parse-canonical.ts +17 -12
- package/src/fgca/preview-layout.ts +5 -5
- package/src/fgca/resolver.ts +3 -1
- package/src/fgca/types.ts +13 -13
- package/src/fgca/validate.ts +17 -14
- package/src/process-blueprint/index.ts +1 -0
- package/src/process-blueprint/layout.ts +71 -42
- package/src/process-blueprint/types.ts +15 -0
- package/src/scenarios/index.ts +2 -2
- package/src/scenarios/types.ts +4 -4
- package/src/scenarios/validate.ts +3 -3
- package/src/theme/themes.ts +2 -0
- package/src/theme/tokens.ts +3 -1
- package/src/webview/__tests__/entry-notations.test.ts +2 -0
- package/src/webview/__tests__/render-process.test.ts +538 -0
- package/src/webview/__tests__/render-util.test.ts +66 -0
- package/src/webview/entry.ts +11 -5
- package/src/webview/render-activities.ts +107 -67
- package/src/webview/render-activity-card.ts +11 -2
- package/src/webview/render-blocks.ts +78 -28
- package/src/webview/render-fgca.ts +52 -28
- package/src/webview/render-goals.ts +45 -23
- package/src/webview/render-process-blueprint.ts +156 -82
- package/src/webview/render-process.ts +507 -0
- package/src/webview/render-scenarios.ts +3 -3
- package/src/webview/render-util.ts +27 -14
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Host-neutral SVG renderer for the Process Blueprint notation.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* `
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* `<text class="text-header">` heading
|
|
9
|
-
* the
|
|
10
|
-
* self-contained.
|
|
4
|
+
* Single-emitter unification (review C): the canonical blueprint body lives
|
|
5
|
+
* here in `renderProcessBlueprintBody` and is shared by both hosts. The VS Code
|
|
6
|
+
* preview (`extension/src/process-blueprint-preview.ts`) wraps it with the VS
|
|
7
|
+
* Code title block; this module wraps it with `render-goals.ts`' simple
|
|
8
|
+
* optional `<text class="text-header">` heading and embeds the shared theme CSS
|
|
9
|
+
* (plus the blueprint-specific `bp-*` rules) so the JCEF output is
|
|
10
|
+
* self-contained. No `vscode`, no `node:*`, no host APIs.
|
|
11
11
|
*/
|
|
12
12
|
import { layoutProcessBlueprint } from '../process-blueprint/layout.js';
|
|
13
13
|
import type {
|
|
@@ -20,6 +20,15 @@ import { escXml } from './render-util.js';
|
|
|
20
20
|
|
|
21
21
|
const PAD = 24;
|
|
22
22
|
|
|
23
|
+
// Blueprint-specific rules not carried by the shared theme CSS. The outer
|
|
24
|
+
// border is drawn last on top of the clipped inner content; the row
|
|
25
|
+
// backgrounds are transparent (pills/chips carry the colour). Mirrors
|
|
26
|
+
// `BLUEPRINT_CSS` from the VS Code preview so the self-contained JCEF output
|
|
27
|
+
// matches the editor.
|
|
28
|
+
const BP_EMBED_CSS = `
|
|
29
|
+
.bp-row-bg { fill: none; }
|
|
30
|
+
.bp-border { fill: none; stroke: var(--ts-border, #cbd5e1); stroke-width: 1; }`;
|
|
31
|
+
|
|
23
32
|
function truncate(text: string, maxChars: number): string {
|
|
24
33
|
if (text.length <= maxChars) return text;
|
|
25
34
|
return text.slice(0, Math.max(0, maxChars - 1)) + '…';
|
|
@@ -42,11 +51,13 @@ function textCellSvg(
|
|
|
42
51
|
const tspans = ls
|
|
43
52
|
.map((ln, i) => `<tspan x="${x}" y="${first + i * lineHeight}">${escXml(ln)}</tspan>`)
|
|
44
53
|
.join('');
|
|
45
|
-
return `<text class="${cls}"
|
|
54
|
+
return `<text class="${cls}" dominant-baseline="central">${tspans}</text>`;
|
|
46
55
|
}
|
|
47
56
|
|
|
48
57
|
/**
|
|
49
|
-
* Render a single compliance law chip
|
|
58
|
+
* Render a single compliance law chip wrapped in a `<g data-chip-law…>` so the
|
|
59
|
+
* VS Code host can wire click-to-drill-down. Other hosts (JCEF) simply ignore
|
|
60
|
+
* the data attributes.
|
|
50
61
|
*
|
|
51
62
|
* Three orthogonal decorations are expressed as SVG overrides:
|
|
52
63
|
* - `new` — dashed stroke border (`stroke-dasharray="4 2"`)
|
|
@@ -54,26 +65,28 @@ function textCellSvg(
|
|
|
54
65
|
* - `deadline` — urgent fill override (`class="…compliance-deadline"`) + a small
|
|
55
66
|
* badge circle in the top-right corner
|
|
56
67
|
*/
|
|
57
|
-
function complianceChipSvg(
|
|
68
|
+
function complianceChipSvg(
|
|
69
|
+
chip: ComplianceChip,
|
|
70
|
+
ox: number,
|
|
71
|
+
oy: number,
|
|
72
|
+
stageId: string,
|
|
73
|
+
): string {
|
|
58
74
|
const { x, y, width, height, lawId, decorations } = chip;
|
|
59
75
|
const ax = x + ox;
|
|
60
76
|
const ay = y + oy;
|
|
61
|
-
const rx = 6;
|
|
62
77
|
const hasNew = decorations.includes('new');
|
|
63
78
|
const hasGap = decorations.includes('gap');
|
|
64
79
|
const hasDeadline = decorations.includes('deadline');
|
|
65
|
-
|
|
66
80
|
let rectClass = 'diagram-node level-5 compliance-chip';
|
|
67
81
|
if (hasDeadline) rectClass += ' compliance-deadline';
|
|
68
82
|
else if (hasGap) rectClass += ' compliance-gap';
|
|
69
|
-
|
|
70
83
|
const strokeDash = hasNew ? ' stroke-dasharray="4 2"' : '';
|
|
71
84
|
const parts: string[] = [];
|
|
72
85
|
parts.push(
|
|
73
|
-
`<rect class="${rectClass}" x="${ax}" y="${ay}" width="${width}" height="${height}" rx="
|
|
86
|
+
`<rect class="${rectClass}" x="${ax}" y="${ay}" width="${width}" height="${height}" rx="6"${strokeDash}/>`,
|
|
74
87
|
);
|
|
75
88
|
parts.push(
|
|
76
|
-
`<text class="text-pill" x="${ax + width / 2}" y="${ay + height / 2}" text-anchor="middle"
|
|
89
|
+
`<text class="text-pill" x="${ax + width / 2}" y="${ay + height / 2}" text-anchor="middle" dominant-baseline="central">${escXml(truncate(lawId, Math.floor(width / 8)))}</text>`,
|
|
77
90
|
);
|
|
78
91
|
if (hasDeadline) {
|
|
79
92
|
const br = 5;
|
|
@@ -81,127 +94,188 @@ function complianceChipSvg(chip: ComplianceChip, ox: number, oy: number): string
|
|
|
81
94
|
const by = ay + br + 3;
|
|
82
95
|
parts.push(`<circle class="compliance-badge" cx="${bx}" cy="${by}" r="${br}"/>`);
|
|
83
96
|
parts.push(
|
|
84
|
-
`<text class="compliance-badge-text" x="${bx}" y="${by}" text-anchor="middle"
|
|
97
|
+
`<text class="compliance-badge-text" x="${bx}" y="${by}" text-anchor="middle" dominant-baseline="central">!</text>`,
|
|
85
98
|
);
|
|
86
99
|
}
|
|
87
|
-
return parts.join('\n')
|
|
100
|
+
return `<g data-chip-law="${escXml(lawId)}" data-chip-stage="${escXml(stageId)}">\n${parts.join('\n')}\n</g>`;
|
|
88
101
|
}
|
|
89
102
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Canonical Process Blueprint body — everything inside the `<svg>` except the
|
|
105
|
+
* host-specific title block. Shared verbatim by the VS Code preview and the
|
|
106
|
+
* host-neutral wrapper below so both surfaces stay pixel-identical.
|
|
107
|
+
*
|
|
108
|
+
* Layering: a clipped group holds the row fills and grid lines; pills and
|
|
109
|
+
* compliance chips render on top (so grid lines sit under pill content); the
|
|
110
|
+
* outer rounded border is drawn last so corner cells never overflow it.
|
|
111
|
+
*/
|
|
112
|
+
export function renderProcessBlueprintBody(
|
|
113
|
+
layout: ProcessBlueprintLayout,
|
|
114
|
+
ox: number,
|
|
115
|
+
oy: number,
|
|
116
|
+
clipId = 'bp-clip',
|
|
97
117
|
): string {
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
const layout: ProcessBlueprintLayout = layoutProcessBlueprint(doc);
|
|
101
|
-
|
|
102
|
-
if (layout.bounds.width === 0 || layout.bounds.height === 0) {
|
|
103
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" viewBox="0 0 0 0"></svg>`;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const titleH = title ? PAD : 0;
|
|
107
|
-
const w = layout.bounds.width + PAD * 2;
|
|
108
|
-
const h = layout.bounds.height + PAD * 2 + titleH;
|
|
109
|
-
const ox = PAD;
|
|
110
|
-
const oy = PAD + titleH;
|
|
118
|
+
const tw = layout.bounds.width;
|
|
119
|
+
const th = layout.bounds.height;
|
|
111
120
|
|
|
112
121
|
const parts: string[] = [];
|
|
122
|
+
// Pills and chips collected separately so they render after (on top of) grid lines.
|
|
123
|
+
const topParts: string[] = [];
|
|
113
124
|
|
|
125
|
+
// Outer background (fill only; border drawn last on top).
|
|
114
126
|
parts.push(
|
|
115
|
-
`<rect class="diagram-node level-0" x="${ox}" y="${oy}" width="${
|
|
127
|
+
`<rect class="diagram-node level-0 bp-row-bg" x="${ox}" y="${oy}" width="${tw}" height="${th}" rx="6" stroke="none"/>`,
|
|
116
128
|
);
|
|
117
129
|
|
|
130
|
+
// ClipPath — clips all inner content to the outer rounded rect so corner cells
|
|
131
|
+
// don't visually overflow the rx=6 boundary.
|
|
132
|
+
parts.push(`<defs><clipPath id="${clipId}"><rect x="${ox}" y="${oy}" width="${tw}" height="${th}" rx="6"/></clipPath></defs>`);
|
|
133
|
+
|
|
134
|
+
const inner: string[] = [];
|
|
135
|
+
|
|
136
|
+
// Stage headers (top row).
|
|
118
137
|
for (const s of layout.stageHeaders) {
|
|
119
|
-
|
|
120
|
-
`<rect class="diagram-node level-1" x="${s.x + ox}" y="${s.y + oy}" width="${s.width}" height="${s.height}"/>`,
|
|
138
|
+
inner.push(
|
|
139
|
+
`<rect class="diagram-node level-1" x="${s.x + ox}" y="${s.y + oy}" width="${s.width}" height="${s.height}" stroke="none"/>`,
|
|
121
140
|
);
|
|
122
|
-
|
|
123
|
-
`<text class="text-header" x="${s.x + ox + s.width / 2}" y="${s.y + oy + s.height / 2}" text-anchor="middle"
|
|
141
|
+
inner.push(
|
|
142
|
+
`<text class="text-header" x="${s.x + ox + s.width / 2}" y="${s.y + oy + s.height / 2}" text-anchor="middle" dominant-baseline="central">${escXml(truncate(s.name, 28))}</text>`,
|
|
124
143
|
);
|
|
125
144
|
}
|
|
126
145
|
|
|
146
|
+
// Legend column (row labels).
|
|
127
147
|
for (const l of layout.legend) {
|
|
128
|
-
|
|
129
|
-
`<rect class="diagram-node level-2" x="${ox}" y="${l.y + oy}" width="${layout.legendColumnWidth}" height="${l.height}"/>`,
|
|
148
|
+
inner.push(
|
|
149
|
+
`<rect class="diagram-node level-2 bp-row-bg" x="${ox}" y="${l.y + oy}" width="${layout.legendColumnWidth}" height="${l.height}" stroke="none"/>`,
|
|
130
150
|
);
|
|
131
|
-
|
|
132
|
-
`<text class="text-primary" x="${ox + 12}" y="${l.y + oy + l.height / 2}"
|
|
151
|
+
inner.push(
|
|
152
|
+
`<text class="text-primary" x="${ox + 12}" y="${l.y + oy + l.height / 2}" dominant-baseline="central">${escXml(l.label)}</text>`,
|
|
133
153
|
);
|
|
134
154
|
}
|
|
135
155
|
|
|
156
|
+
// Goal and result cells.
|
|
136
157
|
const textX = layout.cellTextPadX;
|
|
137
158
|
for (const c of layout.goalCells) {
|
|
138
|
-
|
|
139
|
-
`<rect class="diagram-node level-3" x="${c.x + ox}" y="${c.y + oy}" width="${c.width}" height="${c.height}"/>`,
|
|
140
|
-
);
|
|
141
|
-
parts.push(
|
|
142
|
-
textCellSvg(c.lines, 'text-secondary', c.x + ox + textX, c.y + oy, c.height, layout.textLineHeight),
|
|
159
|
+
inner.push(
|
|
160
|
+
`<rect class="diagram-node level-3 bp-row-bg" x="${c.x + ox}" y="${c.y + oy}" width="${c.width}" height="${c.height}" stroke="none"/>`,
|
|
143
161
|
);
|
|
162
|
+
inner.push(textCellSvg(c.lines, 'text-secondary', c.x + ox + textX, c.y + oy, c.height, layout.textLineHeight));
|
|
144
163
|
}
|
|
145
164
|
for (const c of layout.resultCells) {
|
|
146
|
-
|
|
147
|
-
`<rect class="diagram-node level-4" x="${c.x + ox}" y="${c.y + oy}" width="${c.width}" height="${c.height}"/>`,
|
|
148
|
-
);
|
|
149
|
-
parts.push(
|
|
150
|
-
textCellSvg(c.lines, 'text-secondary', c.x + ox + textX, c.y + oy, c.height, layout.textLineHeight),
|
|
165
|
+
inner.push(
|
|
166
|
+
`<rect class="diagram-node level-4 bp-row-bg" x="${c.x + ox}" y="${c.y + oy}" width="${c.width}" height="${c.height}" stroke="none"/>`,
|
|
151
167
|
);
|
|
168
|
+
inner.push(textCellSvg(c.lines, 'text-secondary', c.x + ox + textX, c.y + oy, c.height, layout.textLineHeight));
|
|
152
169
|
}
|
|
153
170
|
|
|
171
|
+
// Aspect rows — transparent background; pills carry the colour.
|
|
154
172
|
for (let r = 0; r < layout.aspectRows.length; r++) {
|
|
155
173
|
const row = layout.aspectRows[r];
|
|
156
174
|
const level = 5 + (r % 3);
|
|
157
|
-
|
|
158
|
-
`<rect class="diagram-node level-${level}" x="${layout.legendColumnWidth + ox}" y="${row.y + oy}" width="${
|
|
175
|
+
inner.push(
|
|
176
|
+
`<rect class="diagram-node level-${level} bp-row-bg" x="${layout.legendColumnWidth + ox}" y="${row.y + oy}" width="${tw - layout.legendColumnWidth}" height="${row.height}" stroke="none"/>`,
|
|
159
177
|
);
|
|
160
|
-
for (let i = 1; i < layout.stageHeaders.length; i++) {
|
|
161
|
-
const x = layout.legendColumnWidth + i * layout.stageColumnWidth + ox;
|
|
162
|
-
parts.push(
|
|
163
|
-
`<line class="diagram-edge" x1="${x}" y1="${row.y + oy}" x2="${x}" y2="${row.y + row.height + oy}" opacity="0.3"/>`,
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
178
|
for (const p of row.pills) {
|
|
167
|
-
|
|
179
|
+
const cx = p.x + ox + p.width / 2;
|
|
180
|
+
const pillLH = 15;
|
|
181
|
+
// p.lines: name lines (possibly wrapped), optionally with id as last line.
|
|
182
|
+
const hasIdLine = !!(p.id && p.lines.length > 0 && p.lines[p.lines.length - 1] === p.id);
|
|
183
|
+
const nameLines = hasIdLine ? p.lines.slice(0, -1) : p.lines;
|
|
184
|
+
const idLine = hasIdLine ? p.id : undefined;
|
|
185
|
+
const pillFirstY = p.y + oy + p.height / 2 - ((p.lines.length - 1) / 2) * pillLH;
|
|
186
|
+
topParts.push(
|
|
168
187
|
`<rect class="diagram-node level-${level}" x="${p.x + ox}" y="${p.y + oy}" width="${p.width}" height="${p.height}" rx="6"/>`,
|
|
169
188
|
);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
189
|
+
if (nameLines.length > 0) {
|
|
190
|
+
const tspans = nameLines
|
|
191
|
+
.map((ln, i) => `<tspan x="${cx}" y="${pillFirstY + i * pillLH}">${escXml(ln)}</tspan>`)
|
|
192
|
+
.join('');
|
|
193
|
+
topParts.push(`<text class="text-pill" text-anchor="middle" dominant-baseline="central">${tspans}</text>`);
|
|
194
|
+
}
|
|
195
|
+
if (idLine) {
|
|
196
|
+
topParts.push(
|
|
197
|
+
`<text class="text-secondary" x="${cx}" y="${pillFirstY + nameLines.length * pillLH}" text-anchor="middle" dominant-baseline="central">${escXml(idLine)}</text>`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
174
200
|
}
|
|
175
201
|
}
|
|
176
202
|
|
|
177
203
|
// Compliance row (optional).
|
|
178
204
|
if (layout.complianceRow) {
|
|
179
205
|
const row = layout.complianceRow;
|
|
180
|
-
|
|
181
|
-
`<rect class="diagram-node level-5" x="${layout.legendColumnWidth + ox}" y="${row.y + oy}" width="${
|
|
206
|
+
inner.push(
|
|
207
|
+
`<rect class="diagram-node level-5 bp-row-bg" x="${layout.legendColumnWidth + ox}" y="${row.y + oy}" width="${tw - layout.legendColumnWidth}" height="${row.height}" stroke="none"/>`,
|
|
182
208
|
);
|
|
183
|
-
for (let i = 1; i < layout.stageHeaders.length; i++) {
|
|
184
|
-
const x = layout.legendColumnWidth + i * layout.stageColumnWidth + ox;
|
|
185
|
-
parts.push(
|
|
186
|
-
`<line class="diagram-edge" x1="${x}" y1="${row.y + oy}" x2="${x}" y2="${row.y + row.height + oy}" opacity="0.3"/>`,
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
209
|
for (const chip of row.chips) {
|
|
190
|
-
|
|
210
|
+
const stageId = layout.stageHeaders[chip.stageIndex]?.id ?? '';
|
|
211
|
+
topParts.push(complianceChipSvg(chip, ox, oy, stageId));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Grid lines — drawn before pills so they appear under pill content.
|
|
216
|
+
const gridX1 = ox;
|
|
217
|
+
const gridX2 = ox + tw;
|
|
218
|
+
const gridY1 = oy;
|
|
219
|
+
const gridY2 = oy + th;
|
|
220
|
+
if (layout.legend.length > 0) {
|
|
221
|
+
const headerBottomY = oy + layout.legend[0].y;
|
|
222
|
+
inner.push(`<line class="diagram-edge" x1="${gridX1}" y1="${headerBottomY}" x2="${gridX2}" y2="${headerBottomY}"/>`);
|
|
223
|
+
for (let i = 0; i < layout.legend.length - 1; i++) {
|
|
224
|
+
const rowBottomY = oy + layout.legend[i].y + layout.legend[i].height;
|
|
225
|
+
inner.push(`<line class="diagram-edge" x1="${gridX1}" y1="${rowBottomY}" x2="${gridX2}" y2="${rowBottomY}"/>`);
|
|
191
226
|
}
|
|
192
227
|
}
|
|
228
|
+
const legLineX = ox + layout.legendColumnWidth;
|
|
229
|
+
inner.push(`<line class="diagram-edge" x1="${legLineX}" y1="${gridY1}" x2="${legLineX}" y2="${gridY2}"/>`);
|
|
230
|
+
for (let i = 1; i < layout.stageHeaders.length; i++) {
|
|
231
|
+
const stageLineX = ox + layout.legendColumnWidth + i * layout.stageColumnWidth;
|
|
232
|
+
inner.push(`<line class="diagram-edge" x1="${stageLineX}" y1="${gridY1}" x2="${stageLineX}" y2="${gridY2}"/>`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Inner content (fills + grid lines) clipped to rounded outer rect.
|
|
236
|
+
parts.push(`<g clip-path="url(#${clipId})">${inner.join('\n')}${topParts.join('\n')}</g>`);
|
|
237
|
+
|
|
238
|
+
// Outer border on top — fill="none" as attribute so PNG export (no external CSS) never shows black.
|
|
239
|
+
parts.push(`<rect class="bp-border" x="${ox}" y="${oy}" width="${tw}" height="${th}" rx="6" fill="none"/>`);
|
|
240
|
+
|
|
241
|
+
return parts.join('\n');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export interface RenderProcessBlueprintOptions {
|
|
245
|
+
title?: string;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function renderProcessBlueprintSvg(
|
|
249
|
+
doc: ProcessBlueprintFile,
|
|
250
|
+
options: RenderProcessBlueprintOptions = {},
|
|
251
|
+
): string {
|
|
252
|
+
const { title = '' } = options;
|
|
253
|
+
|
|
254
|
+
const layout: ProcessBlueprintLayout = layoutProcessBlueprint(doc);
|
|
255
|
+
|
|
256
|
+
if (layout.bounds.width === 0 || layout.bounds.height === 0) {
|
|
257
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" viewBox="0 0 0 0"></svg>`;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const titleH = title ? PAD : 0;
|
|
261
|
+
const w = layout.bounds.width + PAD * 2;
|
|
262
|
+
const h = layout.bounds.height + PAD * 2 + titleH;
|
|
263
|
+
const ox = PAD;
|
|
264
|
+
const oy = PAD + titleH;
|
|
265
|
+
|
|
266
|
+
const body = renderProcessBlueprintBody(layout, ox, oy);
|
|
193
267
|
|
|
194
268
|
const titleSvg = title
|
|
195
269
|
? `<text class="text-header" x="${PAD}" y="${PAD - 6}">${escXml(title)}</text>`
|
|
196
270
|
: '';
|
|
197
271
|
|
|
198
|
-
// Embed the shared theme CSS
|
|
199
|
-
// self-contained for the JCEF host
|
|
200
|
-
const embedCss = generateSvgEmbedCss('transitrix');
|
|
272
|
+
// Embed the shared theme CSS plus the blueprint-specific rules inside the SVG
|
|
273
|
+
// so the rendered output is self-contained for the JCEF host.
|
|
274
|
+
const embedCss = generateSvgEmbedCss('transitrix') + BP_EMBED_CSS;
|
|
201
275
|
|
|
202
276
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
|
|
203
277
|
<style>${embedCss}</style>
|
|
204
278
|
${titleSvg}
|
|
205
|
-
${
|
|
279
|
+
${body}
|
|
206
280
|
</svg>`;
|
|
207
281
|
}
|