@transitrix/diagrams 1.7.6 → 1.8.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/__tests__/node-size-presets.test.d.ts +1 -0
- package/dist/__tests__/node-size-presets.test.js +30 -0
- package/dist/activities/layout.js +7 -7
- package/dist/activities/types.d.ts +4 -0
- package/dist/blocks/__tests__/layout.test.js +2 -2
- package/dist/blocks/layout.js +3 -2
- package/dist/capability-map/dsm-layout.js +1 -1
- package/dist/capability-map/dsm-schema.d.ts +1 -1
- package/dist/capability-map/dsm-schema.js +1 -1
- package/dist/capability-map/layout-tree.d.ts +6 -2
- package/dist/capability-map/layout-tree.js +12 -10
- package/dist/capability-map/render-capability-tree.d.ts +2 -9
- package/dist/capability-map/render-capability-tree.js +16 -28
- package/dist/fgca/preview-layout.d.ts +10 -6
- package/dist/fgca/preview-layout.js +16 -15
- package/dist/node-size-presets.d.ts +63 -0
- package/dist/node-size-presets.js +117 -0
- package/dist/process-blueprint/layout.js +4 -47
- package/dist/webview/__tests__/entity-text-layout.test.d.ts +1 -0
- package/dist/webview/__tests__/entity-text-layout.test.js +60 -0
- package/dist/webview/__tests__/entry.test.js +3 -3
- package/dist/webview/__tests__/render-blocks.test.js +10 -13
- package/dist/webview/__tests__/render-goals.test.js +41 -0
- package/dist/webview/entity-text-layout.d.ts +95 -0
- package/dist/webview/entity-text-layout.js +277 -0
- package/dist/webview/entry.js +7 -3
- package/dist/webview/notation-style.d.ts +1 -1
- package/dist/webview/notation-style.js +1 -1
- package/dist/webview/render-activities.d.ts +3 -1
- package/dist/webview/render-activities.js +29 -32
- package/dist/webview/render-blocks.d.ts +4 -1
- package/dist/webview/render-blocks.js +27 -108
- package/dist/webview/render-fgca.d.ts +15 -8
- package/dist/webview/render-fgca.js +41 -44
- package/dist/webview/render-goals.d.ts +2 -0
- package/dist/webview/render-goals.js +21 -36
- package/dist/webview/render-process-blueprint.d.ts +4 -10
- package/dist/webview/render-process-blueprint.js +18 -64
- package/package.json +1 -1
- package/src/__tests__/node-size-presets.test.ts +50 -0
- package/src/activities/layout.ts +12 -7
- package/src/activities/types.ts +4 -0
- package/src/blocks/__tests__/layout.test.ts +2 -2
- package/src/blocks/layout.ts +3 -2
- package/src/capability-map/dsm-layout.ts +1 -1
- package/src/capability-map/dsm-schema.ts +1 -1
- package/src/capability-map/index.ts +1 -1
- package/src/capability-map/layout-tree.ts +17 -9
- package/src/capability-map/render-capability-tree.ts +18 -30
- package/src/fgca/preview-layout.ts +24 -17
- package/src/node-size-presets.ts +180 -0
- package/src/process-blueprint/layout.ts +4 -48
- package/src/webview/__tests__/entity-text-layout.test.ts +73 -0
- package/src/webview/__tests__/entry.test.ts +3 -3
- package/src/webview/__tests__/render-blocks.test.ts +11 -14
- package/src/webview/__tests__/render-goals.test.ts +42 -0
- package/src/webview/entity-text-layout.ts +355 -0
- package/src/webview/entry.ts +8 -4
- package/src/webview/notation-style.ts +1 -1
- package/src/webview/render-activities.ts +38 -27
- package/src/webview/render-blocks.ts +35 -118
- package/src/webview/render-fgca.ts +59 -44
- package/src/webview/render-goals.ts +22 -34
- package/src/webview/render-process-blueprint.ts +30 -65
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { ACTION_NODE_SIZE, BLOCKS_LEAF_SIZE, CAPABILITY_MAP_NODE_SIZE, DGCA_NODE_SIZE, ENTITY_NODE_HEIGHT, ENTITY_NODE_SIZE, ENTITY_NODE_WIDTH, GOALS_NODE_SIZE, PROCESS_BLUEPRINT_SIZE, resolveGoalsNodeSize, } from '../node-size-presets.js';
|
|
3
|
+
describe('node-size-presets', () => {
|
|
4
|
+
it('uses shared width and height tiers for entity nodes', () => {
|
|
5
|
+
expect(ENTITY_NODE_WIDTH).toEqual({ compact: 200, normal: 250, wide: 320 });
|
|
6
|
+
expect(ENTITY_NODE_HEIGHT).toEqual({ compact: 72, normal: 80, wide: 96 });
|
|
7
|
+
expect(ENTITY_NODE_SIZE.normal).toEqual({ width: 250, height: 80 });
|
|
8
|
+
expect(ENTITY_NODE_SIZE.compact).toEqual({ width: 200, height: 72 });
|
|
9
|
+
expect(ENTITY_NODE_SIZE.wide).toEqual({ width: 320, height: 96 });
|
|
10
|
+
});
|
|
11
|
+
it('exposes the same entity sizes for every box-based notation', () => {
|
|
12
|
+
for (const table of [GOALS_NODE_SIZE, DGCA_NODE_SIZE, BLOCKS_LEAF_SIZE, ACTION_NODE_SIZE]) {
|
|
13
|
+
expect(table).toBe(ENTITY_NODE_SIZE);
|
|
14
|
+
expect(table.normal).toEqual(ENTITY_NODE_SIZE.normal);
|
|
15
|
+
}
|
|
16
|
+
expect(CAPABILITY_MAP_NODE_SIZE.normal).toEqual({
|
|
17
|
+
nodeWidth: ENTITY_NODE_WIDTH.normal,
|
|
18
|
+
nodeHeight: ENTITY_NODE_HEIGHT.normal,
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
it('scales wide above normal and compact below', () => {
|
|
22
|
+
expect(resolveGoalsNodeSize('wide').height).toBeGreaterThan(resolveGoalsNodeSize('normal').height);
|
|
23
|
+
expect(resolveGoalsNodeSize('compact').height).toBeLessThan(resolveGoalsNodeSize('normal').height);
|
|
24
|
+
expect(resolveGoalsNodeSize('wide').width).toBeGreaterThan(resolveGoalsNodeSize('normal').width);
|
|
25
|
+
});
|
|
26
|
+
it('scales process blueprint from the same tier ratios', () => {
|
|
27
|
+
expect(PROCESS_BLUEPRINT_SIZE.wide.stageColumnWidth).toBeGreaterThan(PROCESS_BLUEPRINT_SIZE.normal.stageColumnWidth);
|
|
28
|
+
expect(PROCESS_BLUEPRINT_SIZE.compact.goalRowHeight).toBeLessThan(PROCESS_BLUEPRINT_SIZE.normal.goalRowHeight);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { computeCpm } from './cpm.js';
|
|
2
|
-
const
|
|
3
|
-
const
|
|
2
|
+
const DEFAULT_NODE_W = 250;
|
|
3
|
+
const DEFAULT_NODE_H = 80;
|
|
4
4
|
const H_GAP = 80;
|
|
5
5
|
const V_GAP = 24;
|
|
6
6
|
export function layoutActivities(doc, options = {}) {
|
|
7
|
-
const { horizontalGap = H_GAP, verticalGap = V_GAP } = options;
|
|
7
|
+
const { horizontalGap = H_GAP, verticalGap = V_GAP, nodeWidth = DEFAULT_NODE_W, nodeHeight = DEFAULT_NODE_H, } = options;
|
|
8
8
|
const activities = doc.activities;
|
|
9
9
|
if (activities.length === 0) {
|
|
10
10
|
return { nodes: [], edges: [], bounds: { x: 0, y: 0, width: 0, height: 0 } };
|
|
@@ -64,21 +64,21 @@ export function layoutActivities(doc, options = {}) {
|
|
|
64
64
|
const colCount = Math.max(0, ...[...cols.keys()]) + 1;
|
|
65
65
|
for (let c = 0; c < colCount; c++) {
|
|
66
66
|
const list = cols.get(c) ?? [];
|
|
67
|
-
const x = c * (
|
|
67
|
+
const x = c * (nodeWidth + horizontalGap);
|
|
68
68
|
let y = 0;
|
|
69
69
|
for (const a of list) {
|
|
70
70
|
const node = {
|
|
71
71
|
id: a.id,
|
|
72
72
|
x,
|
|
73
73
|
y,
|
|
74
|
-
width:
|
|
75
|
-
height:
|
|
74
|
+
width: nodeWidth,
|
|
75
|
+
height: nodeHeight,
|
|
76
76
|
data: a,
|
|
77
77
|
cpm: cpm.get(a.id),
|
|
78
78
|
};
|
|
79
79
|
nodes.push(node);
|
|
80
80
|
nodeMap.set(a.id, node);
|
|
81
|
-
y +=
|
|
81
|
+
y += nodeHeight + verticalGap;
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
// Build edges
|
|
@@ -86,6 +86,10 @@ export interface ActivitiesLayoutOptions {
|
|
|
86
86
|
horizontalGap?: number;
|
|
87
87
|
/** Vertical gap (px) between stacked activity nodes. Default matches the historical hardcoded value. */
|
|
88
88
|
verticalGap?: number;
|
|
89
|
+
/** Activity node width (px). Default 200. */
|
|
90
|
+
nodeWidth?: number;
|
|
91
|
+
/** Activity node height (px). Default 80. */
|
|
92
|
+
nodeHeight?: number;
|
|
89
93
|
}
|
|
90
94
|
export type GanttMode = 'computed' | 'pinned';
|
|
91
95
|
export type GanttBarKind = 'leaf' | 'milestone' | 'phase';
|
|
@@ -108,8 +108,8 @@ describe('layoutNestedBlocks', () => {
|
|
|
108
108
|
},
|
|
109
109
|
});
|
|
110
110
|
const a = layout.blocks[0];
|
|
111
|
-
expect(a.width).toBe(
|
|
112
|
-
expect(a.height).toBe(
|
|
111
|
+
expect(a.width).toBe(250);
|
|
112
|
+
expect(a.height).toBe(80);
|
|
113
113
|
});
|
|
114
114
|
it('honours custom layout options', () => {
|
|
115
115
|
const layout = layoutNestedBlocks({
|
package/dist/blocks/layout.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getLevel, getParentAddress } from './address.js';
|
|
2
2
|
const DEFAULT_NODE_WIDTH = 250;
|
|
3
|
-
const DEFAULT_NODE_HEIGHT =
|
|
3
|
+
const DEFAULT_NODE_HEIGHT = 80;
|
|
4
4
|
const DEFAULT_RANK_SEP = 80;
|
|
5
5
|
const DEFAULT_NODE_SEP = 24;
|
|
6
6
|
/** Width of the virtual organisation-root node — narrower than a capability
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
|
|
2
|
+
// Types for the DSM-migration capability map (docs/internal/extraction/capability-map.md).
|
|
3
3
|
*
|
|
4
4
|
* Distinct from `./types.ts` (`CapabilityNode`/`CapabilityMapFile`), which is
|
|
5
5
|
* Transitrix's own native `*.capability-map.transitrix.yaml` notation — a
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
|
|
2
|
+
// Types for the DSM-migration capability map (docs/internal/extraction/capability-map.md).
|
|
3
3
|
*
|
|
4
4
|
* Distinct from `./types.ts` (`CapabilityNode`/`CapabilityMapFile`), which is
|
|
5
5
|
* Transitrix's own native `*.capability-map.transitrix.yaml` notation — a
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CapabilityNode, CapabilityMapHeader } from './types.js';
|
|
2
2
|
export declare const TREE_NODE_WIDTH = 250;
|
|
3
|
-
export declare const TREE_NODE_HEIGHT =
|
|
3
|
+
export declare const TREE_NODE_HEIGHT = 80;
|
|
4
4
|
/** Gap between sibling columns (node width + rank sep = 330px, matching DSM). */
|
|
5
5
|
export declare const TREE_RANK_SEP = 80;
|
|
6
6
|
/** Vertical gap between sibling nodes. */
|
|
@@ -39,4 +39,8 @@ export interface CapabilityTreeLayout {
|
|
|
39
39
|
};
|
|
40
40
|
levelCounts: CapabilityTreeLevelCounts;
|
|
41
41
|
}
|
|
42
|
-
export
|
|
42
|
+
export interface CapabilityTreeLayoutOptions {
|
|
43
|
+
nodeWidth?: number;
|
|
44
|
+
nodeHeight?: number;
|
|
45
|
+
}
|
|
46
|
+
export declare function layoutCapabilityTree(map: CapabilityMapHeader, collapsedIds?: Set<string>, options?: CapabilityTreeLayoutOptions): CapabilityTreeLayout;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export const TREE_NODE_WIDTH = 250;
|
|
2
|
-
export const TREE_NODE_HEIGHT =
|
|
2
|
+
export const TREE_NODE_HEIGHT = 80;
|
|
3
3
|
/** Gap between sibling columns (node width + rank sep = 330px, matching DSM). */
|
|
4
4
|
export const TREE_RANK_SEP = 80;
|
|
5
5
|
/** Vertical gap between sibling nodes. */
|
|
6
6
|
export const TREE_NODE_SEP = 24;
|
|
7
|
-
export function layoutCapabilityTree(map, collapsedIds = new Set()) {
|
|
7
|
+
export function layoutCapabilityTree(map, collapsedIds = new Set(), options = {}) {
|
|
8
|
+
const nodeWidth = options.nodeWidth ?? TREE_NODE_WIDTH;
|
|
9
|
+
const nodeHeight = options.nodeHeight ?? TREE_NODE_HEIGHT;
|
|
8
10
|
const nodes = [];
|
|
9
11
|
const edges = [];
|
|
10
12
|
const colNextY = new Map();
|
|
@@ -18,12 +20,12 @@ export function layoutCapabilityTree(map, collapsedIds = new Set()) {
|
|
|
18
20
|
const isCollapsed = collapsedIds.has(node.id);
|
|
19
21
|
const hasChildren = Boolean(node.children?.length);
|
|
20
22
|
const visibleChildren = !isCollapsed && node.children?.length ? node.children : [];
|
|
21
|
-
const x = depth * (
|
|
23
|
+
const x = depth * (nodeWidth + TREE_RANK_SEP);
|
|
22
24
|
if (visibleChildren.length === 0) {
|
|
23
25
|
const y = getNextY(depth);
|
|
24
|
-
advanceY(depth,
|
|
25
|
-
nodes.push({ id: node.id, x, y, width:
|
|
26
|
-
return { top: y, bottom: y +
|
|
26
|
+
advanceY(depth, nodeHeight + TREE_NODE_SEP);
|
|
27
|
+
nodes.push({ id: node.id, x, y, width: nodeWidth, height: nodeHeight, data: node, depth, hasChildren, isCollapsed });
|
|
28
|
+
return { top: y, bottom: y + nodeHeight };
|
|
27
29
|
}
|
|
28
30
|
const childSpans = [];
|
|
29
31
|
for (const child of visibleChildren) {
|
|
@@ -32,12 +34,12 @@ export function layoutCapabilityTree(map, collapsedIds = new Set()) {
|
|
|
32
34
|
}
|
|
33
35
|
const spanTop = Math.min(...childSpans.map(s => s.top));
|
|
34
36
|
const spanBottom = Math.max(...childSpans.map(s => s.bottom));
|
|
35
|
-
const parentY = spanTop + (spanBottom - spanTop) / 2 -
|
|
37
|
+
const parentY = spanTop + (spanBottom - spanTop) / 2 - nodeHeight / 2;
|
|
36
38
|
const curColY = getNextY(depth);
|
|
37
39
|
const finalY = Math.max(parentY, curColY);
|
|
38
|
-
advanceY(depth, finalY - curColY +
|
|
39
|
-
nodes.push({ id: node.id, x, y: finalY, width:
|
|
40
|
-
return { top: Math.min(finalY, spanTop), bottom: Math.max(finalY +
|
|
40
|
+
advanceY(depth, finalY - curColY + nodeHeight + TREE_NODE_SEP);
|
|
41
|
+
nodes.push({ id: node.id, x, y: finalY, width: nodeWidth, height: nodeHeight, data: node, depth, hasChildren, isCollapsed });
|
|
42
|
+
return { top: Math.min(finalY, spanTop), bottom: Math.max(finalY + nodeHeight, spanBottom) };
|
|
41
43
|
}
|
|
42
44
|
for (const root of map.capabilities) {
|
|
43
45
|
placeNode(root, 0);
|
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
import type { CapabilityMapHeader } from './types.js';
|
|
2
|
+
import { type NodeSizePreset } from '../node-size-presets.js';
|
|
2
3
|
export interface RenderCapabilityTreeOptions {
|
|
3
4
|
collapsedIds?: Set<string>;
|
|
4
5
|
curvature?: number;
|
|
6
|
+
nodeSizePreset?: NodeSizePreset;
|
|
5
7
|
}
|
|
6
|
-
/**
|
|
7
|
-
* Renders a capability-map as a left-to-right SVG node tree with DSM-matching
|
|
8
|
-
* visual design: depth-banded fills, maturity badges, and +/− collapse buttons.
|
|
9
|
-
*
|
|
10
|
-
* The SVG uses CSS classes (`tree-level-N`, `tree-maturity-N`, `tree-collapse-btn`)
|
|
11
|
-
* that must be resolved by the host's stylesheet (injected via `diagramClassCss()`
|
|
12
|
-
* in themes.ts). Collapse buttons carry `data-node-id` / `data-collapsed` attributes
|
|
13
|
-
* for the webview click handler.
|
|
14
|
-
*/
|
|
15
8
|
export declare function renderCapabilityTreeSvg(map: CapabilityMapHeader, opts?: RenderCapabilityTreeOptions): string;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { layoutCapabilityTree,
|
|
1
|
+
import { layoutCapabilityTree, TREE_NODE_SEP } from './layout-tree.js';
|
|
2
2
|
import { horizontalCubicEdgePath, DEFAULT_EDGE_CURVATURE } from '../edge-path.js';
|
|
3
|
+
import { parseNodeSizePreset, resolveCapabilityMapNodeSize } from '../node-size-presets.js';
|
|
4
|
+
import { maxCharsForInnerWidth, truncateLine } from '../webview/entity-text-layout.js';
|
|
3
5
|
import { escXml } from '../webview/render-util.js';
|
|
4
6
|
const PAD = 24;
|
|
5
7
|
const BTN_R = 8;
|
|
6
8
|
const LEGEND_H = 40;
|
|
7
|
-
const LABEL_MAX_CHARS = 28;
|
|
8
|
-
const LABEL_TRIM_CHARS = 26;
|
|
9
9
|
const BAND_LABELS = ['Levels 0–2', 'Levels 3–4', 'Levels 5+'];
|
|
10
10
|
function depthBand(depth) {
|
|
11
11
|
if (depth <= 2)
|
|
@@ -14,21 +14,10 @@ function depthBand(depth) {
|
|
|
14
14
|
return 1;
|
|
15
15
|
return 2;
|
|
16
16
|
}
|
|
17
|
-
function truncate(s, max, trim) {
|
|
18
|
-
return s.length > max ? s.slice(0, trim) + '…' : s;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Renders a capability-map as a left-to-right SVG node tree with DSM-matching
|
|
22
|
-
* visual design: depth-banded fills, maturity badges, and +/− collapse buttons.
|
|
23
|
-
*
|
|
24
|
-
* The SVG uses CSS classes (`tree-level-N`, `tree-maturity-N`, `tree-collapse-btn`)
|
|
25
|
-
* that must be resolved by the host's stylesheet (injected via `diagramClassCss()`
|
|
26
|
-
* in themes.ts). Collapse buttons carry `data-node-id` / `data-collapsed` attributes
|
|
27
|
-
* for the webview click handler.
|
|
28
|
-
*/
|
|
29
17
|
export function renderCapabilityTreeSvg(map, opts = {}) {
|
|
30
|
-
const { collapsedIds = new Set(), curvature = DEFAULT_EDGE_CURVATURE } = opts;
|
|
31
|
-
const
|
|
18
|
+
const { collapsedIds = new Set(), curvature = DEFAULT_EDGE_CURVATURE, nodeSizePreset = 'normal' } = opts;
|
|
19
|
+
const nodeSize = resolveCapabilityMapNodeSize(parseNodeSizePreset(nodeSizePreset));
|
|
20
|
+
const layout = layoutCapabilityTree(map, collapsedIds, nodeSize);
|
|
32
21
|
if (layout.nodes.length === 0) {
|
|
33
22
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" viewBox="0 0 0 0"></svg>`;
|
|
34
23
|
}
|
|
@@ -53,26 +42,25 @@ export function renderCapabilityTreeSvg(map, opts = {}) {
|
|
|
53
42
|
const y = n.y + oy;
|
|
54
43
|
const band = depthBand(n.depth);
|
|
55
44
|
const mat = Math.max(1, Math.min(5, n.data.current_maturity | 0));
|
|
56
|
-
const nameText = truncate(n.data.name, LABEL_MAX_CHARS, LABEL_TRIM_CHARS);
|
|
57
|
-
const nameTitle = n.data.name.length > LABEL_MAX_CHARS ? escXml(n.data.name) : '';
|
|
58
|
-
// Maturity badge: left side of node
|
|
59
45
|
const badgeW = 28;
|
|
60
46
|
const badgeH = 18;
|
|
47
|
+
const textAreaW = Math.max(0, n.width - badgeW - 26);
|
|
48
|
+
const maxChars = maxCharsForInnerWidth(textAreaW, 7);
|
|
49
|
+
const nameText = truncateLine(n.data.name, maxChars);
|
|
50
|
+
const nameTitle = n.data.name.length > maxChars ? escXml(n.data.name) : '';
|
|
61
51
|
const badgeX = x + 10;
|
|
62
|
-
const badgeY = y + (
|
|
63
|
-
// Text area: right of badge
|
|
52
|
+
const badgeY = y + (n.height - badgeH) / 2;
|
|
64
53
|
const textX = badgeX + badgeW + 8;
|
|
65
|
-
const nameY = y +
|
|
66
|
-
const idY = y +
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
const btnCy = y + TREE_NODE_HEIGHT / 2;
|
|
54
|
+
const nameY = y + n.height / 2 - 8;
|
|
55
|
+
const idY = y + n.height / 2 + 9;
|
|
56
|
+
const btnCx = x + n.width;
|
|
57
|
+
const btnCy = y + n.height / 2;
|
|
70
58
|
const collapseBtn = n.hasChildren
|
|
71
59
|
? `<circle class="tree-collapse-btn" cx="${btnCx}" cy="${btnCy}" r="${BTN_R}" data-node-id="${escXml(n.id)}" data-collapsed="${n.isCollapsed}"/>
|
|
72
60
|
<text class="tree-collapse-lbl" x="${btnCx}" y="${btnCy}" text-anchor="middle">${n.isCollapsed ? '+' : '−'}</text>`
|
|
73
61
|
: '';
|
|
74
62
|
return `<g${nameTitle ? ` title="${nameTitle}"` : ''}>
|
|
75
|
-
<rect class="tree-level-${band}" x="${x}" y="${y}" width="${
|
|
63
|
+
<rect class="tree-level-${band}" x="${x}" y="${y}" width="${n.width}" height="${n.height}" rx="11" stroke="var(--ts-node-stroke,#94a3b8)" stroke-width="3"/>
|
|
76
64
|
<rect class="tree-maturity-${mat}" x="${badgeX}" y="${badgeY}" width="${badgeW}" height="${badgeH}" rx="4"/>
|
|
77
65
|
<text class="text-pill" x="${badgeX + badgeW / 2}" y="${badgeY + badgeH / 2}" text-anchor="middle" fill="white">L${mat}</text>
|
|
78
66
|
<text class="text-primary" x="${textX}" y="${nameY}" dominant-baseline="central">${escXml(nameText)}</text>
|
|
@@ -23,7 +23,7 @@ export interface FGCAPreviewActivity {
|
|
|
23
23
|
name: string;
|
|
24
24
|
goal_id?: number | string | null;
|
|
25
25
|
}
|
|
26
|
-
/** Structural input the preview layout needs — a subset of the parsed
|
|
26
|
+
/** Structural input the preview layout needs — a subset of the parsed DGCA/DGA doc. */
|
|
27
27
|
export interface FGCAPreviewDoc {
|
|
28
28
|
factors: FGCAPreviewFactor[];
|
|
29
29
|
goals: FGCAPreviewGoal[];
|
|
@@ -31,7 +31,7 @@ export interface FGCAPreviewDoc {
|
|
|
31
31
|
activities: FGCAPreviewActivity[];
|
|
32
32
|
}
|
|
33
33
|
export interface FGCAPreviewLayoutOptions {
|
|
34
|
-
/**
|
|
34
|
+
/** DGA hides the Changes column and links Goal → Activity directly. */
|
|
35
35
|
hideChanges?: boolean;
|
|
36
36
|
/** Horizontal gap (px) between columns. Default matches the historical hardcoded value. */
|
|
37
37
|
colGap?: number;
|
|
@@ -39,11 +39,15 @@ export interface FGCAPreviewLayoutOptions {
|
|
|
39
39
|
rowGap?: number;
|
|
40
40
|
/** Trim to a level cap or a single root goal (vkgeorgia/strategy#77). Defaults to 'all'. */
|
|
41
41
|
scope?: Scope;
|
|
42
|
+
/** Entity node width (px). Default {@link FGCA_NODE_W}. */
|
|
43
|
+
nodeWidth?: number;
|
|
44
|
+
/** Entity node height (px). Default {@link FGCA_NODE_H}. */
|
|
45
|
+
nodeHeight?: number;
|
|
42
46
|
}
|
|
43
47
|
/**
|
|
44
|
-
* Trims
|
|
48
|
+
* Trims a DGCA/DGA doc to a scope.
|
|
45
49
|
*
|
|
46
|
-
*
|
|
50
|
+
* DGCA/DGA goals are flat (no parent_id), so:
|
|
47
51
|
* - 'level' → goals with `(level ?? 0) <= maxLevel`.
|
|
48
52
|
* - 'root' → the single goal whose id matches `rootGoalId` (empty when absent).
|
|
49
53
|
*
|
|
@@ -78,8 +82,8 @@ export interface FGCAPreviewLayout {
|
|
|
78
82
|
width: number;
|
|
79
83
|
height: number;
|
|
80
84
|
}
|
|
81
|
-
export declare const FGCA_NODE_W
|
|
82
|
-
export declare const FGCA_NODE_H
|
|
85
|
+
export declare const FGCA_NODE_W: number;
|
|
86
|
+
export declare const FGCA_NODE_H: number;
|
|
83
87
|
export declare const FGCA_HEADER_H = 32;
|
|
84
88
|
export declare const FGCA_PAD = 20;
|
|
85
89
|
export declare const FGCA_DEFAULT_COL_GAP = 160;
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
// Pure column-layout geometry for the static
|
|
1
|
+
// Pure column-layout geometry for the static DGCA / DGA previews.
|
|
2
2
|
//
|
|
3
|
-
// This is the layout the Studio extension's `
|
|
3
|
+
// This is the layout the Studio extension's `dgca-preview.ts` renders to SVG
|
|
4
4
|
// (NOT the ReactFlow `buildFGCALayout` in `./layout.ts`, which targets the
|
|
5
5
|
// interactive web UI). It lives here — rather than inline in the extension —
|
|
6
6
|
// so the gap geometry is unit-testable: the extension has no test harness.
|
|
7
7
|
//
|
|
8
8
|
// No React, no DOM, no vscode. Safe to call in Node.js or a test environment.
|
|
9
|
+
import { ENTITY_NODE_SIZE } from '../node-size-presets.js';
|
|
9
10
|
/**
|
|
10
|
-
* Trims
|
|
11
|
+
* Trims a DGCA/DGA doc to a scope.
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
+
* DGCA/DGA goals are flat (no parent_id), so:
|
|
13
14
|
* - 'level' → goals with `(level ?? 0) <= maxLevel`.
|
|
14
15
|
* - 'root' → the single goal whose id matches `rootGoalId` (empty when absent).
|
|
15
16
|
*
|
|
@@ -38,19 +39,19 @@ export function selectScopedFGCA(doc, scope) {
|
|
|
38
39
|
activities: visibleActivities,
|
|
39
40
|
};
|
|
40
41
|
}
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
export const FGCA_NODE_W =
|
|
44
|
-
export const FGCA_NODE_H =
|
|
42
|
+
// Default node + frame geometry. Inter-node gaps are user-configurable via
|
|
43
|
+
// Inter-node gaps are user-configurable via spacing settings; node size uses presets.
|
|
44
|
+
export const FGCA_NODE_W = ENTITY_NODE_SIZE.normal.width;
|
|
45
|
+
export const FGCA_NODE_H = ENTITY_NODE_SIZE.normal.height;
|
|
45
46
|
export const FGCA_HEADER_H = 32;
|
|
46
47
|
export const FGCA_PAD = 20;
|
|
47
48
|
export const FGCA_DEFAULT_COL_GAP = 160;
|
|
48
49
|
export const FGCA_DEFAULT_ROW_GAP = 20;
|
|
49
50
|
export function layoutFGCAPreview(inputDoc, options = {}) {
|
|
50
|
-
const { hideChanges = false, colGap = FGCA_DEFAULT_COL_GAP, rowGap = FGCA_DEFAULT_ROW_GAP, scope = { mode: 'all' }, } = options;
|
|
51
|
+
const { hideChanges = false, colGap = FGCA_DEFAULT_COL_GAP, rowGap = FGCA_DEFAULT_ROW_GAP, scope = { mode: 'all' }, nodeWidth = FGCA_NODE_W, nodeHeight = FGCA_NODE_H, } = options;
|
|
51
52
|
// Trim to scope first; everything below lays out the visible subset only.
|
|
52
53
|
const doc = selectScopedFGCA(inputDoc, scope);
|
|
53
|
-
const colStride =
|
|
54
|
+
const colStride = nodeWidth + colGap;
|
|
54
55
|
const cols = hideChanges
|
|
55
56
|
? ['driver', 'goal', 'activity']
|
|
56
57
|
: ['driver', 'goal', 'change', 'activity'];
|
|
@@ -110,7 +111,7 @@ export function layoutFGCAPreview(inputDoc, options = {}) {
|
|
|
110
111
|
const nodeMap = new Map();
|
|
111
112
|
const nodes = [];
|
|
112
113
|
const columns = [];
|
|
113
|
-
const yCenters = new Map(); // nodeId → y +
|
|
114
|
+
const yCenters = new Map(); // nodeId → y + nodeHeight / 2
|
|
114
115
|
for (let ci = 0; ci < cols.length; ci++) {
|
|
115
116
|
const col = cols[ci];
|
|
116
117
|
const x = FGCA_PAD + ci * colStride;
|
|
@@ -121,8 +122,8 @@ export function layoutFGCAPreview(inputDoc, options = {}) {
|
|
|
121
122
|
const node = { id: item.id, x, y, label: item.label, col };
|
|
122
123
|
nodes.push(node);
|
|
123
124
|
nodeMap.set(item.id, node);
|
|
124
|
-
yCenters.set(item.id, y +
|
|
125
|
-
y +=
|
|
125
|
+
yCenters.set(item.id, y + nodeHeight / 2);
|
|
126
|
+
y += nodeHeight + rowGap;
|
|
126
127
|
}
|
|
127
128
|
}
|
|
128
129
|
const edges = [];
|
|
@@ -131,7 +132,7 @@ export function layoutFGCAPreview(inputDoc, options = {}) {
|
|
|
131
132
|
const t = nodeMap.get(targetId);
|
|
132
133
|
if (!s || !t)
|
|
133
134
|
return;
|
|
134
|
-
edges.push({ sx: s.x +
|
|
135
|
+
edges.push({ sx: s.x + nodeWidth, sy: s.y + nodeHeight / 2, tx: t.x, ty: t.y + nodeHeight / 2 });
|
|
135
136
|
}
|
|
136
137
|
for (const g of doc.goals) {
|
|
137
138
|
for (const f of (g.factor ?? []))
|
|
@@ -164,7 +165,7 @@ export function layoutFGCAPreview(inputDoc, options = {}) {
|
|
|
164
165
|
}
|
|
165
166
|
}
|
|
166
167
|
}
|
|
167
|
-
const maxNodeBottom = nodes.reduce((m, n) => Math.max(m, n.y +
|
|
168
|
+
const maxNodeBottom = nodes.reduce((m, n) => Math.max(m, n.y + nodeHeight), FGCA_PAD + FGCA_HEADER_H + rowGap + nodeHeight);
|
|
168
169
|
const width = FGCA_PAD * 2 + cols.length * colStride - colGap;
|
|
169
170
|
const height = maxNodeBottom + FGCA_PAD;
|
|
170
171
|
return { nodes, edges, columns, width, height };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Block/cell size presets for diagram renderers.
|
|
3
|
+
*
|
|
4
|
+
* Entity nodes (Goals, DGCA/DGA, Blocks leaves, Activities, Capability Map)
|
|
5
|
+
* share one width × height ladder. Heights use fixed tiers (72 / 80 / 96 px)
|
|
6
|
+
* so name + type + id never collide; widths scale independently (200 / 250 / 320).
|
|
7
|
+
*
|
|
8
|
+
* Process Blueprint uses a separate cell grid, scaled from the same tiers.
|
|
9
|
+
*/
|
|
10
|
+
export type NodeSizePreset = 'compact' | 'normal' | 'wide';
|
|
11
|
+
export declare const NODE_SIZE_PRESET_VALUES: readonly NodeSizePreset[];
|
|
12
|
+
export interface BoxDimensions {
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
}
|
|
16
|
+
export interface BlocksLeafDimensions extends BoxDimensions {
|
|
17
|
+
}
|
|
18
|
+
export interface ProcessBlueprintDimensions {
|
|
19
|
+
legendColumnWidth: number;
|
|
20
|
+
stageColumnWidth: number;
|
|
21
|
+
stageHeaderHeight: number;
|
|
22
|
+
goalRowHeight: number;
|
|
23
|
+
resultRowHeight: number;
|
|
24
|
+
aspectRowMinHeight: number;
|
|
25
|
+
pillHeight: number;
|
|
26
|
+
pillGap: number;
|
|
27
|
+
cellPadding: number;
|
|
28
|
+
textLineHeight: number;
|
|
29
|
+
textCharWidth: number;
|
|
30
|
+
cellTextPadX: number;
|
|
31
|
+
cellTextPadY: number;
|
|
32
|
+
}
|
|
33
|
+
export interface CapabilityMapDimensions {
|
|
34
|
+
nodeWidth: number;
|
|
35
|
+
nodeHeight: number;
|
|
36
|
+
}
|
|
37
|
+
type PresetTable<T> = Record<NodeSizePreset, T>;
|
|
38
|
+
/** Shared entity-node width tiers (px). */
|
|
39
|
+
export declare const ENTITY_NODE_WIDTH: Record<NodeSizePreset, number>;
|
|
40
|
+
/** Shared entity-node height tiers (px) — room for name + type + id. */
|
|
41
|
+
export declare const ENTITY_NODE_HEIGHT: Record<NodeSizePreset, number>;
|
|
42
|
+
/** Canonical entity-node sizes — used by every box-based notation preview. */
|
|
43
|
+
export declare const ENTITY_NODE_SIZE: PresetTable<BoxDimensions>;
|
|
44
|
+
/** @deprecated alias — use {@link ENTITY_NODE_SIZE} */
|
|
45
|
+
export declare const GOALS_NODE_SIZE: PresetTable<BoxDimensions>;
|
|
46
|
+
/** @deprecated alias — use {@link ENTITY_NODE_SIZE} */
|
|
47
|
+
export declare const DGCA_NODE_SIZE: PresetTable<BoxDimensions>;
|
|
48
|
+
/** @deprecated alias — use {@link ENTITY_NODE_SIZE} */
|
|
49
|
+
export declare const BLOCKS_LEAF_SIZE: PresetTable<BoxDimensions>;
|
|
50
|
+
/** @deprecated alias — use {@link ENTITY_NODE_SIZE} */
|
|
51
|
+
export declare const ACTION_NODE_SIZE: PresetTable<BoxDimensions>;
|
|
52
|
+
export declare const CAPABILITY_MAP_NODE_SIZE: PresetTable<CapabilityMapDimensions>;
|
|
53
|
+
export declare const PROCESS_BLUEPRINT_SIZE: PresetTable<ProcessBlueprintDimensions>;
|
|
54
|
+
export type NodeSizeNotation = 'goals' | 'dgca' | 'dga' | 'blocks' | 'action' | 'processBlueprint' | 'capabilityMap';
|
|
55
|
+
/** Parse a VS Code setting value into a preset id (defaults to `normal`). */
|
|
56
|
+
export declare function parseNodeSizePreset(value: string | undefined): NodeSizePreset;
|
|
57
|
+
export declare function resolveGoalsNodeSize(preset: NodeSizePreset): BoxDimensions;
|
|
58
|
+
export declare function resolveDgcaNodeSize(preset: NodeSizePreset): BoxDimensions;
|
|
59
|
+
export declare function resolveBlocksLeafSize(preset: NodeSizePreset): BlocksLeafDimensions;
|
|
60
|
+
export declare function resolveActionNodeSize(preset: NodeSizePreset): BoxDimensions;
|
|
61
|
+
export declare function resolveProcessBlueprintSize(preset: NodeSizePreset): ProcessBlueprintDimensions;
|
|
62
|
+
export declare function resolveCapabilityMapNodeSize(preset: NodeSizePreset): CapabilityMapDimensions;
|
|
63
|
+
export {};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Block/cell size presets for diagram renderers.
|
|
3
|
+
*
|
|
4
|
+
* Entity nodes (Goals, DGCA/DGA, Blocks leaves, Activities, Capability Map)
|
|
5
|
+
* share one width × height ladder. Heights use fixed tiers (72 / 80 / 96 px)
|
|
6
|
+
* so name + type + id never collide; widths scale independently (200 / 250 / 320).
|
|
7
|
+
*
|
|
8
|
+
* Process Blueprint uses a separate cell grid, scaled from the same tiers.
|
|
9
|
+
*/
|
|
10
|
+
export const NODE_SIZE_PRESET_VALUES = ['compact', 'normal', 'wide'];
|
|
11
|
+
/** Shared entity-node width tiers (px). */
|
|
12
|
+
export const ENTITY_NODE_WIDTH = {
|
|
13
|
+
compact: 200,
|
|
14
|
+
normal: 250,
|
|
15
|
+
wide: 320,
|
|
16
|
+
};
|
|
17
|
+
/** Shared entity-node height tiers (px) — room for name + type + id. */
|
|
18
|
+
export const ENTITY_NODE_HEIGHT = {
|
|
19
|
+
compact: 72,
|
|
20
|
+
normal: 80,
|
|
21
|
+
wide: 96,
|
|
22
|
+
};
|
|
23
|
+
function entityNodeSize(preset) {
|
|
24
|
+
return { width: ENTITY_NODE_WIDTH[preset], height: ENTITY_NODE_HEIGHT[preset] };
|
|
25
|
+
}
|
|
26
|
+
/** Canonical entity-node sizes — used by every box-based notation preview. */
|
|
27
|
+
export const ENTITY_NODE_SIZE = {
|
|
28
|
+
compact: entityNodeSize('compact'),
|
|
29
|
+
normal: entityNodeSize('normal'),
|
|
30
|
+
wide: entityNodeSize('wide'),
|
|
31
|
+
};
|
|
32
|
+
/** @deprecated alias — use {@link ENTITY_NODE_SIZE} */
|
|
33
|
+
export const GOALS_NODE_SIZE = ENTITY_NODE_SIZE;
|
|
34
|
+
/** @deprecated alias — use {@link ENTITY_NODE_SIZE} */
|
|
35
|
+
export const DGCA_NODE_SIZE = ENTITY_NODE_SIZE;
|
|
36
|
+
/** @deprecated alias — use {@link ENTITY_NODE_SIZE} */
|
|
37
|
+
export const BLOCKS_LEAF_SIZE = ENTITY_NODE_SIZE;
|
|
38
|
+
/** @deprecated alias — use {@link ENTITY_NODE_SIZE} */
|
|
39
|
+
export const ACTION_NODE_SIZE = ENTITY_NODE_SIZE;
|
|
40
|
+
export const CAPABILITY_MAP_NODE_SIZE = {
|
|
41
|
+
compact: { nodeWidth: ENTITY_NODE_WIDTH.compact, nodeHeight: ENTITY_NODE_HEIGHT.compact },
|
|
42
|
+
normal: { nodeWidth: ENTITY_NODE_WIDTH.normal, nodeHeight: ENTITY_NODE_HEIGHT.normal },
|
|
43
|
+
wide: { nodeWidth: ENTITY_NODE_WIDTH.wide, nodeHeight: ENTITY_NODE_HEIGHT.wide },
|
|
44
|
+
};
|
|
45
|
+
const PROCESS_BLUEPRINT_NORMAL = {
|
|
46
|
+
legendColumnWidth: 140,
|
|
47
|
+
stageColumnWidth: 220,
|
|
48
|
+
stageHeaderHeight: 40,
|
|
49
|
+
goalRowHeight: 56,
|
|
50
|
+
resultRowHeight: 56,
|
|
51
|
+
aspectRowMinHeight: 60,
|
|
52
|
+
pillHeight: 40,
|
|
53
|
+
pillGap: 4,
|
|
54
|
+
cellPadding: 8,
|
|
55
|
+
textLineHeight: 17,
|
|
56
|
+
textCharWidth: 7.5,
|
|
57
|
+
cellTextPadX: 10,
|
|
58
|
+
cellTextPadY: 10,
|
|
59
|
+
};
|
|
60
|
+
function scaleBlueprint(base, widthScale, heightScale) {
|
|
61
|
+
const w = (n) => Math.round(n * widthScale);
|
|
62
|
+
const h = (n) => Math.round(n * heightScale);
|
|
63
|
+
return {
|
|
64
|
+
legendColumnWidth: w(base.legendColumnWidth),
|
|
65
|
+
stageColumnWidth: w(base.stageColumnWidth),
|
|
66
|
+
stageHeaderHeight: h(base.stageHeaderHeight),
|
|
67
|
+
goalRowHeight: h(base.goalRowHeight),
|
|
68
|
+
resultRowHeight: h(base.resultRowHeight),
|
|
69
|
+
aspectRowMinHeight: h(base.aspectRowMinHeight),
|
|
70
|
+
pillHeight: h(base.pillHeight),
|
|
71
|
+
pillGap: base.pillGap,
|
|
72
|
+
cellPadding: base.cellPadding,
|
|
73
|
+
textLineHeight: h(base.textLineHeight),
|
|
74
|
+
textCharWidth: base.textCharWidth,
|
|
75
|
+
cellTextPadX: base.cellTextPadX,
|
|
76
|
+
cellTextPadY: base.cellTextPadY,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const PB_WIDTH_SCALE = {
|
|
80
|
+
compact: ENTITY_NODE_WIDTH.compact / ENTITY_NODE_WIDTH.normal,
|
|
81
|
+
normal: 1,
|
|
82
|
+
wide: ENTITY_NODE_WIDTH.wide / ENTITY_NODE_WIDTH.normal,
|
|
83
|
+
};
|
|
84
|
+
const PB_HEIGHT_SCALE = {
|
|
85
|
+
compact: ENTITY_NODE_HEIGHT.compact / ENTITY_NODE_HEIGHT.normal,
|
|
86
|
+
normal: 1,
|
|
87
|
+
wide: ENTITY_NODE_HEIGHT.wide / ENTITY_NODE_HEIGHT.normal,
|
|
88
|
+
};
|
|
89
|
+
export const PROCESS_BLUEPRINT_SIZE = {
|
|
90
|
+
compact: scaleBlueprint(PROCESS_BLUEPRINT_NORMAL, PB_WIDTH_SCALE.compact, PB_HEIGHT_SCALE.compact),
|
|
91
|
+
normal: PROCESS_BLUEPRINT_NORMAL,
|
|
92
|
+
wide: scaleBlueprint(PROCESS_BLUEPRINT_NORMAL, PB_WIDTH_SCALE.wide, PB_HEIGHT_SCALE.wide),
|
|
93
|
+
};
|
|
94
|
+
/** Parse a VS Code setting value into a preset id (defaults to `normal`). */
|
|
95
|
+
export function parseNodeSizePreset(value) {
|
|
96
|
+
if (value === 'compact' || value === 'wide')
|
|
97
|
+
return value;
|
|
98
|
+
return 'normal';
|
|
99
|
+
}
|
|
100
|
+
export function resolveGoalsNodeSize(preset) {
|
|
101
|
+
return ENTITY_NODE_SIZE[preset];
|
|
102
|
+
}
|
|
103
|
+
export function resolveDgcaNodeSize(preset) {
|
|
104
|
+
return ENTITY_NODE_SIZE[preset];
|
|
105
|
+
}
|
|
106
|
+
export function resolveBlocksLeafSize(preset) {
|
|
107
|
+
return ENTITY_NODE_SIZE[preset];
|
|
108
|
+
}
|
|
109
|
+
export function resolveActionNodeSize(preset) {
|
|
110
|
+
return ENTITY_NODE_SIZE[preset];
|
|
111
|
+
}
|
|
112
|
+
export function resolveProcessBlueprintSize(preset) {
|
|
113
|
+
return PROCESS_BLUEPRINT_SIZE[preset];
|
|
114
|
+
}
|
|
115
|
+
export function resolveCapabilityMapNodeSize(preset) {
|
|
116
|
+
return CAPABILITY_MAP_NODE_SIZE[preset];
|
|
117
|
+
}
|