@principal-ai/principal-view-react 0.14.5 → 0.14.7

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.
Files changed (38) hide show
  1. package/dist/components/GraphRenderer.d.ts +30 -7
  2. package/dist/components/GraphRenderer.d.ts.map +1 -1
  3. package/dist/components/GraphRenderer.js +29 -16
  4. package/dist/components/GraphRenderer.js.map +1 -1
  5. package/dist/components/NodeTooltip.js +1 -1
  6. package/dist/components/NodeTooltip.js.map +1 -1
  7. package/dist/contexts/TooltipPortalContext.d.ts +8 -0
  8. package/dist/contexts/TooltipPortalContext.d.ts.map +1 -0
  9. package/dist/contexts/TooltipPortalContext.js +8 -0
  10. package/dist/contexts/TooltipPortalContext.js.map +1 -0
  11. package/dist/edges/CustomEdge.d.ts +5 -0
  12. package/dist/edges/CustomEdge.d.ts.map +1 -1
  13. package/dist/edges/CustomEdge.js +7 -3
  14. package/dist/edges/CustomEdge.js.map +1 -1
  15. package/dist/hooks/useElkLayout.d.ts +66 -0
  16. package/dist/hooks/useElkLayout.d.ts.map +1 -0
  17. package/dist/hooks/useElkLayout.js +136 -0
  18. package/dist/hooks/useElkLayout.js.map +1 -0
  19. package/dist/index.d.ts +4 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +3 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/utils/elkLayout.d.ts +92 -0
  24. package/dist/utils/elkLayout.d.ts.map +1 -0
  25. package/dist/utils/elkLayout.js +352 -0
  26. package/dist/utils/elkLayout.js.map +1 -0
  27. package/package.json +4 -3
  28. package/src/components/GraphRenderer.tsx +70 -13
  29. package/src/components/NodeTooltip.tsx +1 -1
  30. package/src/contexts/TooltipPortalContext.ts +8 -0
  31. package/src/edges/CustomEdge.tsx +13 -2
  32. package/src/hooks/useElkLayout.test.ts +134 -0
  33. package/src/hooks/useElkLayout.ts +191 -0
  34. package/src/index.ts +6 -0
  35. package/src/stories/ElkEdgeRouting.stories.tsx +497 -0
  36. package/src/stories/SpanBadges.stories.tsx +840 -0
  37. package/src/utils/elkLayout.test.ts +240 -0
  38. package/src/utils/elkLayout.ts +487 -0
