@transitrix/diagrams 1.12.0 → 1.12.2
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/capability-map/CapabilityCardNode.d.ts +1 -0
- package/dist/capability-map/CapabilityCardNode.js +5 -4
- package/dist/capability-map/CapabilityMapView.js +1 -0
- package/dist/capability-map/dsm-layout.js +5 -4
- package/dist/capability-map/dsm-schema.d.ts +1 -0
- package/dist/edge-path.d.ts +17 -0
- package/dist/edge-path.js +33 -4
- package/dist/fgca/index.d.ts +1 -1
- package/dist/fgca/index.js +1 -1
- package/dist/fgca/preview-layout.d.ts +24 -4
- package/dist/fgca/preview-layout.js +178 -2
- package/dist/goals/GoalNode.d.ts +1 -0
- package/dist/goals/GoalNode.js +5 -4
- package/dist/goals/GoalTreeView.js +1 -0
- package/dist/goals/layout.js +7 -5
- package/dist/goals/types.d.ts +2 -0
- package/dist/scope.d.ts +27 -0
- package/dist/scope.js +40 -0
- package/dist/webview/render-fgca.d.ts +3 -0
- package/dist/webview/render-fgca.js +5 -3
- package/dist/webview/render-goals.d.ts +3 -0
- package/dist/webview/render-goals.js +5 -5
- package/package.json +1 -1
- package/src/capability-map/CapabilityCardNode.tsx +8 -6
- package/src/capability-map/CapabilityMapView.tsx +1 -0
- package/src/capability-map/dsm-layout.ts +5 -4
- package/src/capability-map/dsm-schema.ts +1 -0
- package/src/edge-path.ts +45 -4
- package/src/fgca/index.ts +2 -0
- package/src/fgca/preview-layout.ts +205 -5
- package/src/goals/GoalNode.tsx +8 -6
- package/src/goals/GoalTreeView.tsx +1 -0
- package/src/goals/layout.ts +7 -5
- package/src/goals/types.ts +2 -0
- package/src/scope.ts +61 -0
- package/src/webview/render-fgca.ts +7 -2
- package/src/webview/render-goals.ts +7 -5
package/src/scope.ts
CHANGED
|
@@ -20,12 +20,38 @@ export type Scope =
|
|
|
20
20
|
| { mode: 'level'; maxLevel: number }
|
|
21
21
|
| { mode: 'root'; rootGoalId: string };
|
|
22
22
|
|
|
23
|
+
/** Column filters on a DGCA/DGA chain. Empty / omitted id = All for that column. */
|
|
24
|
+
export type ChainColumnKey = 'driverId' | 'goalId' | 'changeId' | 'activityId';
|
|
25
|
+
|
|
26
|
+
export type ChainScope = {
|
|
27
|
+
mode: 'chain';
|
|
28
|
+
driverId?: string;
|
|
29
|
+
goalId?: string;
|
|
30
|
+
changeId?: string;
|
|
31
|
+
activityId?: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** Scope accepted by DGCA/DGA layout — Goals tree still uses {@link Scope} only. */
|
|
35
|
+
export type FgcaScope = Scope | ChainScope;
|
|
36
|
+
|
|
23
37
|
/** Default scope — render everything. */
|
|
24
38
|
export const SCOPE_ALL: Scope = { mode: 'all' };
|
|
25
39
|
|
|
40
|
+
export function isChainScope(scope: FgcaScope): scope is ChainScope {
|
|
41
|
+
return scope.mode === 'chain';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** True when at least one column filter is set. */
|
|
45
|
+
export function chainScopeActive(scope: ChainScope): boolean {
|
|
46
|
+
return !!(scope.driverId || scope.goalId || scope.changeId || scope.activityId);
|
|
47
|
+
}
|
|
48
|
+
|
|
26
49
|
/** Warning code emitted when a root-mode scope names a goal that isn't in the document. */
|
|
27
50
|
export const SCOPE_MISSING_ROOT_CODE = 'SCOPE-001';
|
|
28
51
|
|
|
52
|
+
/** Warning code emitted when a chain-column filter names an id that isn't in the document. */
|
|
53
|
+
export const SCOPE_MISSING_CHAIN_CODE = 'SCOPE-002';
|
|
54
|
+
|
|
29
55
|
/**
|
|
30
56
|
* Returns the SCOPE-001 warning when `scope` is root-mode and `rootGoalId` is
|
|
31
57
|
* not among the document's goal ids; otherwise null. Goal ids are compared as
|
|
@@ -44,3 +70,38 @@ export function checkScopeRoot(scope: Scope, goalIds: Iterable<string | number>)
|
|
|
44
70
|
message: `Scope root goal "${scope.rootGoalId}" was not found in this document — nothing to show. Clear the scope or pick a goal that exists.`,
|
|
45
71
|
};
|
|
46
72
|
}
|
|
73
|
+
|
|
74
|
+
function idSetHas(ids: Iterable<string | number>, want: string): boolean {
|
|
75
|
+
for (const id of ids) {
|
|
76
|
+
if (String(id) === want) return true;
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Returns a SCOPE-002 warning when a chain filter names an id that is not in
|
|
83
|
+
* the document's corresponding column; otherwise null. Several missing ids
|
|
84
|
+
* are listed in one message.
|
|
85
|
+
*/
|
|
86
|
+
export function checkChainScope(
|
|
87
|
+
scope: ChainScope,
|
|
88
|
+
ids: {
|
|
89
|
+
drivers: Iterable<string | number>;
|
|
90
|
+
goals: Iterable<string | number>;
|
|
91
|
+
changes: Iterable<string | number>;
|
|
92
|
+
activities: Iterable<string | number>;
|
|
93
|
+
},
|
|
94
|
+
hideChanges = false,
|
|
95
|
+
): ValidationWarning | null {
|
|
96
|
+
if (scope.mode !== 'chain') return null;
|
|
97
|
+
const missing: string[] = [];
|
|
98
|
+
if (scope.driverId && !idSetHas(ids.drivers, scope.driverId)) missing.push(`driver "${scope.driverId}"`);
|
|
99
|
+
if (scope.goalId && !idSetHas(ids.goals, scope.goalId)) missing.push(`goal "${scope.goalId}"`);
|
|
100
|
+
if (!hideChanges && scope.changeId && !idSetHas(ids.changes, scope.changeId)) missing.push(`change "${scope.changeId}"`);
|
|
101
|
+
if (scope.activityId && !idSetHas(ids.activities, scope.activityId)) missing.push(`action "${scope.activityId}"`);
|
|
102
|
+
if (missing.length === 0) return null;
|
|
103
|
+
return {
|
|
104
|
+
code: SCOPE_MISSING_CHAIN_CODE,
|
|
105
|
+
message: `Scope ${missing.join(', ')} was not found in this document — nothing to show for that filter. Clear the scope or pick an id that exists.`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
type FGCAPreviewColumn,
|
|
22
22
|
} from '../fgca/preview-layout.js';
|
|
23
23
|
import type { FGCADoc } from '../fgca/validate.js';
|
|
24
|
-
import {
|
|
24
|
+
import { previewEdgePath, DEFAULT_EDGE_CURVATURE, type EdgeStyle } from '../edge-path.js';
|
|
25
25
|
import { parseNodeSizePreset, resolveDgcaNodeSize, type NodeSizePreset } from '../node-size-presets.js';
|
|
26
26
|
import { generateSvgEmbedCss } from '../theme/index.js';
|
|
27
27
|
import { emitCenteredTextSvg, layoutCenteredEntityText, truncateLine } from './entity-text-layout.js';
|
|
@@ -39,6 +39,7 @@ type FgcaLayout = ReturnType<typeof layoutFGCAPreview>;
|
|
|
39
39
|
export interface RenderFgcaBodyOptions {
|
|
40
40
|
nodeWidth?: number;
|
|
41
41
|
nodeHeight?: number;
|
|
42
|
+
edgeStyle?: EdgeStyle;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
/**
|
|
@@ -60,6 +61,7 @@ export function renderFgcaBody(
|
|
|
60
61
|
): string {
|
|
61
62
|
const nodeWidth = bodyOptions.nodeWidth ?? FGCA_NODE_W;
|
|
62
63
|
const nodeHeight = bodyOptions.nodeHeight ?? FGCA_NODE_H;
|
|
64
|
+
const edgeStyle = bodyOptions.edgeStyle;
|
|
63
65
|
const headerTruncate = Math.max(8, Math.floor((nodeWidth - 16) / 7));
|
|
64
66
|
|
|
65
67
|
const headerSvg = columns
|
|
@@ -71,7 +73,7 @@ export function renderFgcaBody(
|
|
|
71
73
|
const edgeSvg = edges
|
|
72
74
|
.map(
|
|
73
75
|
(e) =>
|
|
74
|
-
`<path d="${
|
|
76
|
+
`<path d="${previewEdgePath(e.sx, e.sy, e.tx, e.ty, edgeStyle, curvature, entryCurvature)}" class="diagram-edge" marker-end="url(#arrow)"/>`,
|
|
75
77
|
)
|
|
76
78
|
.join('\n');
|
|
77
79
|
|
|
@@ -108,6 +110,7 @@ export interface RenderFgcaOptions {
|
|
|
108
110
|
curvature?: number;
|
|
109
111
|
/** Entry curvature at the target node; defaults to `curvature` when omitted. */
|
|
110
112
|
entryCurvature?: number;
|
|
113
|
+
edgeStyle?: EdgeStyle;
|
|
111
114
|
nodeSizePreset?: NodeSizePreset;
|
|
112
115
|
layoutOptions?: Parameters<typeof layoutFGCAPreview>[1];
|
|
113
116
|
/** Show scope caption when goal-scoped projection omits goals that reference out-of-scope goals. */
|
|
@@ -120,6 +123,7 @@ export function renderFgcaSvg(doc: FGCADoc, options: RenderFgcaOptions = {}): st
|
|
|
120
123
|
title = '',
|
|
121
124
|
curvature = DEFAULT_EDGE_CURVATURE,
|
|
122
125
|
entryCurvature,
|
|
126
|
+
edgeStyle,
|
|
123
127
|
nodeSizePreset = 'normal',
|
|
124
128
|
layoutOptions,
|
|
125
129
|
scopeCaption = false,
|
|
@@ -141,6 +145,7 @@ export function renderFgcaSvg(doc: FGCADoc, options: RenderFgcaOptions = {}): st
|
|
|
141
145
|
const body = renderFgcaBody(columns, nodes, edges, curvature, entryCurvature, {
|
|
142
146
|
nodeWidth: nodeSize.width,
|
|
143
147
|
nodeHeight: nodeSize.height,
|
|
148
|
+
edgeStyle,
|
|
144
149
|
});
|
|
145
150
|
|
|
146
151
|
const titleSvg = title
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { layoutGoalTree } from '../goals/layout.js';
|
|
14
14
|
import type { GoalTree, GoalTreeLayout, LaidOutEdge } from '../goals/types.js';
|
|
15
15
|
import { displayGoalId } from '../goals/parse-canonical.js';
|
|
16
|
-
import {
|
|
16
|
+
import { previewEdgePath, DEFAULT_EDGE_CURVATURE, type EdgeStyle } from '../edge-path.js';
|
|
17
17
|
import { parseNodeSizePreset, resolveGoalsNodeSize, type NodeSizePreset } from '../node-size-presets.js';
|
|
18
18
|
import { generateSvgEmbedCss, type ThemeId } from '../theme/index.js';
|
|
19
19
|
import { emitCenteredTextSvg, layoutCenteredEntityText } from './entity-text-layout.js';
|
|
@@ -28,6 +28,7 @@ export interface RenderGoalsOptions {
|
|
|
28
28
|
treeName?: string;
|
|
29
29
|
curvature?: number;
|
|
30
30
|
entryCurvature?: number;
|
|
31
|
+
edgeStyle?: EdgeStyle;
|
|
31
32
|
nodeSizePreset?: NodeSizePreset;
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -37,7 +38,7 @@ export interface RenderGoalsOptions {
|
|
|
37
38
|
* with the shared theme CSS embedded so the output is self-contained.
|
|
38
39
|
*/
|
|
39
40
|
export function renderGoalsSvg(tree: GoalTree, options: RenderGoalsOptions = {}): string {
|
|
40
|
-
const { treeName = '', curvature = DEFAULT_EDGE_CURVATURE, entryCurvature, nodeSizePreset = 'normal' } = options;
|
|
41
|
+
const { treeName = '', curvature = DEFAULT_EDGE_CURVATURE, entryCurvature, edgeStyle, nodeSizePreset = 'normal' } = options;
|
|
41
42
|
const nodeSize = resolveGoalsNodeSize(parseNodeSizePreset(nodeSizePreset));
|
|
42
43
|
|
|
43
44
|
const layout: GoalTreeLayout = layoutGoalTree(tree, {
|
|
@@ -55,12 +56,13 @@ export function renderGoalsSvg(tree: GoalTree, options: RenderGoalsOptions = {})
|
|
|
55
56
|
? `<text class="text-header" x="${PAD}" y="${PAD - 6}">${escXml(`Goal tree — ${treeName}`)}</text>`
|
|
56
57
|
: '';
|
|
57
58
|
|
|
58
|
-
return renderGoalsLayoutSvg(layout, { curvature, entryCurvature, title, embedCssTheme: 'transitrix' });
|
|
59
|
+
return renderGoalsLayoutSvg(layout, { curvature, entryCurvature, edgeStyle, title, embedCssTheme: 'transitrix' });
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
export interface RenderGoalsLayoutOptions {
|
|
62
63
|
curvature?: number;
|
|
63
64
|
entryCurvature?: number;
|
|
65
|
+
edgeStyle?: EdgeStyle;
|
|
64
66
|
/** Extra vertical space reserved at the top of the canvas (e.g. for a title block). */
|
|
65
67
|
topInset?: number;
|
|
66
68
|
/** Raw SVG injected immediately after `<defs>` — a header line or a full title block. */
|
|
@@ -78,7 +80,7 @@ export interface RenderGoalsLayoutOptions {
|
|
|
78
80
|
* and the export path own styling).
|
|
79
81
|
*/
|
|
80
82
|
export function renderGoalsLayoutSvg(layout: GoalTreeLayout, options: RenderGoalsLayoutOptions = {}): string {
|
|
81
|
-
const { curvature = DEFAULT_EDGE_CURVATURE, entryCurvature, topInset = 0, title = '', embedCssTheme } = options;
|
|
83
|
+
const { curvature = DEFAULT_EDGE_CURVATURE, entryCurvature, edgeStyle, topInset = 0, title = '', embedCssTheme } = options;
|
|
82
84
|
|
|
83
85
|
const w = layout.bounds.width + PAD * 2;
|
|
84
86
|
const h = layout.bounds.height + PAD * 2 + topInset;
|
|
@@ -95,7 +97,7 @@ export function renderGoalsLayoutSvg(layout: GoalTreeLayout, options: RenderGoal
|
|
|
95
97
|
const sy = s.y + oy + s.height / 2;
|
|
96
98
|
const tx = t.x + ox;
|
|
97
99
|
const ty = t.y + oy + t.height / 2;
|
|
98
|
-
return
|
|
100
|
+
return previewEdgePath(sx, sy, tx, ty, edgeStyle, curvature, entryCurvature);
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
const edgeSvg = layout.edges
|