@transitrix/diagrams 1.0.0 → 1.0.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.
- package/LICENSE +21 -21
- package/README.md +47 -47
- package/dist/__tests__/edge-path.test.js +8 -0
- package/dist/activity-card/__tests__/example.test.js +20 -1
- package/dist/activity-card/__tests__/layout.test.js +102 -11
- package/dist/activity-card/__tests__/resolver.test.js +139 -6
- package/dist/activity-card/index.d.ts +1 -1
- package/dist/activity-card/layout.d.ts +1 -1
- package/dist/activity-card/layout.js +220 -103
- package/dist/activity-card/resolver.js +107 -26
- package/dist/activity-card/types.d.ts +150 -27
- package/dist/activity-card/validate.js +3 -0
- package/dist/actor/__tests__/validate.test.js +63 -31
- package/dist/actor/index.d.ts +1 -1
- package/dist/actor/index.js +1 -1
- package/dist/actor/types.d.ts +7 -3
- package/dist/actor/types.js +17 -3
- package/dist/actor/validate.js +55 -26
- package/dist/assertion/types.d.ts +3 -1
- package/dist/assertion/types.js +1 -1
- package/dist/change/__tests__/validate.test.js +40 -34
- package/dist/change/index.d.ts +1 -0
- package/dist/change/types.d.ts +1 -1
- package/dist/change/types.js +2 -2
- package/dist/change/validate.d.ts +6 -1
- package/dist/change/validate.js +68 -41
- package/dist/compliance/__tests__/classify.test.js +34 -0
- package/dist/compliance/__tests__/coverage-metric.test.js +146 -46
- package/dist/compliance/__tests__/html.test.js +2 -2
- package/dist/compliance/__tests__/impact.test.js +311 -6
- package/dist/compliance/classify.d.ts +6 -0
- package/dist/compliance/classify.js +9 -1
- package/dist/compliance/coverage-metric.d.ts +31 -11
- package/dist/compliance/coverage-metric.js +149 -39
- package/dist/compliance/html.js +77 -74
- package/dist/compliance/impact.d.ts +77 -16
- package/dist/compliance/impact.js +193 -25
- package/dist/compliance/index.d.ts +2 -2
- package/dist/compliance/index.js +1 -1
- package/dist/compliance/markdown.js +1 -0
- package/dist/compliance/types.d.ts +25 -6
- package/dist/edge-path.d.ts +8 -4
- package/dist/edge-path.js +11 -6
- package/dist/factor/__tests__/validate.test.js +74 -45
- package/dist/factor/index.d.ts +3 -2
- package/dist/factor/index.js +1 -1
- package/dist/factor/types.d.ts +4 -7
- package/dist/factor/types.js +10 -5
- package/dist/factor/validate.d.ts +6 -1
- package/dist/factor/validate.js +68 -50
- package/dist/fgca/__tests__/resolver.test.js +185 -0
- package/dist/fgca/index.d.ts +2 -0
- package/dist/fgca/index.js +1 -0
- package/dist/fgca/resolver.d.ts +49 -0
- package/dist/fgca/resolver.js +124 -0
- package/dist/goals/index.d.ts +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/process-blueprint/__tests__/layout.test.js +37 -0
- package/dist/process-blueprint/layout.js +5 -1
- package/dist/process-blueprint/types.d.ts +22 -1
- package/dist/stakeholder/__tests__/validate.test.js +49 -47
- package/dist/stakeholder/index.d.ts +1 -0
- package/dist/stakeholder/types.d.ts +3 -4
- package/dist/stakeholder/types.js +2 -3
- package/dist/stakeholder/validate.d.ts +6 -1
- package/dist/stakeholder/validate.js +71 -50
- package/dist/target-state/__tests__/validate.test.js +43 -27
- package/dist/target-state/index.d.ts +2 -0
- package/dist/target-state/index.js +1 -0
- package/dist/target-state/types.d.ts +6 -1
- package/dist/target-state/types.js +8 -7
- package/dist/target-state/validate.d.ts +6 -1
- package/dist/target-state/validate.js +69 -45
- package/dist/theme/themes.js +69 -69
- package/dist/webview/__tests__/entry.test.js +11 -11
- package/dist/webview/render-activities.d.ts +3 -1
- package/dist/webview/render-activities.js +2 -2
- package/dist/webview/render-activity-card.js +104 -77
- package/dist/webview/render-fgca.d.ts +4 -0
- package/dist/webview/render-fgca.js +2 -5
- package/dist/webview/render-goals.d.ts +1 -0
- package/dist/webview/render-goals.js +15 -15
- package/dist/webview/render-process-blueprint.js +10 -10
- package/package.json +40 -40
- package/src/__tests__/edge-path.test.ts +58 -46
- package/src/__tests__/scope.test.ts +27 -27
- package/src/__tests__/yaml-normalize.test.ts +158 -158
- package/src/activities/__tests__/cpm.test.ts +164 -164
- package/src/activities/__tests__/gantt.test.ts +245 -245
- package/src/activities/__tests__/layout.test.ts +112 -112
- package/src/activities/__tests__/validate.test.ts +409 -409
- package/src/activities/cpm.ts +95 -95
- package/src/activities/gantt.ts +276 -276
- package/src/activities/index.ts +28 -28
- package/src/activities/layout.ts +112 -112
- package/src/activities/types.ts +148 -148
- package/src/activities/validate.ts +357 -357
- package/src/activity-card/__tests__/example.test.ts +85 -66
- package/src/activity-card/__tests__/layout.test.ts +169 -63
- package/src/activity-card/__tests__/resolver.test.ts +307 -161
- package/src/activity-card/__tests__/validate.test.ts +131 -131
- package/src/activity-card/index.ts +38 -32
- package/src/activity-card/layout.ts +324 -194
- package/src/activity-card/resolver.ts +415 -337
- package/src/activity-card/types.ts +376 -237
- package/src/activity-card/validate.ts +162 -158
- package/src/actor/__tests__/validate.test.ts +70 -31
- package/src/actor/index.ts +1 -1
- package/src/actor/types.ts +21 -7
- package/src/actor/validate.ts +59 -27
- package/src/applications/__tests__/validate.test.ts +256 -256
- package/src/applications/index.ts +11 -11
- package/src/applications/types.ts +43 -43
- package/src/applications/validate.ts +131 -131
- package/src/assertion/__tests__/example.test.ts +37 -37
- package/src/assertion/__tests__/validate.test.ts +142 -142
- package/src/assertion/index.ts +12 -12
- package/src/assertion/types.ts +60 -58
- package/src/assertion/validate.ts +145 -145
- package/src/blocks/__tests__/layout.test.ts +169 -169
- package/src/blocks/__tests__/validate.test.ts +260 -260
- package/src/blocks/index.ts +17 -17
- package/src/blocks/layout.ts +186 -186
- package/src/blocks/types.ts +62 -62
- package/src/blocks/validate.ts +206 -206
- package/src/capability-map/__tests__/validate.test.ts +238 -238
- package/src/capability-map/index.ts +8 -8
- package/src/capability-map/types.ts +29 -29
- package/src/capability-map/validate.ts +127 -127
- package/src/change/__tests__/validate.test.ts +43 -34
- package/src/change/index.ts +1 -0
- package/src/change/types.ts +5 -3
- package/src/change/validate.ts +75 -43
- package/src/compliance/__tests__/classify.test.ts +64 -27
- package/src/compliance/__tests__/confidence.test.ts +98 -98
- package/src/compliance/__tests__/coverage-metric.test.ts +300 -188
- package/src/compliance/__tests__/demo-scenario.test.ts +273 -273
- package/src/compliance/__tests__/example.test.ts +57 -57
- package/src/compliance/__tests__/gap-report.test.ts +128 -128
- package/src/compliance/__tests__/html.test.ts +141 -141
- package/src/compliance/__tests__/impact.test.ts +840 -481
- package/src/compliance/__tests__/markdown.test.ts +94 -94
- package/src/compliance/__tests__/views.test.ts +64 -64
- package/src/compliance/classify.ts +100 -84
- package/src/compliance/confidence.ts +69 -69
- package/src/compliance/coverage-metric.ts +404 -263
- package/src/compliance/gap-report.ts +94 -94
- package/src/compliance/html.ts +298 -295
- package/src/compliance/impact.ts +861 -677
- package/src/compliance/index.ts +57 -56
- package/src/compliance/markdown.ts +136 -135
- package/src/compliance/reverse-index.ts +33 -33
- package/src/compliance/types.ts +136 -120
- package/src/compliance/views.ts +46 -46
- package/src/compliance-matrix/__tests__/build.test.ts +200 -200
- package/src/compliance-matrix/__tests__/example.test.ts +57 -57
- package/src/compliance-matrix/index.ts +11 -11
- package/src/compliance-matrix/types.ts +102 -102
- package/src/edge-path.ts +50 -44
- package/src/factor/__tests__/validate.test.ts +80 -45
- package/src/factor/index.ts +3 -7
- package/src/factor/types.ts +14 -18
- package/src/factor/validate.ts +77 -51
- package/src/fgca/__tests__/chain-table.test.ts +172 -172
- package/src/fgca/__tests__/layout.test.ts +179 -179
- package/src/fgca/__tests__/parse-canonical.test.ts +248 -248
- package/src/fgca/__tests__/preview-layout.test.ts +124 -124
- package/src/fgca/__tests__/resolver.test.ts +209 -0
- package/src/fgca/__tests__/validate.test.ts +142 -142
- package/src/fgca/chain-table.ts +225 -225
- package/src/fgca/index.ts +47 -45
- package/src/fgca/layout.ts +204 -204
- package/src/fgca/nodeTypes.ts +11 -11
- package/src/fgca/nodes/FGCAActivityNode.tsx +65 -65
- package/src/fgca/nodes/FGCAChangeNode.tsx +64 -64
- package/src/fgca/nodes/FGCAFactorNode.tsx +64 -64
- package/src/fgca/nodes/FGCAGoalNode.tsx +72 -72
- package/src/fgca/parse-canonical.ts +400 -400
- package/src/fgca/preview-layout.ts +217 -217
- package/src/fgca/resolver.ts +157 -0
- package/src/fgca/types.ts +50 -50
- package/src/fgca/validate.ts +126 -126
- package/src/geometry.ts +9 -9
- package/src/goals/__tests__/layout.test.ts +175 -175
- package/src/goals/__tests__/mutations.test.ts +111 -111
- package/src/goals/__tests__/parse-canonical.test.ts +145 -145
- package/src/goals/__tests__/validate.test.ts +202 -202
- package/src/goals/index.ts +20 -21
- package/src/goals/layout.ts +172 -172
- package/src/goals/mutations.ts +123 -123
- package/src/goals/parse-canonical.ts +286 -286
- package/src/goals/types.ts +85 -85
- package/src/goals/validate.ts +136 -136
- package/src/index.ts +28 -23
- package/src/process-blueprint/__tests__/compliance-lane.test.ts +320 -320
- package/src/process-blueprint/__tests__/layout.test.ts +240 -195
- package/src/process-blueprint/__tests__/validate.test.ts +310 -310
- package/src/process-blueprint/index.ts +31 -31
- package/src/process-blueprint/layout.ts +535 -531
- package/src/process-blueprint/types.ts +263 -242
- package/src/process-blueprint/validate.ts +181 -181
- package/src/process-map/__tests__/validate.test.ts +223 -223
- package/src/process-map/index.ts +10 -10
- package/src/process-map/types.ts +36 -36
- package/src/process-map/validate.ts +135 -135
- package/src/products/__tests__/validate.test.ts +239 -239
- package/src/products/index.ts +9 -9
- package/src/products/types.ts +31 -31
- package/src/products/validate.ts +112 -112
- package/src/repo-validate/__tests__/validate-repo.test.ts +149 -149
- package/src/repo-validate/index.ts +2 -2
- package/src/repo-validate/types.ts +50 -50
- package/src/repo-validate/validate-repo.ts +196 -196
- package/src/requirement/__tests__/example.test.ts +24 -24
- package/src/requirement/__tests__/validate.test.ts +96 -96
- package/src/requirement/index.ts +4 -4
- package/src/requirement/types.ts +35 -35
- package/src/requirement/validate.ts +92 -92
- package/src/scenarios/__tests__/validate.test.ts +209 -209
- package/src/scenarios/index.ts +15 -15
- package/src/scenarios/types.ts +37 -37
- package/src/scenarios/validate.ts +122 -122
- package/src/schema-version.ts +16 -16
- package/src/scope.ts +46 -46
- package/src/stakeholder/__tests__/validate.test.ts +51 -47
- package/src/stakeholder/index.ts +1 -0
- package/src/stakeholder/types.ts +7 -7
- package/src/stakeholder/validate.ts +79 -52
- package/src/target-state/__tests__/validate.test.ts +48 -27
- package/src/target-state/index.ts +2 -0
- package/src/target-state/types.ts +11 -7
- package/src/target-state/validate.ts +76 -46
- package/src/theme/index.ts +4 -4
- package/src/theme/themes.ts +237 -237
- package/src/theme/tokens.ts +133 -133
- package/src/typed-id.ts +56 -56
- package/src/validation-types.ts +36 -36
- package/src/webview/__tests__/entry-notations.test.ts +77 -77
- package/src/webview/__tests__/entry.test.ts +64 -64
- package/src/webview/__tests__/render-goals.test.ts +72 -72
- package/src/webview/entry.ts +292 -292
- package/src/webview/render-activities.ts +5 -3
- package/src/webview/render-activity-card.ts +266 -234
- package/src/webview/render-fgca.ts +6 -5
- package/src/webview/render-goals.ts +111 -110
- package/src/webview/render-process-blueprint.ts +207 -207
- package/src/webview/styles.css +322 -323
- package/src/yaml-normalize.ts +56 -56
- package/dist/element-envelope.d.ts +0 -15
- package/dist/element-envelope.js +0 -55
- package/dist/issues/__tests__/layout.test.js +0 -62
- package/dist/issues/__tests__/validate.test.d.ts +0 -1
- package/dist/issues/__tests__/validate.test.js +0 -82
- package/dist/issues/index.d.ts +0 -4
- package/dist/issues/index.js +0 -2
- package/dist/issues/layout.d.ts +0 -10
- package/dist/issues/layout.js +0 -82
- package/dist/issues/types.d.ts +0 -61
- package/dist/issues/types.js +0 -6
- package/dist/issues/validate.d.ts +0 -3
- package/dist/issues/validate.js +0 -143
- package/dist/webview/transitrix-render.css +0 -323
- package/dist/webview/transitrix-render.js +0 -6452
- package/dist/webview/transitrix-render.js.map +0 -7
- package/src/element-envelope.ts +0 -76
- /package/dist/{issues/__tests__/layout.test.d.ts → fgca/__tests__/resolver.test.d.ts} +0 -0
|
@@ -1,110 +1,111 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser-safe SVG renderer for the canonical Goals notation.
|
|
3
|
-
*
|
|
4
|
-
* Step 3 of the IntelliJ epic (ADR 0001): the webview bundle must turn a
|
|
5
|
-
* validated GoalTree into renderable SVG so JCEF can drop it into the preview
|
|
6
|
-
* panel. The VS Code path lives in `extension/src/goals-preview.ts` and pulls
|
|
7
|
-
* in VS Code-specific concerns (themes, title block, save dialogs); this
|
|
8
|
-
* module is the host-neutral subset — pure layout → SVG with no VS Code APIs,
|
|
9
|
-
* no `node:fs`, no `node:path`.
|
|
10
|
-
*
|
|
11
|
-
* Step 4 will follow the same shape for the remaining ten notations.
|
|
12
|
-
*/
|
|
13
|
-
import { layoutGoalTree } from '../goals/layout.js';
|
|
14
|
-
import type { GoalTree, GoalTreeLayout, LaidOutEdge } from '../goals/types.js';
|
|
15
|
-
import { horizontalCubicEdgePath, DEFAULT_EDGE_CURVATURE } from '../edge-path.js';
|
|
16
|
-
import { generateSvgEmbedCss } from '../theme/index.js';
|
|
17
|
-
|
|
18
|
-
const NODE_W = 250;
|
|
19
|
-
const NODE_H = 60;
|
|
20
|
-
const RANK_SEP = 100;
|
|
21
|
-
const NODE_SEP = 24;
|
|
22
|
-
const PAD = 24;
|
|
23
|
-
const LABEL_MAX = 36;
|
|
24
|
-
const LABEL_TRIM = 34;
|
|
25
|
-
|
|
26
|
-
function escXml(s: string): string {
|
|
27
|
-
return s
|
|
28
|
-
.replace(/&/g, '&')
|
|
29
|
-
.replace(/</g, '<')
|
|
30
|
-
.replace(/>/g, '>')
|
|
31
|
-
.replace(/"/g, '"');
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface RenderGoalsOptions {
|
|
35
|
-
treeName?: string;
|
|
36
|
-
curvature?: number;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
<
|
|
85
|
-
</
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
<
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
</
|
|
106
|
-
|
|
107
|
-
${
|
|
108
|
-
${
|
|
109
|
-
|
|
110
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Browser-safe SVG renderer for the canonical Goals notation.
|
|
3
|
+
*
|
|
4
|
+
* Step 3 of the IntelliJ epic (ADR 0001): the webview bundle must turn a
|
|
5
|
+
* validated GoalTree into renderable SVG so JCEF can drop it into the preview
|
|
6
|
+
* panel. The VS Code path lives in `extension/src/goals-preview.ts` and pulls
|
|
7
|
+
* in VS Code-specific concerns (themes, title block, save dialogs); this
|
|
8
|
+
* module is the host-neutral subset — pure layout → SVG with no VS Code APIs,
|
|
9
|
+
* no `node:fs`, no `node:path`.
|
|
10
|
+
*
|
|
11
|
+
* Step 4 will follow the same shape for the remaining ten notations.
|
|
12
|
+
*/
|
|
13
|
+
import { layoutGoalTree } from '../goals/layout.js';
|
|
14
|
+
import type { GoalTree, GoalTreeLayout, LaidOutEdge } from '../goals/types.js';
|
|
15
|
+
import { horizontalCubicEdgePath, DEFAULT_EDGE_CURVATURE } from '../edge-path.js';
|
|
16
|
+
import { generateSvgEmbedCss } from '../theme/index.js';
|
|
17
|
+
|
|
18
|
+
const NODE_W = 250;
|
|
19
|
+
const NODE_H = 60;
|
|
20
|
+
const RANK_SEP = 100;
|
|
21
|
+
const NODE_SEP = 24;
|
|
22
|
+
const PAD = 24;
|
|
23
|
+
const LABEL_MAX = 36;
|
|
24
|
+
const LABEL_TRIM = 34;
|
|
25
|
+
|
|
26
|
+
function escXml(s: string): string {
|
|
27
|
+
return s
|
|
28
|
+
.replace(/&/g, '&')
|
|
29
|
+
.replace(/</g, '<')
|
|
30
|
+
.replace(/>/g, '>')
|
|
31
|
+
.replace(/"/g, '"');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface RenderGoalsOptions {
|
|
35
|
+
treeName?: string;
|
|
36
|
+
curvature?: number;
|
|
37
|
+
entryCurvature?: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function renderGoalsSvg(tree: GoalTree, options: RenderGoalsOptions = {}): string {
|
|
41
|
+
const { treeName = '', curvature = DEFAULT_EDGE_CURVATURE, entryCurvature } = options;
|
|
42
|
+
|
|
43
|
+
const layout: GoalTreeLayout = layoutGoalTree(tree, {
|
|
44
|
+
nodeWidth: NODE_W,
|
|
45
|
+
nodeHeight: NODE_H,
|
|
46
|
+
rankSep: RANK_SEP,
|
|
47
|
+
nodeSep: NODE_SEP,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (layout.nodes.length === 0) {
|
|
51
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" viewBox="0 0 0 0"></svg>`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const w = layout.bounds.width + PAD * 2;
|
|
55
|
+
const h = layout.bounds.height + PAD * 2;
|
|
56
|
+
const ox = -layout.bounds.x + PAD;
|
|
57
|
+
const oy = -layout.bounds.y + PAD;
|
|
58
|
+
|
|
59
|
+
const nodeMap = new Map(layout.nodes.map((n) => [n.id, n]));
|
|
60
|
+
|
|
61
|
+
function edgePath(e: LaidOutEdge): string {
|
|
62
|
+
const s = nodeMap.get(e.source);
|
|
63
|
+
const t = nodeMap.get(e.target);
|
|
64
|
+
if (!s || !t) return '';
|
|
65
|
+
const sx = s.x + ox + s.width;
|
|
66
|
+
const sy = s.y + oy + s.height / 2;
|
|
67
|
+
const tx = t.x + ox;
|
|
68
|
+
const ty = t.y + oy + t.height / 2;
|
|
69
|
+
return horizontalCubicEdgePath(sx, sy, tx, ty, curvature, entryCurvature);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const edgeSvg = layout.edges
|
|
73
|
+
.map((e) => `<path d="${edgePath(e)}" class="diagram-edge" marker-end="url(#arrow)"/>`)
|
|
74
|
+
.join('\n');
|
|
75
|
+
|
|
76
|
+
const nodeSvg = layout.nodes
|
|
77
|
+
.map((n) => {
|
|
78
|
+
const x = n.x + ox;
|
|
79
|
+
const y = n.y + oy;
|
|
80
|
+
const level = n.data.level % 8;
|
|
81
|
+
const labelText = n.data.name ?? String(n.id);
|
|
82
|
+
const label = labelText.length > LABEL_MAX ? labelText.slice(0, LABEL_TRIM) + '…' : labelText;
|
|
83
|
+
return `<g>
|
|
84
|
+
<rect class="diagram-node level-${level}" x="${x}" y="${y}" width="${n.width}" height="${n.height}" rx="8"/>
|
|
85
|
+
<text class="text-primary" x="${x + n.width / 2}" y="${y + n.height / 2}" text-anchor="middle" dominant-baseline="central">${escXml(label)}</text>
|
|
86
|
+
</g>`;
|
|
87
|
+
})
|
|
88
|
+
.join('\n');
|
|
89
|
+
|
|
90
|
+
const titleSvg = treeName
|
|
91
|
+
? `<text class="text-header" x="${PAD}" y="${PAD - 6}">${escXml(`Goal tree — ${treeName}`)}</text>`
|
|
92
|
+
: '';
|
|
93
|
+
|
|
94
|
+
// Embed the shared theme CSS inside the SVG so the rendered output is
|
|
95
|
+
// self-contained — the JCEF host page only needs to drop the SVG into the
|
|
96
|
+
// DOM and styling resolves without any cooperation from the host stylesheet.
|
|
97
|
+
// Matches what the VS Code path produces via `prepareSvgForExport`.
|
|
98
|
+
const embedCss = generateSvgEmbedCss('transitrix');
|
|
99
|
+
|
|
100
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
|
|
101
|
+
<style>${embedCss}</style>
|
|
102
|
+
<defs>
|
|
103
|
+
<marker id="arrow" markerWidth="8" markerHeight="8" refX="8" refY="3" orient="auto">
|
|
104
|
+
<path d="M0,0 L0,6 L8,3 z" class="arrow-fill"/>
|
|
105
|
+
</marker>
|
|
106
|
+
</defs>
|
|
107
|
+
${titleSvg}
|
|
108
|
+
${nodeSvg}
|
|
109
|
+
${edgeSvg}
|
|
110
|
+
</svg>`;
|
|
111
|
+
}
|
|
@@ -1,207 +1,207 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Host-neutral SVG renderer for the Process Blueprint notation.
|
|
3
|
-
*
|
|
4
|
-
* Step 4 of the IntelliJ epic (ADR 0001): ported from the pure SVG core of
|
|
5
|
-
* `extension/src/process-blueprint-preview.ts` (`layoutToSvg`). The VS Code
|
|
6
|
-
* title block (`svg-title-block.ts`) is intentionally dropped — JCEF has no
|
|
7
|
-
* VS Code APIs — and replaced with `render-goals.ts`' simple optional
|
|
8
|
-
* `<text class="text-header">` heading. No `vscode`, no `node:*`, no host APIs;
|
|
9
|
-
* the shared theme CSS is embedded inside the `<svg>` so the output is
|
|
10
|
-
* self-contained.
|
|
11
|
-
*/
|
|
12
|
-
import { layoutProcessBlueprint } from '../process-blueprint/layout.js';
|
|
13
|
-
import type {
|
|
14
|
-
ComplianceChip,
|
|
15
|
-
ProcessBlueprintFile,
|
|
16
|
-
ProcessBlueprintLayout,
|
|
17
|
-
} from '../process-blueprint/types.js';
|
|
18
|
-
import { generateSvgEmbedCss } from '../theme/index.js';
|
|
19
|
-
import { escXml } from './render-util.js';
|
|
20
|
-
|
|
21
|
-
const PAD = 24;
|
|
22
|
-
|
|
23
|
-
function truncate(text: string, maxChars: number): string {
|
|
24
|
-
if (text.length <= maxChars) return text;
|
|
25
|
-
return text.slice(0, Math.max(0, maxChars - 1)) + '…';
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Render a left-anchored, vertically centred multi-line text cell. `lines` are
|
|
30
|
-
* pre-wrapped by the layout; the block is centred within the cell height.
|
|
31
|
-
*/
|
|
32
|
-
function textCellSvg(
|
|
33
|
-
lines: string[],
|
|
34
|
-
cls: string,
|
|
35
|
-
x: number,
|
|
36
|
-
cellTop: number,
|
|
37
|
-
cellHeight: number,
|
|
38
|
-
lineHeight: number,
|
|
39
|
-
): string {
|
|
40
|
-
const ls = lines.length > 0 ? lines : [''];
|
|
41
|
-
const first = cellTop + cellHeight / 2 - ((ls.length - 1) / 2) * lineHeight;
|
|
42
|
-
const tspans = ls
|
|
43
|
-
.map((ln, i) => `<tspan x="${x}" y="${first + i * lineHeight}">${escXml(ln)}</tspan>`)
|
|
44
|
-
.join('');
|
|
45
|
-
return `<text class="${cls}" dominant-baseline
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Render a single compliance law chip.
|
|
50
|
-
*
|
|
51
|
-
* Three orthogonal decorations are expressed as SVG overrides:
|
|
52
|
-
* - `new` — dashed stroke border (`stroke-dasharray="4 2"`)
|
|
53
|
-
* - `gap` — warning-level fill (`class="…compliance-gap"`)
|
|
54
|
-
* - `deadline` — urgent fill override (`class="…compliance-deadline"`) + a small
|
|
55
|
-
* badge circle in the top-right corner
|
|
56
|
-
*/
|
|
57
|
-
function complianceChipSvg(chip: ComplianceChip, ox: number, oy: number): string {
|
|
58
|
-
const { x, y, width, height, lawId, decorations } = chip;
|
|
59
|
-
const ax = x + ox;
|
|
60
|
-
const ay = y + oy;
|
|
61
|
-
const rx = 6;
|
|
62
|
-
const hasNew = decorations.includes('new');
|
|
63
|
-
const hasGap = decorations.includes('gap');
|
|
64
|
-
const hasDeadline = decorations.includes('deadline');
|
|
65
|
-
|
|
66
|
-
let rectClass = 'diagram-node level-5 compliance-chip';
|
|
67
|
-
if (hasDeadline) rectClass += ' compliance-deadline';
|
|
68
|
-
else if (hasGap) rectClass += ' compliance-gap';
|
|
69
|
-
|
|
70
|
-
const strokeDash = hasNew ? ' stroke-dasharray="4 2"' : '';
|
|
71
|
-
const parts: string[] = [];
|
|
72
|
-
parts.push(
|
|
73
|
-
`<rect class="${rectClass}" x="${ax}" y="${ay}" width="${width}" height="${height}" rx="${rx}"${strokeDash}/>`,
|
|
74
|
-
);
|
|
75
|
-
parts.push(
|
|
76
|
-
`<text class="text-pill" x="${ax + width / 2}" y="${ay + height / 2}" text-anchor="middle" dominant-baseline
|
|
77
|
-
);
|
|
78
|
-
if (hasDeadline) {
|
|
79
|
-
const br = 5;
|
|
80
|
-
const bx = ax + width - br - 3;
|
|
81
|
-
const by = ay + br + 3;
|
|
82
|
-
parts.push(`<circle class="compliance-badge" cx="${bx}" cy="${by}" r="${br}"/>`);
|
|
83
|
-
parts.push(
|
|
84
|
-
`<text class="compliance-badge-text" x="${bx}" y="${by}" text-anchor="middle" dominant-baseline
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
return parts.join('\n');
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export interface RenderProcessBlueprintOptions {
|
|
91
|
-
title?: string;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function renderProcessBlueprintSvg(
|
|
95
|
-
doc: ProcessBlueprintFile,
|
|
96
|
-
options: RenderProcessBlueprintOptions = {},
|
|
97
|
-
): string {
|
|
98
|
-
const { title = '' } = options;
|
|
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;
|
|
111
|
-
|
|
112
|
-
const parts: string[] = [];
|
|
113
|
-
|
|
114
|
-
parts.push(
|
|
115
|
-
`<rect class="diagram-node level-0" x="${ox}" y="${oy}" width="${layout.bounds.width}" height="${layout.bounds.height}" rx="6"/>`,
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
for (const s of layout.stageHeaders) {
|
|
119
|
-
parts.push(
|
|
120
|
-
`<rect class="diagram-node level-1" x="${s.x + ox}" y="${s.y + oy}" width="${s.width}" height="${s.height}"/>`,
|
|
121
|
-
);
|
|
122
|
-
parts.push(
|
|
123
|
-
`<text class="text-header" x="${s.x + ox + s.width / 2}" y="${s.y + oy + s.height / 2}" text-anchor="middle" dominant-baseline
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
for (const l of layout.legend) {
|
|
128
|
-
parts.push(
|
|
129
|
-
`<rect class="diagram-node level-2" x="${ox}" y="${l.y + oy}" width="${layout.legendColumnWidth}" height="${l.height}"/>`,
|
|
130
|
-
);
|
|
131
|
-
parts.push(
|
|
132
|
-
`<text class="text-primary" x="${ox + 12}" y="${l.y + oy + l.height / 2}" dominant-baseline
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const textX = layout.cellTextPadX;
|
|
137
|
-
for (const c of layout.goalCells) {
|
|
138
|
-
parts.push(
|
|
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),
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
for (const c of layout.resultCells) {
|
|
146
|
-
parts.push(
|
|
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),
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
for (let r = 0; r < layout.aspectRows.length; r++) {
|
|
155
|
-
const row = layout.aspectRows[r];
|
|
156
|
-
const level = 5 + (r % 3);
|
|
157
|
-
parts.push(
|
|
158
|
-
`<rect class="diagram-node level-${level}" x="${layout.legendColumnWidth + ox}" y="${row.y + oy}" width="${layout.bounds.width - layout.legendColumnWidth}" height="${row.height}" opacity="0.15"/>`,
|
|
159
|
-
);
|
|
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
|
-
for (const p of row.pills) {
|
|
167
|
-
parts.push(
|
|
168
|
-
`<rect class="diagram-node level-${level}" x="${p.x + ox}" y="${p.y + oy}" width="${p.width}" height="${p.height}" rx="6"/>`,
|
|
169
|
-
);
|
|
170
|
-
const label = p.id ? `${p.name} · ${p.id}` : p.name;
|
|
171
|
-
parts.push(
|
|
172
|
-
`<text class="text-pill" x="${p.x + ox + p.width / 2}" y="${p.y + oy + p.height / 2}" text-anchor="middle" dominant-baseline
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Compliance row (optional).
|
|
178
|
-
if (layout.complianceRow) {
|
|
179
|
-
const row = layout.complianceRow;
|
|
180
|
-
parts.push(
|
|
181
|
-
`<rect class="diagram-node level-5" x="${layout.legendColumnWidth + ox}" y="${row.y + oy}" width="${layout.bounds.width - layout.legendColumnWidth}" height="${row.height}" opacity="0.10"/>`,
|
|
182
|
-
);
|
|
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
|
-
for (const chip of row.chips) {
|
|
190
|
-
parts.push(complianceChipSvg(chip, ox, oy));
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const titleSvg = title
|
|
195
|
-
? `<text class="text-header" x="${PAD}" y="${PAD - 6}">${escXml(title)}</text>`
|
|
196
|
-
: '';
|
|
197
|
-
|
|
198
|
-
// Embed the shared theme CSS inside the SVG so the rendered output is
|
|
199
|
-
// self-contained for the JCEF host — matches render-goals.ts.
|
|
200
|
-
const embedCss = generateSvgEmbedCss('transitrix');
|
|
201
|
-
|
|
202
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
|
|
203
|
-
<style>${embedCss}</style>
|
|
204
|
-
${titleSvg}
|
|
205
|
-
${parts.join('\n')}
|
|
206
|
-
</svg>`;
|
|
207
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Host-neutral SVG renderer for the Process Blueprint notation.
|
|
3
|
+
*
|
|
4
|
+
* Step 4 of the IntelliJ epic (ADR 0001): ported from the pure SVG core of
|
|
5
|
+
* `extension/src/process-blueprint-preview.ts` (`layoutToSvg`). The VS Code
|
|
6
|
+
* title block (`svg-title-block.ts`) is intentionally dropped — JCEF has no
|
|
7
|
+
* VS Code APIs — and replaced with `render-goals.ts`' simple optional
|
|
8
|
+
* `<text class="text-header">` heading. No `vscode`, no `node:*`, no host APIs;
|
|
9
|
+
* the shared theme CSS is embedded inside the `<svg>` so the output is
|
|
10
|
+
* self-contained.
|
|
11
|
+
*/
|
|
12
|
+
import { layoutProcessBlueprint } from '../process-blueprint/layout.js';
|
|
13
|
+
import type {
|
|
14
|
+
ComplianceChip,
|
|
15
|
+
ProcessBlueprintFile,
|
|
16
|
+
ProcessBlueprintLayout,
|
|
17
|
+
} from '../process-blueprint/types.js';
|
|
18
|
+
import { generateSvgEmbedCss } from '../theme/index.js';
|
|
19
|
+
import { escXml } from './render-util.js';
|
|
20
|
+
|
|
21
|
+
const PAD = 24;
|
|
22
|
+
|
|
23
|
+
function truncate(text: string, maxChars: number): string {
|
|
24
|
+
if (text.length <= maxChars) return text;
|
|
25
|
+
return text.slice(0, Math.max(0, maxChars - 1)) + '…';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Render a left-anchored, vertically centred multi-line text cell. `lines` are
|
|
30
|
+
* pre-wrapped by the layout; the block is centred within the cell height.
|
|
31
|
+
*/
|
|
32
|
+
function textCellSvg(
|
|
33
|
+
lines: string[],
|
|
34
|
+
cls: string,
|
|
35
|
+
x: number,
|
|
36
|
+
cellTop: number,
|
|
37
|
+
cellHeight: number,
|
|
38
|
+
lineHeight: number,
|
|
39
|
+
): string {
|
|
40
|
+
const ls = lines.length > 0 ? lines : [''];
|
|
41
|
+
const first = cellTop + cellHeight / 2 - ((ls.length - 1) / 2) * lineHeight;
|
|
42
|
+
const tspans = ls
|
|
43
|
+
.map((ln, i) => `<tspan x="${x}" y="${first + i * lineHeight}">${escXml(ln)}</tspan>`)
|
|
44
|
+
.join('');
|
|
45
|
+
return `<text class="${cls}" style="dominant-baseline:central">${tspans}</text>`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Render a single compliance law chip.
|
|
50
|
+
*
|
|
51
|
+
* Three orthogonal decorations are expressed as SVG overrides:
|
|
52
|
+
* - `new` — dashed stroke border (`stroke-dasharray="4 2"`)
|
|
53
|
+
* - `gap` — warning-level fill (`class="…compliance-gap"`)
|
|
54
|
+
* - `deadline` — urgent fill override (`class="…compliance-deadline"`) + a small
|
|
55
|
+
* badge circle in the top-right corner
|
|
56
|
+
*/
|
|
57
|
+
function complianceChipSvg(chip: ComplianceChip, ox: number, oy: number): string {
|
|
58
|
+
const { x, y, width, height, lawId, decorations } = chip;
|
|
59
|
+
const ax = x + ox;
|
|
60
|
+
const ay = y + oy;
|
|
61
|
+
const rx = 6;
|
|
62
|
+
const hasNew = decorations.includes('new');
|
|
63
|
+
const hasGap = decorations.includes('gap');
|
|
64
|
+
const hasDeadline = decorations.includes('deadline');
|
|
65
|
+
|
|
66
|
+
let rectClass = 'diagram-node level-5 compliance-chip';
|
|
67
|
+
if (hasDeadline) rectClass += ' compliance-deadline';
|
|
68
|
+
else if (hasGap) rectClass += ' compliance-gap';
|
|
69
|
+
|
|
70
|
+
const strokeDash = hasNew ? ' stroke-dasharray="4 2"' : '';
|
|
71
|
+
const parts: string[] = [];
|
|
72
|
+
parts.push(
|
|
73
|
+
`<rect class="${rectClass}" x="${ax}" y="${ay}" width="${width}" height="${height}" rx="${rx}"${strokeDash}/>`,
|
|
74
|
+
);
|
|
75
|
+
parts.push(
|
|
76
|
+
`<text class="text-pill" x="${ax + width / 2}" y="${ay + height / 2}" text-anchor="middle" style="dominant-baseline:central">${escXml(truncate(lawId, Math.floor(width / 8)))}</text>`,
|
|
77
|
+
);
|
|
78
|
+
if (hasDeadline) {
|
|
79
|
+
const br = 5;
|
|
80
|
+
const bx = ax + width - br - 3;
|
|
81
|
+
const by = ay + br + 3;
|
|
82
|
+
parts.push(`<circle class="compliance-badge" cx="${bx}" cy="${by}" r="${br}"/>`);
|
|
83
|
+
parts.push(
|
|
84
|
+
`<text class="compliance-badge-text" x="${bx}" y="${by}" text-anchor="middle" style="dominant-baseline:central">!</text>`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
return parts.join('\n');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface RenderProcessBlueprintOptions {
|
|
91
|
+
title?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function renderProcessBlueprintSvg(
|
|
95
|
+
doc: ProcessBlueprintFile,
|
|
96
|
+
options: RenderProcessBlueprintOptions = {},
|
|
97
|
+
): string {
|
|
98
|
+
const { title = '' } = options;
|
|
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;
|
|
111
|
+
|
|
112
|
+
const parts: string[] = [];
|
|
113
|
+
|
|
114
|
+
parts.push(
|
|
115
|
+
`<rect class="diagram-node level-0" x="${ox}" y="${oy}" width="${layout.bounds.width}" height="${layout.bounds.height}" rx="6"/>`,
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
for (const s of layout.stageHeaders) {
|
|
119
|
+
parts.push(
|
|
120
|
+
`<rect class="diagram-node level-1" x="${s.x + ox}" y="${s.y + oy}" width="${s.width}" height="${s.height}"/>`,
|
|
121
|
+
);
|
|
122
|
+
parts.push(
|
|
123
|
+
`<text class="text-header" x="${s.x + ox + s.width / 2}" y="${s.y + oy + s.height / 2}" text-anchor="middle" style="dominant-baseline:central">${escXml(truncate(s.name, 28))}</text>`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (const l of layout.legend) {
|
|
128
|
+
parts.push(
|
|
129
|
+
`<rect class="diagram-node level-2" x="${ox}" y="${l.y + oy}" width="${layout.legendColumnWidth}" height="${l.height}"/>`,
|
|
130
|
+
);
|
|
131
|
+
parts.push(
|
|
132
|
+
`<text class="text-primary" x="${ox + 12}" y="${l.y + oy + l.height / 2}" style="dominant-baseline:central">${escXml(l.label)}</text>`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const textX = layout.cellTextPadX;
|
|
137
|
+
for (const c of layout.goalCells) {
|
|
138
|
+
parts.push(
|
|
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),
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
for (const c of layout.resultCells) {
|
|
146
|
+
parts.push(
|
|
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),
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for (let r = 0; r < layout.aspectRows.length; r++) {
|
|
155
|
+
const row = layout.aspectRows[r];
|
|
156
|
+
const level = 5 + (r % 3);
|
|
157
|
+
parts.push(
|
|
158
|
+
`<rect class="diagram-node level-${level}" x="${layout.legendColumnWidth + ox}" y="${row.y + oy}" width="${layout.bounds.width - layout.legendColumnWidth}" height="${row.height}" opacity="0.15"/>`,
|
|
159
|
+
);
|
|
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
|
+
for (const p of row.pills) {
|
|
167
|
+
parts.push(
|
|
168
|
+
`<rect class="diagram-node level-${level}" x="${p.x + ox}" y="${p.y + oy}" width="${p.width}" height="${p.height}" rx="6"/>`,
|
|
169
|
+
);
|
|
170
|
+
const label = p.id ? `${p.name} · ${p.id}` : p.name;
|
|
171
|
+
parts.push(
|
|
172
|
+
`<text class="text-pill" x="${p.x + ox + p.width / 2}" y="${p.y + oy + p.height / 2}" text-anchor="middle" style="dominant-baseline:central">${escXml(truncate(label, Math.floor(p.width / 8)))}</text>`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Compliance row (optional).
|
|
178
|
+
if (layout.complianceRow) {
|
|
179
|
+
const row = layout.complianceRow;
|
|
180
|
+
parts.push(
|
|
181
|
+
`<rect class="diagram-node level-5" x="${layout.legendColumnWidth + ox}" y="${row.y + oy}" width="${layout.bounds.width - layout.legendColumnWidth}" height="${row.height}" opacity="0.10"/>`,
|
|
182
|
+
);
|
|
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
|
+
for (const chip of row.chips) {
|
|
190
|
+
parts.push(complianceChipSvg(chip, ox, oy));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const titleSvg = title
|
|
195
|
+
? `<text class="text-header" x="${PAD}" y="${PAD - 6}">${escXml(title)}</text>`
|
|
196
|
+
: '';
|
|
197
|
+
|
|
198
|
+
// Embed the shared theme CSS inside the SVG so the rendered output is
|
|
199
|
+
// self-contained for the JCEF host — matches render-goals.ts.
|
|
200
|
+
const embedCss = generateSvgEmbedCss('transitrix');
|
|
201
|
+
|
|
202
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
|
|
203
|
+
<style>${embedCss}</style>
|
|
204
|
+
${titleSvg}
|
|
205
|
+
${parts.join('\n')}
|
|
206
|
+
</svg>`;
|
|
207
|
+
}
|