@transitrix/diagrams 1.8.4 → 1.8.6
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__/edge-path.test.js +8 -0
- package/dist/edge-path.js +10 -1
- package/dist/webview/render-activities.d.ts +1 -1
- package/dist/webview/render-activities.js +2 -2
- package/dist/webview/render-fgca.js +1 -4
- package/package.json +1 -1
- package/src/__tests__/edge-path.test.ts +9 -0
- package/src/edge-path.ts +15 -1
- package/src/webview/render-activities.ts +2 -2
- package/src/webview/render-fgca.ts +1 -4
|
@@ -44,4 +44,12 @@ describe('horizontalCubicEdgePath', () => {
|
|
|
44
44
|
it('omitting entryCurvature gives symmetric handles (historical behaviour)', () => {
|
|
45
45
|
expect(horizontalCubicEdgePath(0, 0, 40, 0, 0.5)).toBe(horizontalCubicEdgePath(0, 0, 40, 0, 0.5, 0.5));
|
|
46
46
|
});
|
|
47
|
+
it('caps the handle for a tall edge with a narrow column gap, instead of overshooting the target', () => {
|
|
48
|
+
// A network-view edge spanning several rows (|dy|=500) with a typical
|
|
49
|
+
// adjacent-column gap (dx=80): uncapped, |dy|*0.8=400 would push the exit
|
|
50
|
+
// control point 320px past the target's x, producing an exaggerated
|
|
51
|
+
// S-bow. Capped at 160, the control points stay close to the endpoints.
|
|
52
|
+
const d = horizontalCubicEdgePath(0, 0, 80, 500, 1);
|
|
53
|
+
expect(d).toBe('M0,0 C160,0 -80,500 80,500');
|
|
54
|
+
});
|
|
47
55
|
});
|
package/dist/edge-path.js
CHANGED
|
@@ -15,6 +15,15 @@
|
|
|
15
15
|
export const EDGE_MIN_HANDLE = 64;
|
|
16
16
|
const DX_FACTOR = 0.5;
|
|
17
17
|
const DY_FACTOR = 0.8;
|
|
18
|
+
/**
|
|
19
|
+
* Ceiling on each span factor's contribution. Uncapped, a tall edge with a
|
|
20
|
+
* narrow column gap (large dy, small dx — the common case in a many-row
|
|
21
|
+
* network view) grows a handle far past the endpoints' actual x-distance;
|
|
22
|
+
* the two control points then overshoot each other, producing an
|
|
23
|
+
* exaggerated S-bow instead of a gentle curve. Capping keeps typical spans
|
|
24
|
+
* (within the existing historical range) untouched while reining in outliers.
|
|
25
|
+
*/
|
|
26
|
+
const MAX_HANDLE = 160;
|
|
18
27
|
/** Multiplier applied to the base handle length. 1 = historical appearance. */
|
|
19
28
|
export const DEFAULT_EDGE_CURVATURE = 1;
|
|
20
29
|
/**
|
|
@@ -33,7 +42,7 @@ export const DEFAULT_EDGE_CURVATURE = 1;
|
|
|
33
42
|
export function horizontalCubicEdgePath(sx, sy, tx, ty, curvature = DEFAULT_EDGE_CURVATURE, entryCurvature) {
|
|
34
43
|
const dx = tx - sx;
|
|
35
44
|
const dy = ty - sy;
|
|
36
|
-
const baseHandle = Math.max(EDGE_MIN_HANDLE, Math.abs(dx) * DX_FACTOR, Math.abs(dy) * DY_FACTOR);
|
|
45
|
+
const baseHandle = Math.max(EDGE_MIN_HANDLE, Math.min(Math.abs(dx) * DX_FACTOR, MAX_HANDLE), Math.min(Math.abs(dy) * DY_FACTOR, MAX_HANDLE));
|
|
37
46
|
const exitHandle = baseHandle * curvature;
|
|
38
47
|
const entryHandle = baseHandle * (entryCurvature ?? curvature);
|
|
39
48
|
return `M${sx},${sy} C${sx + exitHandle},${sy} ${tx - entryHandle},${ty} ${tx},${ty}`;
|
|
@@ -7,7 +7,7 @@ import { type NodeSizePreset } from '../node-size-presets.js';
|
|
|
7
7
|
* network subset. Embedded alongside the shared theme CSS so the SVG is
|
|
8
8
|
* self-contained for the JCEF host.
|
|
9
9
|
*/
|
|
10
|
-
export declare const ACTIVITIES_NETWORK_CSS = "\n .act-node { fill: var(--ts-layer-activity, #d4edda); stroke: var(--ts-node-stroke, #004d67); stroke-width: 1; }\n .critical-node { fill: var(--ts-brand-orange-tint, #ffeee5); stroke: var(--ts-brand-orange, #ff4d00); stroke-width:
|
|
10
|
+
export declare const ACTIVITIES_NETWORK_CSS = "\n .act-node { fill: var(--ts-layer-activity, #d4edda); stroke: var(--ts-node-stroke, #004d67); stroke-width: 1; }\n .critical-node { fill: var(--ts-brand-orange-tint, #ffeee5); stroke: var(--ts-brand-orange, #ff4d00); stroke-width: 1; }\n .milestone-node { fill: #ecfeff; stroke: var(--ts-text-muted, #64748b); stroke-dasharray: 4 2; }\n .critical-edge { stroke: var(--ts-brand-orange, #ff4d00); stroke-width: 1.5; }\n .arrow-fill-critical { fill: var(--ts-brand-orange, #ff4d00); }\n";
|
|
11
11
|
/** Arrowhead marker defs shared by both hosts for the network view. */
|
|
12
12
|
export declare const ACTIVITIES_NETWORK_DEFS = "<defs>\n <marker id=\"arrow\" markerWidth=\"8\" markerHeight=\"8\" refX=\"8\" refY=\"3\" orient=\"auto\">\n <path d=\"M0,0 L0,6 L8,3 z\" class=\"arrow-fill\"/>\n </marker>\n <marker id=\"arrow-crit\" markerWidth=\"8\" markerHeight=\"8\" refX=\"8\" refY=\"3\" orient=\"auto\">\n <path d=\"M0,0 L0,6 L8,3 z\" class=\"arrow-fill-critical\"/>\n </marker>\n</defs>";
|
|
13
13
|
/**
|
|
@@ -32,9 +32,9 @@ const N_PAD = 24;
|
|
|
32
32
|
*/
|
|
33
33
|
export const ACTIVITIES_NETWORK_CSS = `
|
|
34
34
|
.act-node { fill: var(--ts-layer-activity, #d4edda); stroke: var(--ts-node-stroke, #004d67); stroke-width: 1; }
|
|
35
|
-
.critical-node { fill: var(--ts-brand-orange-tint, #ffeee5); stroke: var(--ts-brand-orange, #ff4d00); stroke-width:
|
|
35
|
+
.critical-node { fill: var(--ts-brand-orange-tint, #ffeee5); stroke: var(--ts-brand-orange, #ff4d00); stroke-width: 1; }
|
|
36
36
|
.milestone-node { fill: #ecfeff; stroke: var(--ts-text-muted, #64748b); stroke-dasharray: 4 2; }
|
|
37
|
-
.critical-edge { stroke: var(--ts-brand-orange, #ff4d00); stroke-width:
|
|
37
|
+
.critical-edge { stroke: var(--ts-brand-orange, #ff4d00); stroke-width: 1.5; }
|
|
38
38
|
.arrow-fill-critical { fill: var(--ts-brand-orange, #ff4d00); }
|
|
39
39
|
`;
|
|
40
40
|
/** Arrowhead marker defs shared by both hosts for the network view. */
|
|
@@ -38,10 +38,7 @@ export function renderFgcaBody(columns, nodes, edges, curvature, entryCurvature,
|
|
|
38
38
|
const nodeHeight = bodyOptions.nodeHeight ?? FGCA_NODE_H;
|
|
39
39
|
const headerTruncate = Math.max(8, Math.floor((nodeWidth - 16) / 7));
|
|
40
40
|
const headerSvg = columns
|
|
41
|
-
.map(({ col, x }) => [
|
|
42
|
-
`<rect class="diagram-node layer-${col}" x="${x}" y="${PAD}" width="${nodeWidth}" height="${HEADER_H}" rx="6"/>`,
|
|
43
|
-
`<text class="text-header" x="${x + nodeWidth / 2}" y="${PAD + HEADER_H / 2}" text-anchor="middle" dominant-baseline="central">${escXml(truncateLine(COL_LABELS[col], headerTruncate))}</text>`,
|
|
44
|
-
].join('\n'))
|
|
41
|
+
.map(({ col, x }) => `<text class="text-header" x="${x + nodeWidth / 2}" y="${PAD + HEADER_H / 2}" text-anchor="middle" dominant-baseline="central">${escXml(truncateLine(COL_LABELS[col], headerTruncate))}</text>`)
|
|
45
42
|
.join('\n');
|
|
46
43
|
const edgeSvg = edges
|
|
47
44
|
.map((e) => `<path d="${horizontalCubicEdgePath(e.sx, e.sy, e.tx, e.ty, curvature, entryCurvature)}" class="diagram-edge" marker-end="url(#arrow)"/>`)
|
package/package.json
CHANGED
|
@@ -55,4 +55,13 @@ describe('horizontalCubicEdgePath', () => {
|
|
|
55
55
|
horizontalCubicEdgePath(0, 0, 40, 0, 0.5, 0.5),
|
|
56
56
|
);
|
|
57
57
|
});
|
|
58
|
+
|
|
59
|
+
it('caps the handle for a tall edge with a narrow column gap, instead of overshooting the target', () => {
|
|
60
|
+
// A network-view edge spanning several rows (|dy|=500) with a typical
|
|
61
|
+
// adjacent-column gap (dx=80): uncapped, |dy|*0.8=400 would push the exit
|
|
62
|
+
// control point 320px past the target's x, producing an exaggerated
|
|
63
|
+
// S-bow. Capped at 160, the control points stay close to the endpoints.
|
|
64
|
+
const d = horizontalCubicEdgePath(0, 0, 80, 500, 1);
|
|
65
|
+
expect(d).toBe('M0,0 C160,0 -80,500 80,500');
|
|
66
|
+
});
|
|
58
67
|
});
|
package/src/edge-path.ts
CHANGED
|
@@ -17,6 +17,16 @@ export const EDGE_MIN_HANDLE = 64;
|
|
|
17
17
|
const DX_FACTOR = 0.5;
|
|
18
18
|
const DY_FACTOR = 0.8;
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Ceiling on each span factor's contribution. Uncapped, a tall edge with a
|
|
22
|
+
* narrow column gap (large dy, small dx — the common case in a many-row
|
|
23
|
+
* network view) grows a handle far past the endpoints' actual x-distance;
|
|
24
|
+
* the two control points then overshoot each other, producing an
|
|
25
|
+
* exaggerated S-bow instead of a gentle curve. Capping keeps typical spans
|
|
26
|
+
* (within the existing historical range) untouched while reining in outliers.
|
|
27
|
+
*/
|
|
28
|
+
const MAX_HANDLE = 160;
|
|
29
|
+
|
|
20
30
|
/** Multiplier applied to the base handle length. 1 = historical appearance. */
|
|
21
31
|
export const DEFAULT_EDGE_CURVATURE = 1;
|
|
22
32
|
|
|
@@ -43,7 +53,11 @@ export function horizontalCubicEdgePath(
|
|
|
43
53
|
): string {
|
|
44
54
|
const dx = tx - sx;
|
|
45
55
|
const dy = ty - sy;
|
|
46
|
-
const baseHandle = Math.max(
|
|
56
|
+
const baseHandle = Math.max(
|
|
57
|
+
EDGE_MIN_HANDLE,
|
|
58
|
+
Math.min(Math.abs(dx) * DX_FACTOR, MAX_HANDLE),
|
|
59
|
+
Math.min(Math.abs(dy) * DY_FACTOR, MAX_HANDLE),
|
|
60
|
+
);
|
|
47
61
|
const exitHandle = baseHandle * curvature;
|
|
48
62
|
const entryHandle = baseHandle * (entryCurvature ?? curvature);
|
|
49
63
|
return `M${sx},${sy} C${sx + exitHandle},${sy} ${tx - entryHandle},${ty} ${tx},${ty}`;
|
|
@@ -39,9 +39,9 @@ const N_PAD = 24;
|
|
|
39
39
|
*/
|
|
40
40
|
export const ACTIVITIES_NETWORK_CSS = `
|
|
41
41
|
.act-node { fill: var(--ts-layer-activity, #d4edda); stroke: var(--ts-node-stroke, #004d67); stroke-width: 1; }
|
|
42
|
-
.critical-node { fill: var(--ts-brand-orange-tint, #ffeee5); stroke: var(--ts-brand-orange, #ff4d00); stroke-width:
|
|
42
|
+
.critical-node { fill: var(--ts-brand-orange-tint, #ffeee5); stroke: var(--ts-brand-orange, #ff4d00); stroke-width: 1; }
|
|
43
43
|
.milestone-node { fill: #ecfeff; stroke: var(--ts-text-muted, #64748b); stroke-dasharray: 4 2; }
|
|
44
|
-
.critical-edge { stroke: var(--ts-brand-orange, #ff4d00); stroke-width:
|
|
44
|
+
.critical-edge { stroke: var(--ts-brand-orange, #ff4d00); stroke-width: 1.5; }
|
|
45
45
|
.arrow-fill-critical { fill: var(--ts-brand-orange, #ff4d00); }
|
|
46
46
|
`;
|
|
47
47
|
|
|
@@ -64,10 +64,7 @@ export function renderFgcaBody(
|
|
|
64
64
|
|
|
65
65
|
const headerSvg = columns
|
|
66
66
|
.map(({ col, x }) =>
|
|
67
|
-
[
|
|
68
|
-
`<rect class="diagram-node layer-${col}" x="${x}" y="${PAD}" width="${nodeWidth}" height="${HEADER_H}" rx="6"/>`,
|
|
69
|
-
`<text class="text-header" x="${x + nodeWidth / 2}" y="${PAD + HEADER_H / 2}" text-anchor="middle" dominant-baseline="central">${escXml(truncateLine(COL_LABELS[col], headerTruncate))}</text>`,
|
|
70
|
-
].join('\n'),
|
|
67
|
+
`<text class="text-header" x="${x + nodeWidth / 2}" y="${PAD + HEADER_H / 2}" text-anchor="middle" dominant-baseline="central">${escXml(truncateLine(COL_LABELS[col], headerTruncate))}</text>`,
|
|
71
68
|
)
|
|
72
69
|
.join('\n');
|
|
73
70
|
|