@transitrix/diagrams 1.13.0 → 1.13.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/dist/fgca/preview-layout.d.ts +2 -0
- package/dist/fgca/preview-layout.js +2 -1
- package/dist/fgca/resolver.d.ts +4 -0
- package/dist/fgca/resolver.js +1 -0
- package/dist/webview/render-fgca.d.ts +3 -0
- package/dist/webview/render-fgca.js +15 -8
- package/package.json +2 -2
- package/src/fgca/preview-layout.ts +7 -4
- package/src/fgca/resolver.ts +2 -0
- package/src/webview/render-fgca.ts +18 -7
|
@@ -94,6 +94,8 @@ export interface FGCAPreviewNode {
|
|
|
94
94
|
};
|
|
95
95
|
/** Marks a synthesized node (e.g. virtual Change) vs. a real entity. */
|
|
96
96
|
virtual?: boolean;
|
|
97
|
+
/** For activity nodes: true if the activity is linked to a goal. */
|
|
98
|
+
linked?: boolean;
|
|
97
99
|
}
|
|
98
100
|
export interface FGCAPreviewEdge {
|
|
99
101
|
sx: number;
|
|
@@ -249,7 +249,7 @@ export function layoutFGCAPreview(inputDoc, options = {}) {
|
|
|
249
249
|
...changes.map(c => ({ id: `change_${c.id}`, label: c.name })),
|
|
250
250
|
...virtualChanges.map(v => ({ id: v.id, label: '–', virtual: true })),
|
|
251
251
|
],
|
|
252
|
-
activity: doc.activities.map(a => ({ id: `activity_${a.id}`, label: a.name, progress: a.progress })),
|
|
252
|
+
activity: doc.activities.map(a => ({ id: `activity_${a.id}`, label: a.name, progress: a.progress, linked: a.goal_id != null })),
|
|
253
253
|
};
|
|
254
254
|
// Build predecessor map: for each node, which node IDs in the previous column
|
|
255
255
|
// connect to it from the left. Used for barycenter crossing minimization.
|
|
@@ -321,6 +321,7 @@ export function layoutFGCAPreview(inputDoc, options = {}) {
|
|
|
321
321
|
type: item.type,
|
|
322
322
|
progress: item.progress,
|
|
323
323
|
virtual: item.virtual,
|
|
324
|
+
linked: item.linked,
|
|
324
325
|
};
|
|
325
326
|
nodes.push(node);
|
|
326
327
|
nodeMap.set(item.id, node);
|
package/dist/fgca/resolver.d.ts
CHANGED
|
@@ -33,6 +33,10 @@ export interface FGCAViewConfig {
|
|
|
33
33
|
export interface FGCACanonSources {
|
|
34
34
|
elements: unknown[];
|
|
35
35
|
relations: unknown[];
|
|
36
|
+
progressData?: Map<string, {
|
|
37
|
+
percent: number;
|
|
38
|
+
computedAt: string;
|
|
39
|
+
}>;
|
|
36
40
|
}
|
|
37
41
|
/**
|
|
38
42
|
* Returns true when the parsed view document uses the canon-projection form
|
package/dist/fgca/resolver.js
CHANGED
|
@@ -21,6 +21,7 @@ export interface RenderFgcaBodyOptions {
|
|
|
21
21
|
nodeWidth?: number;
|
|
22
22
|
nodeHeight?: number;
|
|
23
23
|
edgeStyle?: EdgeStyle;
|
|
24
|
+
showCompletionPercent?: boolean;
|
|
24
25
|
}
|
|
25
26
|
/**
|
|
26
27
|
* Canonical DGCA/DGA body — the column headers, node rects and edge paths that
|
|
@@ -46,6 +47,8 @@ export interface RenderFgcaOptions {
|
|
|
46
47
|
layoutOptions?: Parameters<typeof layoutFGCAPreview>[1];
|
|
47
48
|
/** Show scope caption when goal-scoped projection omits goals that reference out-of-scope goals. */
|
|
48
49
|
scopeCaption?: boolean;
|
|
50
|
+
/** Show completion percent badges on action nodes. */
|
|
51
|
+
showCompletionPercent?: boolean;
|
|
49
52
|
}
|
|
50
53
|
export declare function renderFgcaSvg(doc: FGCADoc, options?: RenderFgcaOptions): string;
|
|
51
54
|
export {};
|
|
@@ -37,6 +37,7 @@ export function renderFgcaBody(columns, nodes, edges, curvature, entryCurvature,
|
|
|
37
37
|
const nodeWidth = bodyOptions.nodeWidth ?? FGCA_NODE_W;
|
|
38
38
|
const nodeHeight = bodyOptions.nodeHeight ?? FGCA_NODE_H;
|
|
39
39
|
const edgeStyle = bodyOptions.edgeStyle;
|
|
40
|
+
const showCompletionPercent = bodyOptions.showCompletionPercent ?? true;
|
|
40
41
|
const headerTruncate = Math.max(8, Math.floor((nodeWidth - 16) / 7));
|
|
41
42
|
const STALENESS_THRESHOLD_MS = 7 * 24 * 60 * 60 * 1000;
|
|
42
43
|
const headerSvg = columns
|
|
@@ -64,14 +65,19 @@ export function renderFgcaBody(columns, nodes, edges, curvature, entryCurvature,
|
|
|
64
65
|
const typeBadge = n.type && n.col === 'activity'
|
|
65
66
|
? `<text class="text-id" x="${n.x + nodeWidth - 6}" y="${n.y + 10}" text-anchor="end" dominant-baseline="hanging" font-size="10">${escXml(n.type)}</text>`
|
|
66
67
|
: '';
|
|
67
|
-
const progressBadge =
|
|
68
|
+
const progressBadge = showCompletionPercent && n.col === 'activity'
|
|
68
69
|
? (() => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
if (n.progress) {
|
|
71
|
+
const computedAt = new Date(n.progress.computedAt);
|
|
72
|
+
const now = new Date();
|
|
73
|
+
const isStale = now.getTime() - computedAt.getTime() > STALENESS_THRESHOLD_MS;
|
|
74
|
+
const opacity = isStale ? ' opacity="0.5"' : '';
|
|
75
|
+
return `<text class="text-id" x="${n.x + nodeWidth - 6}" y="${n.y + 22}" text-anchor="end" dominant-baseline="hanging" font-size="10"${opacity}>${escXml(n.progress.percent + '%')}</text>`;
|
|
76
|
+
}
|
|
77
|
+
if (n.linked) {
|
|
78
|
+
return `<text class="text-id" x="${n.x + nodeWidth - 6}" y="${n.y + 22}" text-anchor="end" dominant-baseline="hanging" font-size="10">–%</text>`;
|
|
79
|
+
}
|
|
80
|
+
return '';
|
|
75
81
|
})()
|
|
76
82
|
: '';
|
|
77
83
|
return [
|
|
@@ -88,7 +94,7 @@ export function renderFgcaBody(columns, nodes, edges, curvature, entryCurvature,
|
|
|
88
94
|
return `${headerSvg}\n${nodeSvg}\n${edgeSvg}`;
|
|
89
95
|
}
|
|
90
96
|
export function renderFgcaSvg(doc, options = {}) {
|
|
91
|
-
const { variant = 'dgca', title = '', curvature = DEFAULT_EDGE_CURVATURE, entryCurvature, edgeStyle, nodeSizePreset = 'normal', layoutOptions, scopeCaption = false, } = options;
|
|
97
|
+
const { variant = 'dgca', title = '', curvature = DEFAULT_EDGE_CURVATURE, entryCurvature, edgeStyle, nodeSizePreset = 'normal', layoutOptions, scopeCaption = false, showCompletionPercent = true, } = options;
|
|
92
98
|
const hideChanges = variant === 'dga' || doc.hideChanges === true;
|
|
93
99
|
const nodeSize = resolveDgcaNodeSize(parseNodeSizePreset(nodeSizePreset));
|
|
94
100
|
const { nodes, edges, columns, width, height } = layoutFGCAPreview(doc, {
|
|
@@ -104,6 +110,7 @@ export function renderFgcaSvg(doc, options = {}) {
|
|
|
104
110
|
nodeWidth: nodeSize.width,
|
|
105
111
|
nodeHeight: nodeSize.height,
|
|
106
112
|
edgeStyle,
|
|
113
|
+
showCompletionPercent,
|
|
107
114
|
});
|
|
108
115
|
const titleSvg = title
|
|
109
116
|
? `<text class="text-header" x="${PAD}" y="${PAD - 6}">${escXml(title)}</text>`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@transitrix/diagrams",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Shared rendering, validation, and pure mutations for Transitrix custom diagram formats.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@testing-library/dom": "^10.4.1",
|
|
56
|
-
"@testing-library/react": "^16.3.
|
|
56
|
+
"@testing-library/react": "^16.3.3",
|
|
57
57
|
"@types/react": "^19.2.17",
|
|
58
58
|
"@types/react-dom": "^19.2.5",
|
|
59
59
|
"jsdom": "^29.1.1",
|
|
@@ -315,6 +315,8 @@ export interface FGCAPreviewNode {
|
|
|
315
315
|
};
|
|
316
316
|
/** Marks a synthesized node (e.g. virtual Change) vs. a real entity. */
|
|
317
317
|
virtual?: boolean;
|
|
318
|
+
/** For activity nodes: true if the activity is linked to a goal. */
|
|
319
|
+
linked?: boolean;
|
|
318
320
|
}
|
|
319
321
|
export interface FGCAPreviewEdge {
|
|
320
322
|
sx: number;
|
|
@@ -377,14 +379,14 @@ export function layoutFGCAPreview(
|
|
|
377
379
|
}
|
|
378
380
|
}
|
|
379
381
|
|
|
380
|
-
const colItems: Record<FGCAPreviewColumn, Array<{ id: string; label: string; type?: string; progress?: { percent: number; computedAt: string }; virtual?: boolean }>> = {
|
|
382
|
+
const colItems: Record<FGCAPreviewColumn, Array<{ id: string; label: string; type?: string; progress?: { percent: number; computedAt: string }; virtual?: boolean; linked?: boolean }>> = {
|
|
381
383
|
driver: doc.factors.map(f => ({ id: `driver_${f.id}`, label: f.name })),
|
|
382
384
|
goal: doc.goals.map(g => ({ id: `goal_${g.id}`, label: g.name })),
|
|
383
385
|
change: [
|
|
384
386
|
...changes.map(c => ({ id: `change_${c.id}`, label: c.name })),
|
|
385
387
|
...virtualChanges.map(v => ({ id: v.id, label: '–', virtual: true })),
|
|
386
388
|
],
|
|
387
|
-
activity: doc.activities.map(a => ({ id: `activity_${a.id}`, label: a.name, progress: a.progress })),
|
|
389
|
+
activity: doc.activities.map(a => ({ id: `activity_${a.id}`, label: a.name, progress: a.progress, linked: a.goal_id != null })),
|
|
388
390
|
};
|
|
389
391
|
|
|
390
392
|
// Build predecessor map: for each node, which node IDs in the previous column
|
|
@@ -429,9 +431,9 @@ export function layoutFGCAPreview(
|
|
|
429
431
|
// Nodes with no predecessors sort last (Infinity barycenter) so they don't
|
|
430
432
|
// displace connected nodes.
|
|
431
433
|
function barycentricSort(
|
|
432
|
-
items: Array<{ id: string; label: string; type?: string; progress?: { percent: number; computedAt: string }; virtual?: boolean }>,
|
|
434
|
+
items: Array<{ id: string; label: string; type?: string; progress?: { percent: number; computedAt: string }; virtual?: boolean; linked?: boolean }>,
|
|
433
435
|
yCenters: Map<string, number>,
|
|
434
|
-
): Array<{ id: string; label: string; type?: string; progress?: { percent: number; computedAt: string }; virtual?: boolean }> {
|
|
436
|
+
): Array<{ id: string; label: string; type?: string; progress?: { percent: number; computedAt: string }; virtual?: boolean; linked?: boolean }> {
|
|
435
437
|
return [...items].sort((a, b) => {
|
|
436
438
|
const pA = (predecessors.get(a.id) ?? []).map(p => yCenters.get(p) ?? 0).filter(v => v > 0);
|
|
437
439
|
const pB = (predecessors.get(b.id) ?? []).map(p => yCenters.get(p) ?? 0).filter(v => v > 0);
|
|
@@ -462,6 +464,7 @@ export function layoutFGCAPreview(
|
|
|
462
464
|
type: item.type,
|
|
463
465
|
progress: item.progress,
|
|
464
466
|
virtual: item.virtual,
|
|
467
|
+
linked: item.linked,
|
|
465
468
|
};
|
|
466
469
|
nodes.push(node);
|
|
467
470
|
nodeMap.set(item.id, node);
|
package/src/fgca/resolver.ts
CHANGED
|
@@ -26,6 +26,7 @@ export interface FGCAViewConfig {
|
|
|
26
26
|
export interface FGCACanonSources {
|
|
27
27
|
elements: unknown[];
|
|
28
28
|
relations: unknown[];
|
|
29
|
+
progressData?: Map<string, { percent: number; computedAt: string }>;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
function isObject(v: unknown): v is Record<string, unknown> {
|
|
@@ -192,5 +193,6 @@ export function resolveFGCA(
|
|
|
192
193
|
view_config: viewDoc['view_config'],
|
|
193
194
|
__outOfScopeGoalIds: outOfScopeGoalIds,
|
|
194
195
|
__outOfScopeFactorIds: outOfScopeFactorIds,
|
|
196
|
+
__progressData: sources.progressData,
|
|
195
197
|
};
|
|
196
198
|
}
|
|
@@ -40,6 +40,7 @@ export interface RenderFgcaBodyOptions {
|
|
|
40
40
|
nodeWidth?: number;
|
|
41
41
|
nodeHeight?: number;
|
|
42
42
|
edgeStyle?: EdgeStyle;
|
|
43
|
+
showCompletionPercent?: boolean;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
/**
|
|
@@ -62,6 +63,7 @@ export function renderFgcaBody(
|
|
|
62
63
|
const nodeWidth = bodyOptions.nodeWidth ?? FGCA_NODE_W;
|
|
63
64
|
const nodeHeight = bodyOptions.nodeHeight ?? FGCA_NODE_H;
|
|
64
65
|
const edgeStyle = bodyOptions.edgeStyle;
|
|
66
|
+
const showCompletionPercent = bodyOptions.showCompletionPercent ?? true;
|
|
65
67
|
const headerTruncate = Math.max(8, Math.floor((nodeWidth - 16) / 7));
|
|
66
68
|
const STALENESS_THRESHOLD_MS = 7 * 24 * 60 * 60 * 1000;
|
|
67
69
|
|
|
@@ -97,14 +99,19 @@ export function renderFgcaBody(
|
|
|
97
99
|
const typeBadge = n.type && n.col === 'activity'
|
|
98
100
|
? `<text class="text-id" x="${n.x + nodeWidth - 6}" y="${n.y + 10}" text-anchor="end" dominant-baseline="hanging" font-size="10">${escXml(n.type)}</text>`
|
|
99
101
|
: '';
|
|
100
|
-
const progressBadge =
|
|
102
|
+
const progressBadge = showCompletionPercent && n.col === 'activity'
|
|
101
103
|
? (() => {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
if (n.progress) {
|
|
105
|
+
const computedAt = new Date(n.progress.computedAt);
|
|
106
|
+
const now = new Date();
|
|
107
|
+
const isStale = now.getTime() - computedAt.getTime() > STALENESS_THRESHOLD_MS;
|
|
108
|
+
const opacity = isStale ? ' opacity="0.5"' : '';
|
|
109
|
+
return `<text class="text-id" x="${n.x + nodeWidth - 6}" y="${n.y + 22}" text-anchor="end" dominant-baseline="hanging" font-size="10"${opacity}>${escXml(n.progress.percent + '%')}</text>`;
|
|
110
|
+
}
|
|
111
|
+
if (n.linked) {
|
|
112
|
+
return `<text class="text-id" x="${n.x + nodeWidth - 6}" y="${n.y + 22}" text-anchor="end" dominant-baseline="hanging" font-size="10">–%</text>`;
|
|
113
|
+
}
|
|
114
|
+
return '';
|
|
108
115
|
})()
|
|
109
116
|
: '';
|
|
110
117
|
return [
|
|
@@ -136,6 +143,8 @@ export interface RenderFgcaOptions {
|
|
|
136
143
|
layoutOptions?: Parameters<typeof layoutFGCAPreview>[1];
|
|
137
144
|
/** Show scope caption when goal-scoped projection omits goals that reference out-of-scope goals. */
|
|
138
145
|
scopeCaption?: boolean;
|
|
146
|
+
/** Show completion percent badges on action nodes. */
|
|
147
|
+
showCompletionPercent?: boolean;
|
|
139
148
|
}
|
|
140
149
|
|
|
141
150
|
export function renderFgcaSvg(doc: FGCADoc, options: RenderFgcaOptions = {}): string {
|
|
@@ -148,6 +157,7 @@ export function renderFgcaSvg(doc: FGCADoc, options: RenderFgcaOptions = {}): st
|
|
|
148
157
|
nodeSizePreset = 'normal',
|
|
149
158
|
layoutOptions,
|
|
150
159
|
scopeCaption = false,
|
|
160
|
+
showCompletionPercent = true,
|
|
151
161
|
} = options;
|
|
152
162
|
const hideChanges = variant === 'dga' || doc.hideChanges === true;
|
|
153
163
|
const nodeSize = resolveDgcaNodeSize(parseNodeSizePreset(nodeSizePreset));
|
|
@@ -167,6 +177,7 @@ export function renderFgcaSvg(doc: FGCADoc, options: RenderFgcaOptions = {}): st
|
|
|
167
177
|
nodeWidth: nodeSize.width,
|
|
168
178
|
nodeHeight: nodeSize.height,
|
|
169
179
|
edgeStyle,
|
|
180
|
+
showCompletionPercent,
|
|
170
181
|
});
|
|
171
182
|
|
|
172
183
|
const titleSvg = title
|