@@ -0,0 +1,497 @@
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { GraphRenderer } from '../components/GraphRenderer';
4
+ import type { ExtendedCanvas } from '@principal-ai/principal-view-core';
5
+ import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
6
+
7
+ const meta = {
8
+ title: 'Layout/ELK Edge Routing',
9
+ component: GraphRenderer,
10
+ parameters: {
11
+ layout: 'centered',
12
+ },
13
+ tags: ['autodocs'],
14
+ decorators: [
15
+ (Story) => (
16
+ <ThemeProvider theme={defaultEditorTheme}>
17
+ <Story />
18
+ </ThemeProvider>
19
+ ),
20
+ ],
21
+ } satisfies Meta<typeof GraphRenderer>;
22
+
23
+ export default meta;
24
+ type Story = StoryObj<typeof meta>;
25
+
26
+ const createNode = (
27
+ id: string,
28
+ x: number,
29
+ y: number,
30
+ label: string,
31
+ color: 1 | 2 | 3 | 4 | 5 | 6 = 4
32
+ ) => ({
33
+ id,
34
+ type: 'text' as const,
35
+ x,
36
+ y,
37
+ width: 120,
38
+ height: 60,
39
+ text: label,
40
+ color,
41
+ pv: {
42
+ nodeType: 'component',
43
+ shape: 'rectangle' as const,
44
+ },
45
+ });
46
+
47
+ // =============================================================================
48
+ // Level 1: Two nodes, one edge
49
+ // =============================================================================
50
+ const twoNodeCanvas: ExtendedCanvas = {
51
+ nodes: [
52
+ createNode('a', 0, 0, 'Node A', 5),
53
+ createNode('b', 250, 0, 'Node B', 4),
54
+ ],
55
+ edges: [
56
+ { id: 'e1', fromNode: 'a', toNode: 'b', fromSide: 'right', toSide: 'left', pv: { edgeType: 'flow' } },
57
+ ],
58
+ pv: {
59
+ version: '1.0.0',
60
+ name: 'Two Nodes',
61
+ edgeTypes: {
62
+ flow: { style: 'solid', color: '#3b82f6', width: 2, directed: true },
63
+ },
64
+ },
65
+ };
66
+
67
+ export const Level1_TwoNodes: Story = {
68
+ args: {
69
+ canvas: twoNodeCanvas,
70
+ width: 500,
71
+ height: 200,
72
+ showBackground: true,
73
+ backgroundVariant: 'dots',
74
+ },
75
+ };
76
+
77
+ export const Level1_TwoNodes_ELK: Story = {
78
+ args: {
79
+ canvas: twoNodeCanvas,
80
+ width: 500,
81
+ height: 200,
82
+ showBackground: true,
83
+ backgroundVariant: 'dots',
84
+ elkLayout: { enabled: true, routingStyle: 'orthogonal' },
85
+ },
86
+ };
87
+
88
+ // =============================================================================
89
+ // Level 1b: Two nodes with vertical offset
90
+ // =============================================================================
91
+ const twoNodeVerticalOffsetCanvas: ExtendedCanvas = {
92
+ nodes: [
93
+ createNode('a', 0, 0, 'Node A', 5),
94
+ createNode('b', 250, 100, 'Node B', 4),
95
+ ],
96
+ edges: [
97
+ { id: 'e1', fromNode: 'a', toNode: 'b', fromSide: 'right', toSide: 'left', pv: { edgeType: 'flow' } },
98
+ ],
99
+ pv: {
100
+ version: '1.0.0',
101
+ name: 'Two Nodes Vertical Offset',
102
+ edgeTypes: {
103
+ flow: { style: 'solid', color: '#3b82f6', width: 2, directed: true },
104
+ },
105
+ },
106
+ };
107
+
108
+ export const Level1b_TwoNodesVerticalOffset: Story = {
109
+ args: {
110
+ canvas: twoNodeVerticalOffsetCanvas,
111
+ width: 500,
112
+ height: 250,
113
+ showBackground: true,
114
+ backgroundVariant: 'dots',
115
+ },
116
+ };
117
+
118
+ export const Level1b_TwoNodesVerticalOffset_ELK: Story = {
119
+ args: {
120
+ canvas: twoNodeVerticalOffsetCanvas,
121
+ width: 500,
122
+ height: 250,
123
+ showBackground: true,
124
+ backgroundVariant: 'dots',
125
+ elkLayout: { enabled: true, routingStyle: 'orthogonal' },
126
+ },
127
+ };
128
+
129
+ // =============================================================================
130
+ // Level 1c: Two nodes stacked vertically (target below source)
131
+ // =============================================================================
132
+ const twoNodeStackedCanvas: ExtendedCanvas = {
133
+ nodes: [
134
+ createNode('a', 50, 0, 'Node A', 5),
135
+ createNode('b', 50, 120, 'Node B', 4),
136
+ ],
137
+ edges: [
138
+ { id: 'e1', fromNode: 'a', toNode: 'b', fromSide: 'bottom', toSide: 'top', pv: { edgeType: 'flow' } },
139
+ ],
140
+ pv: {
141
+ version: '1.0.0',
142
+ name: 'Two Nodes Stacked',
143
+ edgeTypes: {
144
+ flow: { style: 'solid', color: '#3b82f6', width: 2, directed: true },
145
+ },
146
+ },
147
+ };
148
+
149
+ export const Level1c_TwoNodesStacked: Story = {
150
+ args: {
151
+ canvas: twoNodeStackedCanvas,
152
+ width: 300,
153
+ height: 300,
154
+ showBackground: true,
155
+ backgroundVariant: 'dots',
156
+ },
157
+ };
158
+
159
+ export const Level1c_TwoNodesStacked_ELK: Story = {
160
+ args: {
161
+ canvas: twoNodeStackedCanvas,
162
+ width: 300,
163
+ height: 300,
164
+ showBackground: true,
165
+ backgroundVariant: 'dots',
166
+ elkLayout: { enabled: true, routingStyle: 'orthogonal' },
167
+ },
168
+ };
169
+
170
+ // =============================================================================
171
+ // Level 2: Three nodes in a line
172
+ // =============================================================================
173
+ const threeNodeCanvas: ExtendedCanvas = {
174
+ nodes: [
175
+ createNode('a', 0, 0, 'Node A', 5),
176
+ createNode('b', 200, 0, 'Node B', 2),
177
+ createNode('c', 400, 0, 'Node C', 4),
178
+ ],
179
+ edges: [
180
+ { id: 'e1', fromNode: 'a', toNode: 'b', fromSide: 'right', toSide: 'left', pv: { edgeType: 'flow' } },
181
+ { id: 'e2', fromNode: 'b', toNode: 'c', fromSide: 'right', toSide: 'left', pv: { edgeType: 'flow' } },
182
+ ],
183
+ pv: {
184
+ version: '1.0.0',
185
+ name: 'Three Nodes Linear',
186
+ edgeTypes: {
187
+ flow: { style: 'solid', color: '#3b82f6', width: 2, directed: true },
188
+ },
189
+ },
190
+ };
191
+
192
+ export const Level2_ThreeNodesLinear: Story = {
193
+ args: {
194
+ canvas: threeNodeCanvas,
195
+ width: 600,
196
+ height: 200,
197
+ showBackground: true,
198
+ backgroundVariant: 'dots',
199
+ },
200
+ };
201
+
202
+ export const Level2_ThreeNodesLinear_ELK: Story = {
203
+ args: {
204
+ canvas: threeNodeCanvas,
205
+ width: 600,
206
+ height: 200,
207
+ showBackground: true,
208
+ backgroundVariant: 'dots',
209
+ elkLayout: { enabled: true, routingStyle: 'orthogonal' },
210
+ },
211
+ };
212
+
213
+ // =============================================================================
214
+ // Level 3: One source, two targets (fan out)
215
+ // =============================================================================
216
+ const fanOutCanvas: ExtendedCanvas = {
217
+ nodes: [
218
+ createNode('a', 0, 50, 'Source', 5),
219
+ createNode('b', 250, 0, 'Target 1', 4),
220
+ createNode('c', 250, 100, 'Target 2', 4),
221
+ ],
222
+ edges: [
223
+ { id: 'e1', fromNode: 'a', toNode: 'b', fromSide: 'right', toSide: 'left', pv: { edgeType: 'flow' } },
224
+ { id: 'e2', fromNode: 'a', toNode: 'c', fromSide: 'right', toSide: 'left', pv: { edgeType: 'flow' } },
225
+ ],
226
+ pv: {
227
+ version: '1.0.0',
228
+ name: 'Fan Out',
229
+ edgeTypes: {
230
+ flow: { style: 'solid', color: '#3b82f6', width: 2, directed: true },
231
+ },
232
+ },
233
+ };
234
+
235
+ export const Level3_FanOut: Story = {
236
+ args: {
237
+ canvas: fanOutCanvas,
238
+ width: 500,
239
+ height: 250,
240
+ showBackground: true,
241
+ backgroundVariant: 'dots',
242
+ },
243
+ };
244
+
245
+ export const Level3_FanOut_ELK: Story = {
246
+ args: {
247
+ canvas: fanOutCanvas,
248
+ width: 500,
249
+ height: 250,
250
+ showBackground: true,
251
+ backgroundVariant: 'dots',
252
+ elkLayout: { enabled: true, routingStyle: 'orthogonal' },
253
+ },
254
+ };
255
+
256
+ // =============================================================================
257
+ // Level 4: Two sources, one target (fan in)
258
+ // =============================================================================
259
+ const fanInCanvas: ExtendedCanvas = {
260
+ nodes: [
261
+ createNode('a', 0, 0, 'Source 1', 5),
262
+ createNode('b', 0, 100, 'Source 2', 5),
263
+ createNode('c', 250, 50, 'Target', 4),
264
+ ],
265
+ edges: [
266
+ { id: 'e1', fromNode: 'a', toNode: 'c', fromSide: 'right', toSide: 'left', pv: { edgeType: 'flow' } },
267
+ { id: 'e2', fromNode: 'b', toNode: 'c', fromSide: 'right', toSide: 'left', pv: { edgeType: 'flow' } },
268
+ ],
269
+ pv: {
270
+ version: '1.0.0',
271
+ name: 'Fan In',
272
+ edgeTypes: {
273
+ flow: { style: 'solid', color: '#3b82f6', width: 2, directed: true },
274
+ },
275
+ },
276
+ };
277
+
278
+ export const Level4_FanIn: Story = {
279
+ args: {
280
+ canvas: fanInCanvas,
281
+ width: 500,
282
+ height: 250,
283
+ showBackground: true,
284
+ backgroundVariant: 'dots',
285
+ },
286
+ };
287
+
288
+ export const Level4_FanIn_ELK: Story = {
289
+ args: {
290
+ canvas: fanInCanvas,
291
+ width: 500,
292
+ height: 250,
293
+ showBackground: true,
294
+ backgroundVariant: 'dots',
295
+ elkLayout: { enabled: true, routingStyle: 'orthogonal' },
296
+ },
297
+ };
298
+
299
+ // =============================================================================
300
+ // Level 5: Diamond (fan out then fan in)
301
+ // =============================================================================
302
+ const diamondCanvas: ExtendedCanvas = {
303
+ nodes: [
304
+ createNode('a', 0, 50, 'Start', 5),
305
+ createNode('b', 200, 0, 'Path 1', 2),
306
+ createNode('c', 200, 100, 'Path 2', 2),
307
+ createNode('d', 400, 50, 'End', 4),
308
+ ],
309
+ edges: [
310
+ { id: 'e1', fromNode: 'a', toNode: 'b', fromSide: 'right', toSide: 'left', pv: { edgeType: 'flow' } },
311
+ { id: 'e2', fromNode: 'a', toNode: 'c', fromSide: 'right', toSide: 'left', pv: { edgeType: 'flow' } },
312
+ { id: 'e3', fromNode: 'b', toNode: 'd', fromSide: 'right', toSide: 'left', pv: { edgeType: 'flow' } },
313
+ { id: 'e4', fromNode: 'c', toNode: 'd', fromSide: 'right', toSide: 'left', pv: { edgeType: 'flow' } },
314
+ ],
315
+ pv: {
316
+ version: '1.0.0',
317
+ name: 'Diamond',
318
+ edgeTypes: {
319
+ flow: { style: 'solid', color: '#3b82f6', width: 2, directed: true },
320
+ },
321
+ },
322
+ };
323
+
324
+ export const Level5_Diamond: Story = {
325
+ args: {
326
+ canvas: diamondCanvas,
327
+ width: 600,
328
+ height: 250,
329
+ showBackground: true,
330
+ backgroundVariant: 'dots',
331
+ },
332
+ };
333
+
334
+ export const Level5_Diamond_ELK: Story = {
335
+ args: {
336
+ canvas: diamondCanvas,
337
+ width: 600,
338
+ height: 250,
339
+ showBackground: true,
340
+ backgroundVariant: 'dots',
341
+ elkLayout: { enabled: true, routingStyle: 'orthogonal' },
342
+ },
343
+ };
344
+
345
+ // =============================================================================
346
+ // Level 6: Cross connection (edges that would overlap)
347
+ // =============================================================================
348
+ const crossCanvas: ExtendedCanvas = {
349
+ nodes: [
350
+ createNode('a1', 0, 0, 'A1', 5),
351
+ createNode('a2', 0, 100, 'A2', 5),
352
+ createNode('b1', 300, 0, 'B1', 4),
353
+ createNode('b2', 300, 100, 'B2', 4),
354
+ ],
355
+ edges: [
356
+ // Crossing edges
357
+ { id: 'e1', fromNode: 'a1', toNode: 'b2', fromSide: 'right', toSide: 'left', pv: { edgeType: 'flow' } },
358
+ { id: 'e2', fromNode: 'a2', toNode: 'b1', fromSide: 'right', toSide: 'left', pv: { edgeType: 'signal' } },
359
+ ],
360
+ pv: {
361
+ version: '1.0.0',
362
+ name: 'Cross Connection',
363
+ edgeTypes: {
364
+ flow: { style: 'solid', color: '#3b82f6', width: 2, directed: true },
365
+ signal: { style: 'dashed', color: '#ef4444', width: 2, directed: true },
366
+ },
367
+ },
368
+ };
369
+
370
+ export const Level6_CrossConnection: Story = {
371
+ args: {
372
+ canvas: crossCanvas,
373
+ width: 500,
374
+ height: 250,
375
+ showBackground: true,
376
+ backgroundVariant: 'dots',
377
+ },
378
+ };
379
+
380
+ export const Level6_CrossConnection_ELK: Story = {
381
+ args: {
382
+ canvas: crossCanvas,
383
+ width: 500,
384
+ height: 250,
385
+ showBackground: true,
386
+ backgroundVariant: 'dots',
387
+ elkLayout: { enabled: true, routingStyle: 'orthogonal' },
388
+ },
389
+ };
390
+
391
+ // =============================================================================
392
+ // Level 7: Multiple parallel edges (same source/target sides)
393
+ // =============================================================================
394
+ const parallelCanvas: ExtendedCanvas = {
395
+ nodes: [
396
+ createNode('a', 0, 0, 'Source', 5),
397
+ createNode('b', 300, 0, 'Target', 4),
398
+ ],
399
+ edges: [
400
+ { id: 'e1', fromNode: 'a', toNode: 'b', fromSide: 'right', toSide: 'left', pv: { edgeType: 'data' } },
401
+ { id: 'e2', fromNode: 'a', toNode: 'b', fromSide: 'right', toSide: 'left', pv: { edgeType: 'control' } },
402
+ { id: 'e3', fromNode: 'a', toNode: 'b', fromSide: 'right', toSide: 'left', pv: { edgeType: 'signal' } },
403
+ ],
404
+ pv: {
405
+ version: '1.0.0',
406
+ name: 'Parallel Edges',
407
+ edgeTypes: {
408
+ data: { style: 'solid', color: '#3b82f6', width: 2, directed: true },
409
+ control: { style: 'solid', color: '#22c55e', width: 2, directed: true },
410
+ signal: { style: 'dashed', color: '#ef4444', width: 2, directed: true },
411
+ },
412
+ },
413
+ };
414
+
415
+ export const Level7_ParallelEdges: Story = {
416
+ args: {
417
+ canvas: parallelCanvas,
418
+ width: 500,
419
+ height: 200,
420
+ showBackground: true,
421
+ backgroundVariant: 'dots',
422
+ },
423
+ };
424
+
425
+ export const Level7_ParallelEdges_ELK: Story = {
426
+ args: {
427
+ canvas: parallelCanvas,
428
+ width: 500,
429
+ height: 200,
430
+ showBackground: true,
431
+ backgroundVariant: 'dots',
432
+ elkLayout: { enabled: true, routingStyle: 'orthogonal' },
433
+ },
434
+ };
435
+
436
+ // =============================================================================
437
+ // Level 8: Complex - 3x3 grid with various connections
438
+ // =============================================================================
439
+ const complexCanvas: ExtendedCanvas = {
440
+ nodes: [
441
+ // Left column
442
+ createNode('l1', 0, 0, 'L1', 5),
443
+ createNode('l2', 0, 100, 'L2', 5),
444
+ createNode('l3', 0, 200, 'L3', 5),
445
+ // Middle column
446
+ createNode('m1', 200, 50, 'M1', 2),
447
+ createNode('m2', 200, 150, 'M2', 2),
448
+ // Right column
449
+ createNode('r1', 400, 0, 'R1', 4),
450
+ createNode('r2', 400, 100, 'R2', 4),
451
+ createNode('r3', 400, 200, 'R3', 4),
452
+ ],
453
+ edges: [
454
+ // Left to middle
455
+ { id: 'e1', fromNode: 'l1', toNode: 'm1', fromSide: 'right', toSide: 'left', pv: { edgeType: 'flow' } },
456
+ { id: 'e2', fromNode: 'l2', toNode: 'm1', fromSide: 'right', toSide: 'left', pv: { edgeType: 'flow' } },
457
+ { id: 'e3', fromNode: 'l2', toNode: 'm2', fromSide: 'right', toSide: 'left', pv: { edgeType: 'flow' } },
458
+ { id: 'e4', fromNode: 'l3', toNode: 'm2', fromSide: 'right', toSide: 'left', pv: { edgeType: 'flow' } },
459
+ // Middle to right
460
+ { id: 'e5', fromNode: 'm1', toNode: 'r1', fromSide: 'right', toSide: 'left', pv: { edgeType: 'flow' } },
461
+ { id: 'e6', fromNode: 'm1', toNode: 'r2', fromSide: 'right', toSide: 'left', pv: { edgeType: 'flow' } },
462
+ { id: 'e7', fromNode: 'm2', toNode: 'r2', fromSide: 'right', toSide: 'left', pv: { edgeType: 'flow' } },
463
+ { id: 'e8', fromNode: 'm2', toNode: 'r3', fromSide: 'right', toSide: 'left', pv: { edgeType: 'flow' } },
464
+ // Cross connections
465
+ { id: 'e9', fromNode: 'l1', toNode: 'r3', fromSide: 'right', toSide: 'left', pv: { edgeType: 'signal' } },
466
+ { id: 'e10', fromNode: 'l3', toNode: 'r1', fromSide: 'right', toSide: 'left', pv: { edgeType: 'signal' } },
467
+ ],
468
+ pv: {
469
+ version: '1.0.0',
470
+ name: 'Complex Graph',
471
+ edgeTypes: {
472
+ flow: { style: 'solid', color: '#3b82f6', width: 2, directed: true },
473
+ signal: { style: 'dashed', color: '#ef4444', width: 2, directed: true },
474
+ },
475
+ },
476
+ };
477
+
478
+ export const Level8_Complex: Story = {
479
+ args: {
480
+ canvas: complexCanvas,
481
+ width: 600,
482
+ height: 350,
483
+ showBackground: true,
484
+ backgroundVariant: 'dots',
485
+ },
486
+ };
487
+
488
+ export const Level8_Complex_ELK: Story = {
489
+ args: {
490
+ canvas: complexCanvas,
491
+ width: 600,
492
+ height: 350,
493
+ showBackground: true,
494
+ backgroundVariant: 'dots',
495
+ elkLayout: { enabled: true, routingStyle: 'orthogonal' },
496
+ },
497
+ };