@principal-ai/principal-view-react 0.14.4 → 0.14.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/components/GraphRenderer.d.ts +30 -7
- package/dist/components/GraphRenderer.d.ts.map +1 -1
- package/dist/components/GraphRenderer.js +29 -16
- package/dist/components/GraphRenderer.js.map +1 -1
- package/dist/components/NodeTooltip.js +1 -1
- package/dist/components/NodeTooltip.js.map +1 -1
- package/dist/contexts/TooltipPortalContext.d.ts +8 -0
- package/dist/contexts/TooltipPortalContext.d.ts.map +1 -0
- package/dist/contexts/TooltipPortalContext.js +8 -0
- package/dist/contexts/TooltipPortalContext.js.map +1 -0
- package/dist/edges/CustomEdge.d.ts +5 -0
- package/dist/edges/CustomEdge.d.ts.map +1 -1
- package/dist/edges/CustomEdge.js +7 -3
- package/dist/edges/CustomEdge.js.map +1 -1
- package/dist/hooks/useElkLayout.d.ts +66 -0
- package/dist/hooks/useElkLayout.d.ts.map +1 -0
- package/dist/hooks/useElkLayout.js +136 -0
- package/dist/hooks/useElkLayout.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/nodes/otel/OtelSpanConventionNode.js +3 -3
- package/dist/nodes/otel/OtelSpanConventionNode.js.map +1 -1
- package/dist/utils/elkLayout.d.ts +92 -0
- package/dist/utils/elkLayout.d.ts.map +1 -0
- package/dist/utils/elkLayout.js +281 -0
- package/dist/utils/elkLayout.js.map +1 -0
- package/package.json +4 -3
- package/src/components/GraphRenderer.tsx +70 -13
- package/src/components/NodeTooltip.tsx +1 -1
- package/src/contexts/TooltipPortalContext.ts +8 -0
- package/src/edges/CustomEdge.tsx +13 -2
- package/src/hooks/useElkLayout.test.ts +134 -0
- package/src/hooks/useElkLayout.ts +191 -0
- package/src/index.ts +6 -0
- package/src/nodes/otel/OtelSpanConventionNode.tsx +3 -3
- package/src/stories/ElkEdgeRouting.stories.tsx +415 -0
- package/src/stories/SpanBadges.stories.tsx +840 -0
- package/src/utils/elkLayout.test.ts +240 -0
- package/src/utils/elkLayout.ts +412 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ELK (Eclipse Layout Kernel) Layout Utility
|
|
3
|
+
*
|
|
4
|
+
* Provides sophisticated edge routing with orthogonal (circuit-board style) paths
|
|
5
|
+
* that don't overlap and run parallel to each other.
|
|
6
|
+
*/
|
|
7
|
+
import ELK from 'elkjs/lib/elk.bundled.js';
|
|
8
|
+
// Create ELK instance lazily to avoid issues in test environments
|
|
9
|
+
let elkInstance = null;
|
|
10
|
+
function getElkInstance() {
|
|
11
|
+
if (!elkInstance) {
|
|
12
|
+
elkInstance = new ELK();
|
|
13
|
+
}
|
|
14
|
+
return elkInstance;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Convert bend points to SVG path string
|
|
18
|
+
* @public Exported for testing
|
|
19
|
+
*/
|
|
20
|
+
export function pointsToPath(points) {
|
|
21
|
+
if (points.length === 0)
|
|
22
|
+
return '';
|
|
23
|
+
if (points.length === 1)
|
|
24
|
+
return `M ${points[0].x} ${points[0].y}`;
|
|
25
|
+
let path = `M ${points[0].x} ${points[0].y}`;
|
|
26
|
+
for (let i = 1; i < points.length; i++) {
|
|
27
|
+
path += ` L ${points[i].x} ${points[i].y}`;
|
|
28
|
+
}
|
|
29
|
+
return path;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Convert bend points to smooth orthogonal path with rounded corners
|
|
33
|
+
* @public Exported for testing
|
|
34
|
+
*/
|
|
35
|
+
export function pointsToSmoothPath(points, cornerRadius = 8) {
|
|
36
|
+
if (points.length === 0)
|
|
37
|
+
return '';
|
|
38
|
+
if (points.length === 1)
|
|
39
|
+
return `M ${points[0].x} ${points[0].y}`;
|
|
40
|
+
if (points.length === 2) {
|
|
41
|
+
return `M ${points[0].x} ${points[0].y} L ${points[1].x} ${points[1].y}`;
|
|
42
|
+
}
|
|
43
|
+
let path = `M ${points[0].x} ${points[0].y}`;
|
|
44
|
+
for (let i = 1; i < points.length - 1; i++) {
|
|
45
|
+
const prev = points[i - 1];
|
|
46
|
+
const curr = points[i];
|
|
47
|
+
const next = points[i + 1];
|
|
48
|
+
// Calculate distances
|
|
49
|
+
const d1 = Math.sqrt(Math.pow(curr.x - prev.x, 2) + Math.pow(curr.y - prev.y, 2));
|
|
50
|
+
const d2 = Math.sqrt(Math.pow(next.x - curr.x, 2) + Math.pow(next.y - curr.y, 2));
|
|
51
|
+
// Limit corner radius to half the shorter segment
|
|
52
|
+
const maxRadius = Math.min(d1, d2) / 2;
|
|
53
|
+
const radius = Math.min(cornerRadius, maxRadius);
|
|
54
|
+
if (radius < 1) {
|
|
55
|
+
// Too short for curve, just draw line
|
|
56
|
+
path += ` L ${curr.x} ${curr.y}`;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
// Calculate direction vectors
|
|
60
|
+
const dir1 = { x: (curr.x - prev.x) / d1, y: (curr.y - prev.y) / d1 };
|
|
61
|
+
const dir2 = { x: (next.x - curr.x) / d2, y: (next.y - curr.y) / d2 };
|
|
62
|
+
// Calculate arc start and end points
|
|
63
|
+
const arcStart = {
|
|
64
|
+
x: curr.x - dir1.x * radius,
|
|
65
|
+
y: curr.y - dir1.y * radius,
|
|
66
|
+
};
|
|
67
|
+
const arcEnd = {
|
|
68
|
+
x: curr.x + dir2.x * radius,
|
|
69
|
+
y: curr.y + dir2.y * radius,
|
|
70
|
+
};
|
|
71
|
+
// Draw line to arc start, then quadratic curve to arc end
|
|
72
|
+
path += ` L ${arcStart.x} ${arcStart.y}`;
|
|
73
|
+
path += ` Q ${curr.x} ${curr.y} ${arcEnd.x} ${arcEnd.y}`;
|
|
74
|
+
}
|
|
75
|
+
// Final line to last point
|
|
76
|
+
const last = points[points.length - 1];
|
|
77
|
+
path += ` L ${last.x} ${last.y}`;
|
|
78
|
+
return path;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Calculate the midpoint of a path for label positioning
|
|
82
|
+
* @public Exported for testing
|
|
83
|
+
*/
|
|
84
|
+
export function calculatePathMidpoint(points) {
|
|
85
|
+
if (points.length === 0)
|
|
86
|
+
return { x: 0, y: 0 };
|
|
87
|
+
if (points.length === 1)
|
|
88
|
+
return points[0];
|
|
89
|
+
// Calculate total path length
|
|
90
|
+
let totalLength = 0;
|
|
91
|
+
const segmentLengths = [];
|
|
92
|
+
for (let i = 1; i < points.length; i++) {
|
|
93
|
+
const len = Math.sqrt(Math.pow(points[i].x - points[i - 1].x, 2) + Math.pow(points[i].y - points[i - 1].y, 2));
|
|
94
|
+
segmentLengths.push(len);
|
|
95
|
+
totalLength += len;
|
|
96
|
+
}
|
|
97
|
+
// Find midpoint
|
|
98
|
+
const targetLength = totalLength / 2;
|
|
99
|
+
let currentLength = 0;
|
|
100
|
+
for (let i = 0; i < segmentLengths.length; i++) {
|
|
101
|
+
if (currentLength + segmentLengths[i] >= targetLength) {
|
|
102
|
+
// Midpoint is on this segment
|
|
103
|
+
const remaining = targetLength - currentLength;
|
|
104
|
+
const ratio = remaining / segmentLengths[i];
|
|
105
|
+
return {
|
|
106
|
+
x: points[i].x + (points[i + 1].x - points[i].x) * ratio,
|
|
107
|
+
y: points[i].y + (points[i + 1].y - points[i].y) * ratio,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
currentLength += segmentLengths[i];
|
|
111
|
+
}
|
|
112
|
+
// Fallback to last point
|
|
113
|
+
return points[points.length - 1];
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get ELK layout options based on configuration
|
|
117
|
+
*/
|
|
118
|
+
function getElkOptions(options) {
|
|
119
|
+
const { routingStyle = 'orthogonal', nodeSpacing = 50, edgeSpacing = 8, edgeNodeSpacing = 10, direction = 'RIGHT', } = options;
|
|
120
|
+
const baseOptions = {
|
|
121
|
+
'elk.algorithm': 'layered',
|
|
122
|
+
'elk.direction': direction,
|
|
123
|
+
'elk.spacing.nodeNode': String(nodeSpacing),
|
|
124
|
+
'elk.spacing.edgeEdge': String(edgeSpacing),
|
|
125
|
+
'elk.spacing.edgeNode': String(edgeNodeSpacing),
|
|
126
|
+
'elk.layered.spacing.edgeEdgeBetweenLayers': String(edgeSpacing),
|
|
127
|
+
'elk.layered.spacing.edgeNodeBetweenLayers': String(edgeNodeSpacing),
|
|
128
|
+
};
|
|
129
|
+
// Set edge routing style
|
|
130
|
+
switch (routingStyle) {
|
|
131
|
+
case 'orthogonal':
|
|
132
|
+
baseOptions['elk.edgeRouting'] = 'ORTHOGONAL';
|
|
133
|
+
break;
|
|
134
|
+
case 'splines':
|
|
135
|
+
baseOptions['elk.edgeRouting'] = 'SPLINES';
|
|
136
|
+
break;
|
|
137
|
+
case 'polyline':
|
|
138
|
+
baseOptions['elk.edgeRouting'] = 'POLYLINE';
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
return baseOptions;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Compute ELK layout for nodes and edges
|
|
145
|
+
*
|
|
146
|
+
* @param nodes - xyflow nodes
|
|
147
|
+
* @param edges - xyflow edges
|
|
148
|
+
* @param options - Layout options
|
|
149
|
+
* @returns Layout result with edge paths
|
|
150
|
+
*/
|
|
151
|
+
export async function computeElkLayout(nodes, edges, options = {}) {
|
|
152
|
+
const { preserveNodePositions = true } = options;
|
|
153
|
+
// Convert nodes to ELK format - simple, no ports
|
|
154
|
+
const elkNodes = nodes.map((node) => {
|
|
155
|
+
const width = node.measured?.width ?? node.width ?? 200;
|
|
156
|
+
const height = node.measured?.height ?? node.height ?? 100;
|
|
157
|
+
return {
|
|
158
|
+
id: node.id,
|
|
159
|
+
width,
|
|
160
|
+
height,
|
|
161
|
+
x: node.position.x,
|
|
162
|
+
y: node.position.y,
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
// Convert edges to ELK format - simple node references
|
|
166
|
+
const elkEdges = edges.map((edge) => ({
|
|
167
|
+
id: edge.id,
|
|
168
|
+
sources: [edge.source],
|
|
169
|
+
targets: [edge.target],
|
|
170
|
+
}));
|
|
171
|
+
// Create ELK graph
|
|
172
|
+
const elkGraph = {
|
|
173
|
+
id: 'root',
|
|
174
|
+
layoutOptions: getElkOptions(options),
|
|
175
|
+
children: elkNodes,
|
|
176
|
+
edges: elkEdges,
|
|
177
|
+
};
|
|
178
|
+
// Run ELK layout
|
|
179
|
+
const layoutedGraph = await getElkInstance().layout(elkGraph);
|
|
180
|
+
// Build a map of original node positions (what we passed in)
|
|
181
|
+
const originalPositions = new Map();
|
|
182
|
+
for (const node of elkNodes) {
|
|
183
|
+
originalPositions.set(node.id, { x: node.x ?? 0, y: node.y ?? 0 });
|
|
184
|
+
}
|
|
185
|
+
// Build a map of ELK-computed node positions
|
|
186
|
+
const elkPositions = new Map();
|
|
187
|
+
if (layoutedGraph.children) {
|
|
188
|
+
for (const child of layoutedGraph.children) {
|
|
189
|
+
elkPositions.set(child.id, { x: child.x ?? 0, y: child.y ?? 0 });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Extract results
|
|
193
|
+
const edgePaths = new Map();
|
|
194
|
+
const edgeLabelPositions = new Map();
|
|
195
|
+
// Process edges
|
|
196
|
+
if (layoutedGraph.edges) {
|
|
197
|
+
for (const edge of layoutedGraph.edges) {
|
|
198
|
+
if (edge.sections && edge.sections.length > 0) {
|
|
199
|
+
// Get source and target nodes for this edge
|
|
200
|
+
const sourceId = edges.find(e => e.id === edge.id)?.source;
|
|
201
|
+
const targetId = edges.find(e => e.id === edge.id)?.target;
|
|
202
|
+
// Calculate offset needed to translate from ELK positions to original positions
|
|
203
|
+
// We need to figure out which node each point is closest to and offset accordingly
|
|
204
|
+
const sourceOriginal = sourceId ? originalPositions.get(sourceId) : null;
|
|
205
|
+
const sourceElk = sourceId ? elkPositions.get(sourceId) : null;
|
|
206
|
+
const targetOriginal = targetId ? originalPositions.get(targetId) : null;
|
|
207
|
+
const targetElk = targetId ? elkPositions.get(targetId) : null;
|
|
208
|
+
// Collect all points from sections
|
|
209
|
+
const allPoints = [];
|
|
210
|
+
for (const section of edge.sections) {
|
|
211
|
+
allPoints.push(section.startPoint);
|
|
212
|
+
if (section.bendPoints) {
|
|
213
|
+
allPoints.push(...section.bendPoints);
|
|
214
|
+
}
|
|
215
|
+
allPoints.push(section.endPoint);
|
|
216
|
+
}
|
|
217
|
+
// If preserving positions, we need to offset the edge points
|
|
218
|
+
// The edge path is relative to ELK's layout, so we translate it
|
|
219
|
+
if (preserveNodePositions && sourceOriginal && sourceElk && targetOriginal && targetElk) {
|
|
220
|
+
// Calculate the offset from ELK space to original space
|
|
221
|
+
// For the start point, use source node offset
|
|
222
|
+
// For the end point, use target node offset
|
|
223
|
+
// For middle points, interpolate based on x position
|
|
224
|
+
const sourceOffset = {
|
|
225
|
+
x: sourceOriginal.x - sourceElk.x,
|
|
226
|
+
y: sourceOriginal.y - sourceElk.y,
|
|
227
|
+
};
|
|
228
|
+
const targetOffset = {
|
|
229
|
+
x: targetOriginal.x - targetElk.x,
|
|
230
|
+
y: targetOriginal.y - targetElk.y,
|
|
231
|
+
};
|
|
232
|
+
// Get the x-range of the path for interpolation
|
|
233
|
+
const minX = Math.min(...allPoints.map(p => p.x));
|
|
234
|
+
const maxX = Math.max(...allPoints.map(p => p.x));
|
|
235
|
+
const xRange = maxX - minX || 1;
|
|
236
|
+
// Offset each point - interpolate between source and target offset based on x position
|
|
237
|
+
for (let i = 0; i < allPoints.length; i++) {
|
|
238
|
+
const t = (allPoints[i].x - minX) / xRange; // 0 at source, 1 at target
|
|
239
|
+
allPoints[i] = {
|
|
240
|
+
x: allPoints[i].x + sourceOffset.x + (targetOffset.x - sourceOffset.x) * t,
|
|
241
|
+
y: allPoints[i].y + sourceOffset.y + (targetOffset.y - sourceOffset.y) * t,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// Convert to path
|
|
246
|
+
const path = options.routingStyle === 'orthogonal'
|
|
247
|
+
? pointsToSmoothPath(allPoints, 8)
|
|
248
|
+
: pointsToPath(allPoints);
|
|
249
|
+
edgePaths.set(edge.id, path);
|
|
250
|
+
// Calculate label position
|
|
251
|
+
const labelPos = calculatePathMidpoint(allPoints);
|
|
252
|
+
edgeLabelPositions.set(edge.id, labelPos);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// Process nodes (update positions if not preserving)
|
|
257
|
+
const resultNodes = preserveNodePositions
|
|
258
|
+
? nodes
|
|
259
|
+
: nodes.map((node) => {
|
|
260
|
+
const elkNode = layoutedGraph.children?.find((n) => n.id === node.id);
|
|
261
|
+
if (elkNode && elkNode.x !== undefined && elkNode.y !== undefined) {
|
|
262
|
+
return {
|
|
263
|
+
...node,
|
|
264
|
+
position: { x: elkNode.x, y: elkNode.y },
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
return node;
|
|
268
|
+
});
|
|
269
|
+
return {
|
|
270
|
+
nodes: resultNodes,
|
|
271
|
+
edgePaths,
|
|
272
|
+
edgeLabelPositions,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Hook-friendly version that returns a layout function
|
|
277
|
+
*/
|
|
278
|
+
export function createElkLayouter(options = {}) {
|
|
279
|
+
return (nodes, edges) => computeElkLayout(nodes, edges, options);
|
|
280
|
+
}
|
|
281
|
+
//# sourceMappingURL=elkLayout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"elkLayout.js","sourceRoot":"","sources":["../../src/utils/elkLayout.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@principal-ai/principal-view-react",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.6",
|
|
4
4
|
"description": "React components for graph-based principal view framework",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@xyflow/react": "12.0.0",
|
|
18
|
+
"elkjs": "^0.11.1",
|
|
18
19
|
"hast-util-sanitize": "^5.0.2",
|
|
19
20
|
"highlight.js": "^11.11.1",
|
|
20
21
|
"js-yaml": "4.1.1",
|
|
@@ -31,12 +32,12 @@
|
|
|
31
32
|
},
|
|
32
33
|
"peerDependencies": {
|
|
33
34
|
"@principal-ade/industry-theme": "^0.1.7",
|
|
34
|
-
"@principal-ai/principal-view-core": "^0.
|
|
35
|
+
"@principal-ai/principal-view-core": "^0.26.5",
|
|
35
36
|
"react": "^18.0.0 || ^19.0.0",
|
|
36
37
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
|
-
"@principal-ai/principal-view-core": "0.
|
|
40
|
+
"@principal-ai/principal-view-core": "0.26.5",
|
|
40
41
|
"@principal-ade/industry-theme": "0.1.7",
|
|
41
42
|
"@storybook/addon-docs": "10.1.2",
|
|
42
43
|
"@storybook/addon-links": "10.1.2",
|
|
@@ -7,7 +7,6 @@ import React, {
|
|
|
7
7
|
useRef,
|
|
8
8
|
useImperativeHandle,
|
|
9
9
|
forwardRef,
|
|
10
|
-
createContext,
|
|
11
10
|
} from 'react';
|
|
12
11
|
import {
|
|
13
12
|
ReactFlow,
|
|
@@ -59,6 +58,7 @@ import {
|
|
|
59
58
|
convertToXYFlowNodes,
|
|
60
59
|
convertToXYFlowEdges,
|
|
61
60
|
} from '../utils/graphConverter';
|
|
61
|
+
import { useElkLayout, applyElkPathsToEdges } from '../hooks/useElkLayout';
|
|
62
62
|
import {
|
|
63
63
|
getCanvasBounds,
|
|
64
64
|
calculateInitialViewport,
|
|
@@ -66,13 +66,10 @@ import {
|
|
|
66
66
|
} from '../utils/canvasBounds';
|
|
67
67
|
import { GraphEditProvider } from '../contexts/GraphEditContext';
|
|
68
68
|
import { useUndoRedo, type HistoryEntry } from '../hooks/useUndoRedo';
|
|
69
|
+
import { TooltipPortalContext } from '../contexts/TooltipPortalContext';
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
* This allows tooltips to be rendered within the graph container
|
|
73
|
-
* instead of document.body, so they hide properly when tabs switch.
|
|
74
|
-
*/
|
|
75
|
-
export const TooltipPortalContext = createContext<HTMLElement | null>(null);
|
|
71
|
+
// Re-export for backwards compatibility
|
|
72
|
+
export { TooltipPortalContext };
|
|
76
73
|
|
|
77
74
|
/** Position change event for tracking node movements */
|
|
78
75
|
export interface NodePositionChange {
|
|
@@ -277,6 +274,35 @@ interface GraphRendererBaseProps {
|
|
|
277
274
|
*/
|
|
278
275
|
containerHeight?: number;
|
|
279
276
|
|
|
277
|
+
/**
|
|
278
|
+
* ELK layout configuration for circuit-board style edge routing.
|
|
279
|
+
* When enabled, edges are routed with orthogonal paths that don't overlap.
|
|
280
|
+
*/
|
|
281
|
+
elkLayout?: {
|
|
282
|
+
/**
|
|
283
|
+
* Whether ELK layout is enabled
|
|
284
|
+
* @default false
|
|
285
|
+
*/
|
|
286
|
+
enabled: boolean;
|
|
287
|
+
/**
|
|
288
|
+
* Edge routing style
|
|
289
|
+
* - 'orthogonal': Circuit-board style with 90-degree angles (default)
|
|
290
|
+
* - 'splines': Smooth curved edges
|
|
291
|
+
* - 'polyline': Straight line segments
|
|
292
|
+
*/
|
|
293
|
+
routingStyle?: 'orthogonal' | 'splines' | 'polyline';
|
|
294
|
+
/**
|
|
295
|
+
* Minimum spacing between parallel edges in pixels
|
|
296
|
+
* @default 15
|
|
297
|
+
*/
|
|
298
|
+
edgeSpacing?: number;
|
|
299
|
+
/**
|
|
300
|
+
* Spacing between edges and nodes in pixels
|
|
301
|
+
* @default 20
|
|
302
|
+
*/
|
|
303
|
+
edgeNodeSpacing?: number;
|
|
304
|
+
};
|
|
305
|
+
|
|
280
306
|
}
|
|
281
307
|
|
|
282
308
|
/** GraphRenderer props - canvas format only */
|
|
@@ -535,6 +561,13 @@ interface GraphRendererInnerProps {
|
|
|
535
561
|
onCopy?: (selectedNodeIds: string[]) => void;
|
|
536
562
|
/** Pre-calculated initial viewport to avoid zoom animation on mount */
|
|
537
563
|
initialViewport?: Viewport;
|
|
564
|
+
/** ELK layout configuration for circuit-board style edge routing */
|
|
565
|
+
elkLayout?: {
|
|
566
|
+
enabled: boolean;
|
|
567
|
+
routingStyle?: 'orthogonal' | 'splines' | 'polyline';
|
|
568
|
+
edgeSpacing?: number;
|
|
569
|
+
edgeNodeSpacing?: number;
|
|
570
|
+
};
|
|
538
571
|
}
|
|
539
572
|
|
|
540
573
|
/**
|
|
@@ -575,6 +608,7 @@ const GraphRendererInner: React.FC<GraphRendererInnerProps> = ({
|
|
|
575
608
|
onNodeDragStop: onNodeDragStopProp,
|
|
576
609
|
onCopy,
|
|
577
610
|
initialViewport,
|
|
611
|
+
elkLayout,
|
|
578
612
|
}) => {
|
|
579
613
|
const { fitView, fitBounds, getNodes } = useReactFlow();
|
|
580
614
|
const updateNodeInternals = useUpdateNodeInternals();
|
|
@@ -2151,31 +2185,52 @@ const GraphRendererInner: React.FC<GraphRendererInnerProps> = ({
|
|
|
2151
2185
|
});
|
|
2152
2186
|
}, [edges, configuration, violations, animationState.edgeAnimations, showTooltips, selectedEdgeIds, shiftKeyPressed, activeNodeIds, hiddenNodeIds]);
|
|
2153
2187
|
|
|
2188
|
+
// ELK layout for circuit-board style edge routing
|
|
2189
|
+
const { edgePaths: elkEdgePaths, edgeLabelPositions: elkLabelPositions } = useElkLayout(
|
|
2190
|
+
xyflowNodesBase,
|
|
2191
|
+
xyflowEdgesBase,
|
|
2192
|
+
{
|
|
2193
|
+
enabled: elkLayout?.enabled ?? false,
|
|
2194
|
+
routingStyle: elkLayout?.routingStyle ?? 'orthogonal',
|
|
2195
|
+
edgeSpacing: elkLayout?.edgeSpacing ?? 5,
|
|
2196
|
+
edgeNodeSpacing: elkLayout?.edgeNodeSpacing ?? 10,
|
|
2197
|
+
preserveNodePositions: true,
|
|
2198
|
+
}
|
|
2199
|
+
);
|
|
2200
|
+
|
|
2201
|
+
// Apply ELK paths to edges when ELK layout is enabled
|
|
2202
|
+
const xyflowEdgesWithElk = useMemo(() => {
|
|
2203
|
+
if (!elkLayout?.enabled || elkEdgePaths.size === 0) {
|
|
2204
|
+
return xyflowEdgesBase;
|
|
2205
|
+
}
|
|
2206
|
+
return applyElkPathsToEdges(xyflowEdgesBase, elkEdgePaths, elkLabelPositions);
|
|
2207
|
+
}, [elkLayout?.enabled, xyflowEdgesBase, elkEdgePaths, elkLabelPositions]);
|
|
2208
|
+
|
|
2154
2209
|
// Local xyflow edges state for reconnection
|
|
2155
|
-
const [xyflowLocalEdges, setXyflowLocalEdges] = useState<Edge<CustomEdgeData>[]>(
|
|
2210
|
+
const [xyflowLocalEdges, setXyflowLocalEdges] = useState<Edge<CustomEdgeData>[]>(xyflowEdgesWithElk);
|
|
2156
2211
|
|
|
2157
2212
|
// Sync when base edges change (structure changes like add/remove)
|
|
2158
2213
|
const prevBaseEdgesKeyRef2 = useRef(baseEdgesKey);
|
|
2159
2214
|
useEffect(() => {
|
|
2160
2215
|
if (prevBaseEdgesKeyRef2.current !== baseEdgesKey) {
|
|
2161
2216
|
prevBaseEdgesKeyRef2.current = baseEdgesKey;
|
|
2162
|
-
setXyflowLocalEdges(
|
|
2217
|
+
setXyflowLocalEdges(xyflowEdgesWithElk);
|
|
2163
2218
|
}
|
|
2164
|
-
}, [baseEdgesKey,
|
|
2219
|
+
}, [baseEdgesKey, xyflowEdgesWithElk]);
|
|
2165
2220
|
|
|
2166
2221
|
// Set the reset visual state function for use by resetEditState
|
|
2167
2222
|
// This resets both nodes and edges to their original state
|
|
2168
2223
|
useEffect(() => {
|
|
2169
2224
|
resetVisualStateRef.current = () => {
|
|
2170
2225
|
setXyflowLocalNodes(xyflowNodesBase);
|
|
2171
|
-
setXyflowLocalEdges(
|
|
2226
|
+
setXyflowLocalEdges(xyflowEdgesWithElk);
|
|
2172
2227
|
// Notify parent that changes have been cleared
|
|
2173
2228
|
onPendingChangesChange?.(false);
|
|
2174
2229
|
};
|
|
2175
|
-
}, [xyflowNodesBase,
|
|
2230
|
+
}, [xyflowNodesBase, xyflowEdgesWithElk, onPendingChangesChange]);
|
|
2176
2231
|
|
|
2177
2232
|
// Use local edges in edit mode, base edges otherwise
|
|
2178
|
-
const xyflowEdges = editable ? xyflowLocalEdges :
|
|
2233
|
+
const xyflowEdges = editable ? xyflowLocalEdges : xyflowEdgesWithElk;
|
|
2179
2234
|
|
|
2180
2235
|
// Handle edge changes (selection, reconnection, etc.)
|
|
2181
2236
|
const handleEdgesChange = useCallback(
|
|
@@ -2645,6 +2700,7 @@ export const GraphRenderer = forwardRef<GraphRendererHandle, GraphRendererProps>
|
|
|
2645
2700
|
height = '100%',
|
|
2646
2701
|
containerWidth,
|
|
2647
2702
|
containerHeight,
|
|
2703
|
+
elkLayout,
|
|
2648
2704
|
} = props;
|
|
2649
2705
|
const { theme } = useTheme();
|
|
2650
2706
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
@@ -2867,6 +2923,7 @@ export const GraphRenderer = forwardRef<GraphRendererHandle, GraphRendererProps>
|
|
|
2867
2923
|
onNodeDragStop={onNodeDragStop}
|
|
2868
2924
|
onCopy={onCopy}
|
|
2869
2925
|
initialViewport={initialViewport}
|
|
2926
|
+
elkLayout={elkLayout}
|
|
2870
2927
|
/>
|
|
2871
2928
|
</ReactFlowProvider>
|
|
2872
2929
|
</TooltipPortalContext.Provider>
|
|
@@ -2,7 +2,7 @@ import React, { useEffect, useState, useContext } from 'react';
|
|
|
2
2
|
import { createPortal } from 'react-dom';
|
|
3
3
|
import { useTheme } from '@principal-ade/industry-theme';
|
|
4
4
|
import { IndustryMarkdownSlide } from 'themed-markdown';
|
|
5
|
-
import { TooltipPortalContext } from '
|
|
5
|
+
import { TooltipPortalContext } from '../contexts/TooltipPortalContext';
|
|
6
6
|
|
|
7
7
|
export interface OtelInfo {
|
|
8
8
|
kind: 'type' | 'service' | 'instance';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createContext } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Context for providing a portal target for tooltips.
|
|
5
|
+
* This allows tooltips to be rendered within the graph container
|
|
6
|
+
* instead of document.body, so they hide properly when tabs switch.
|
|
7
|
+
*/
|
|
8
|
+
export const TooltipPortalContext = createContext<HTMLElement | null>(null);
|
package/src/edges/CustomEdge.tsx
CHANGED
|
@@ -17,6 +17,10 @@ export interface CustomEdgeData extends Record<string, unknown> {
|
|
|
17
17
|
tooltipsEnabled?: boolean;
|
|
18
18
|
// Whether shift key is currently pressed (for tooltip control)
|
|
19
19
|
shiftKeyPressed?: boolean;
|
|
20
|
+
// ELK-computed path (circuit-board style routing)
|
|
21
|
+
elkPath?: string;
|
|
22
|
+
// ELK-computed label position
|
|
23
|
+
elkLabelPosition?: { x: number; y: number };
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
/**
|
|
@@ -46,6 +50,8 @@ export const CustomEdge: React.FC<EdgeProps<Edge<CustomEdgeData>>> = ({
|
|
|
46
50
|
animationDirection = 'forward',
|
|
47
51
|
tooltipsEnabled = true,
|
|
48
52
|
shiftKeyPressed = false,
|
|
53
|
+
elkPath,
|
|
54
|
+
elkLabelPosition,
|
|
49
55
|
} = edgeProps || ({} as CustomEdgeData);
|
|
50
56
|
|
|
51
57
|
const [particlePosition, setParticlePosition] = useState(0);
|
|
@@ -77,8 +83,8 @@ export const CustomEdge: React.FC<EdgeProps<Edge<CustomEdgeData>>> = ({
|
|
|
77
83
|
const color = hasViolations ? '#D0021B' : edgeColor || typeDefinition.color || '#888';
|
|
78
84
|
const width = typeDefinition.width || 2;
|
|
79
85
|
|
|
80
|
-
// Get
|
|
81
|
-
const [
|
|
86
|
+
// Get edge path - use ELK path if available, otherwise fall back to SmoothStep
|
|
87
|
+
const [defaultPath, defaultLabelX, defaultLabelY] = getSmoothStepPath({
|
|
82
88
|
sourceX,
|
|
83
89
|
sourceY,
|
|
84
90
|
sourcePosition,
|
|
@@ -89,6 +95,11 @@ export const CustomEdge: React.FC<EdgeProps<Edge<CustomEdgeData>>> = ({
|
|
|
89
95
|
offset: 20,
|
|
90
96
|
});
|
|
91
97
|
|
|
98
|
+
// Use ELK-computed path for circuit-board style routing when available
|
|
99
|
+
const edgePath = elkPath || defaultPath;
|
|
100
|
+
const labelX = elkLabelPosition?.x ?? defaultLabelX;
|
|
101
|
+
const labelY = elkLabelPosition?.y ?? defaultLabelY;
|
|
102
|
+
|
|
92
103
|
// Style based on edge type
|
|
93
104
|
const getStrokeStyle = () => {
|
|
94
105
|
switch (typeDefinition.style) {
|