@principal-ai/principal-view-react 0.6.11 → 0.6.12

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.
@@ -0,0 +1,980 @@
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: 'Audit/CanvasEdgeTypes',
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
+ /**
27
+ * Helper to create a simple node
28
+ */
29
+ const createNode = (
30
+ id: string,
31
+ x: number,
32
+ y: number,
33
+ label: string,
34
+ color: 1 | 2 | 3 | 4 | 5 | 6 = 4
35
+ ) => ({
36
+ id,
37
+ type: 'text' as const,
38
+ x,
39
+ y,
40
+ width: 120,
41
+ height: 60,
42
+ text: label,
43
+ color,
44
+ pv: {
45
+ nodeType: 'example',
46
+ shape: 'rectangle' as const,
47
+ },
48
+ });
49
+
50
+ /**
51
+ * Canvas showing all edge connection sides
52
+ */
53
+ const edgeSidesCanvas: ExtendedCanvas = {
54
+ nodes: [
55
+ createNode('center', 300, 200, 'Center', 6),
56
+ createNode('top', 300, 50, 'Top', 4),
57
+ createNode('right', 500, 200, 'Right', 4),
58
+ createNode('bottom', 300, 350, 'Bottom', 4),
59
+ createNode('left', 100, 200, 'Left', 4),
60
+ ],
61
+ edges: [
62
+ {
63
+ id: 'top-edge',
64
+ fromNode: 'top',
65
+ toNode: 'center',
66
+ fromSide: 'bottom',
67
+ toSide: 'top',
68
+ pv: { edgeType: 'connection' },
69
+ },
70
+ {
71
+ id: 'right-edge',
72
+ fromNode: 'center',
73
+ toNode: 'right',
74
+ fromSide: 'right',
75
+ toSide: 'left',
76
+ pv: { edgeType: 'connection' },
77
+ },
78
+ {
79
+ id: 'bottom-edge',
80
+ fromNode: 'center',
81
+ toNode: 'bottom',
82
+ fromSide: 'bottom',
83
+ toSide: 'top',
84
+ pv: { edgeType: 'connection' },
85
+ },
86
+ {
87
+ id: 'left-edge',
88
+ fromNode: 'left',
89
+ toNode: 'center',
90
+ fromSide: 'right',
91
+ toSide: 'left',
92
+ pv: { edgeType: 'connection' },
93
+ },
94
+ ],
95
+ pv: {
96
+ version: '1.0.0',
97
+ name: 'Edge Sides Demo',
98
+ description: 'Demonstrating fromSide and toSide fields',
99
+ edgeTypes: {
100
+ connection: {
101
+ style: 'solid',
102
+ color: '#22c55e',
103
+ directed: true,
104
+ },
105
+ },
106
+ },
107
+ };
108
+
109
+ export const EdgeSides: Story = {
110
+ args: {
111
+ canvas: edgeSidesCanvas,
112
+ width: 700,
113
+ height: 500,
114
+ },
115
+ parameters: {
116
+ docs: {
117
+ description: {
118
+ story: `
119
+ **Edge Connection Sides**
120
+
121
+ Edges can connect to any of the 4 sides of a node:
122
+
123
+ - \`fromSide\` - Which side of the source node the edge starts from
124
+ - \`toSide\` - Which side of the target node the edge connects to
125
+
126
+ Values: \`'top'\` | \`'right'\` | \`'bottom'\` | \`'left'\`
127
+
128
+ If not specified, the renderer automatically chooses the best sides.
129
+ `,
130
+ },
131
+ },
132
+ },
133
+ };
134
+
135
+ /**
136
+ * Canvas showing endpoint shapes
137
+ */
138
+ const edgeEndpointsCanvas: ExtendedCanvas = {
139
+ nodes: [
140
+ createNode('a1', 100, 100, 'Source', 5),
141
+ createNode('b1', 350, 100, 'Target', 5),
142
+ createNode('a2', 100, 200, 'Source', 5),
143
+ createNode('b2', 350, 200, 'Target', 5),
144
+ createNode('a3', 100, 300, 'Source', 5),
145
+ createNode('b3', 350, 300, 'Target', 5),
146
+ createNode('a4', 100, 400, 'Source', 5),
147
+ createNode('b4', 350, 400, 'Target', 5),
148
+ ],
149
+ edges: [
150
+ {
151
+ id: 'default',
152
+ fromNode: 'a1',
153
+ toNode: 'b1',
154
+ fromSide: 'right',
155
+ toSide: 'left',
156
+ label: 'default (none → arrow)',
157
+ pv: { edgeType: 'demo' },
158
+ },
159
+ {
160
+ id: 'arrow-arrow',
161
+ fromNode: 'a2',
162
+ toNode: 'b2',
163
+ fromSide: 'right',
164
+ toSide: 'left',
165
+ fromEnd: 'arrow',
166
+ toEnd: 'arrow',
167
+ label: 'arrow → arrow',
168
+ pv: { edgeType: 'demo' },
169
+ },
170
+ {
171
+ id: 'none-none',
172
+ fromNode: 'a3',
173
+ toNode: 'b3',
174
+ fromSide: 'right',
175
+ toSide: 'left',
176
+ fromEnd: 'none',
177
+ toEnd: 'none',
178
+ label: 'none → none',
179
+ pv: { edgeType: 'demo' },
180
+ },
181
+ {
182
+ id: 'arrow-none',
183
+ fromNode: 'a4',
184
+ toNode: 'b4',
185
+ fromSide: 'right',
186
+ toSide: 'left',
187
+ fromEnd: 'arrow',
188
+ toEnd: 'none',
189
+ label: 'arrow → none',
190
+ pv: { edgeType: 'demo' },
191
+ },
192
+ ],
193
+ pv: {
194
+ version: '1.0.0',
195
+ name: 'Edge Endpoints Demo',
196
+ description: 'Demonstrating fromEnd and toEnd fields',
197
+ edgeTypes: {
198
+ demo: {
199
+ style: 'solid',
200
+ color: '#06b6d4',
201
+ directed: true,
202
+ },
203
+ },
204
+ },
205
+ };
206
+
207
+ export const EdgeEndpoints: Story = {
208
+ args: {
209
+ canvas: edgeEndpointsCanvas,
210
+ width: 600,
211
+ height: 550,
212
+ },
213
+ parameters: {
214
+ docs: {
215
+ description: {
216
+ story: `
217
+ **Edge Endpoint Shapes**
218
+
219
+ Control the arrow markers at each end of an edge:
220
+
221
+ - \`fromEnd\` - Shape at the source end (default: \`'none'\`)
222
+ - \`toEnd\` - Shape at the target end (default: \`'arrow'\`)
223
+
224
+ Values: \`'none'\` | \`'arrow'\`
225
+
226
+ This allows creating undirected edges, bidirectional arrows, or reversed arrows.
227
+ `,
228
+ },
229
+ },
230
+ },
231
+ };
232
+
233
+ /**
234
+ * Canvas showing edge colors
235
+ */
236
+ const edgeColorsCanvas: ExtendedCanvas = {
237
+ nodes: [
238
+ createNode('a1', 100, 80, 'Node', 1),
239
+ createNode('b1', 320, 80, 'Node', 1),
240
+ createNode('a2', 100, 160, 'Node', 2),
241
+ createNode('b2', 320, 160, 'Node', 2),
242
+ createNode('a3', 100, 240, 'Node', 3),
243
+ createNode('b3', 320, 240, 'Node', 3),
244
+ createNode('a4', 100, 320, 'Node', 4),
245
+ createNode('b4', 320, 320, 'Node', 4),
246
+ createNode('a5', 100, 400, 'Node', 5),
247
+ createNode('b5', 320, 400, 'Node', 5),
248
+ createNode('a6', 100, 480, 'Node', 6),
249
+ createNode('b6', 320, 480, 'Node', 6),
250
+ ],
251
+ edges: [
252
+ {
253
+ id: 'red',
254
+ fromNode: 'a1',
255
+ toNode: 'b1',
256
+ fromSide: 'right',
257
+ toSide: 'left',
258
+ color: 1,
259
+ label: 'color: 1 (red)',
260
+ pv: { edgeType: 'colored' },
261
+ },
262
+ {
263
+ id: 'orange',
264
+ fromNode: 'a2',
265
+ toNode: 'b2',
266
+ fromSide: 'right',
267
+ toSide: 'left',
268
+ color: 2,
269
+ label: 'color: 2 (orange)',
270
+ pv: { edgeType: 'colored' },
271
+ },
272
+ {
273
+ id: 'yellow',
274
+ fromNode: 'a3',
275
+ toNode: 'b3',
276
+ fromSide: 'right',
277
+ toSide: 'left',
278
+ color: 3,
279
+ label: 'color: 3 (yellow)',
280
+ pv: { edgeType: 'colored' },
281
+ },
282
+ {
283
+ id: 'green',
284
+ fromNode: 'a4',
285
+ toNode: 'b4',
286
+ fromSide: 'right',
287
+ toSide: 'left',
288
+ color: 4,
289
+ label: 'color: 4 (green)',
290
+ pv: { edgeType: 'colored' },
291
+ },
292
+ {
293
+ id: 'cyan',
294
+ fromNode: 'a5',
295
+ toNode: 'b5',
296
+ fromSide: 'right',
297
+ toSide: 'left',
298
+ color: 5,
299
+ label: 'color: 5 (cyan)',
300
+ pv: { edgeType: 'colored' },
301
+ },
302
+ {
303
+ id: 'purple',
304
+ fromNode: 'a6',
305
+ toNode: 'b6',
306
+ fromSide: 'right',
307
+ toSide: 'left',
308
+ color: 6,
309
+ label: 'color: 6 (purple)',
310
+ pv: { edgeType: 'colored' },
311
+ },
312
+ ],
313
+ pv: {
314
+ version: '1.0.0',
315
+ name: 'Edge Colors Demo',
316
+ description: 'Demonstrating edge color presets',
317
+ edgeTypes: {
318
+ colored: {
319
+ style: 'solid',
320
+ directed: true,
321
+ },
322
+ },
323
+ },
324
+ };
325
+
326
+ export const EdgeColors: Story = {
327
+ args: {
328
+ canvas: edgeColorsCanvas,
329
+ width: 550,
330
+ height: 620,
331
+ },
332
+ parameters: {
333
+ docs: {
334
+ description: {
335
+ story: `
336
+ **Edge Colors**
337
+
338
+ Edges support the same color system as nodes:
339
+
340
+ | Preset | Color |
341
+ |--------|---------|
342
+ | 1 | Red |
343
+ | 2 | Orange |
344
+ | 3 | Yellow |
345
+ | 4 | Green |
346
+ | 5 | Cyan |
347
+ | 6 | Purple |
348
+
349
+ You can also use hex strings: \`color: '#3b82f6'\`
350
+ `,
351
+ },
352
+ },
353
+ },
354
+ };
355
+
356
+ /**
357
+ * Canvas showing edge line styles (PV extension)
358
+ */
359
+ const edgeStylesCanvas: ExtendedCanvas = {
360
+ nodes: [
361
+ createNode('a1', 100, 100, 'Source', 6),
362
+ createNode('b1', 380, 100, 'Target', 6),
363
+ createNode('a2', 100, 200, 'Source', 6),
364
+ createNode('b2', 380, 200, 'Target', 6),
365
+ createNode('a3', 100, 300, 'Source', 6),
366
+ createNode('b3', 380, 300, 'Target', 6),
367
+ createNode('a4', 100, 400, 'Source', 6),
368
+ createNode('b4', 380, 400, 'Target', 6),
369
+ ],
370
+ edges: [
371
+ {
372
+ id: 'solid',
373
+ fromNode: 'a1',
374
+ toNode: 'b1',
375
+ fromSide: 'right',
376
+ toSide: 'left',
377
+ label: 'solid',
378
+ pv: { edgeType: 'solid' },
379
+ },
380
+ {
381
+ id: 'dashed',
382
+ fromNode: 'a2',
383
+ toNode: 'b2',
384
+ fromSide: 'right',
385
+ toSide: 'left',
386
+ label: 'dashed',
387
+ pv: { edgeType: 'dashed' },
388
+ },
389
+ {
390
+ id: 'dotted',
391
+ fromNode: 'a3',
392
+ toNode: 'b3',
393
+ fromSide: 'right',
394
+ toSide: 'left',
395
+ label: 'dotted',
396
+ pv: { edgeType: 'dotted' },
397
+ },
398
+ {
399
+ id: 'animated',
400
+ fromNode: 'a4',
401
+ toNode: 'b4',
402
+ fromSide: 'right',
403
+ toSide: 'left',
404
+ label: 'animated',
405
+ pv: { edgeType: 'animated' },
406
+ },
407
+ ],
408
+ pv: {
409
+ version: '1.0.0',
410
+ name: 'Edge Styles Demo',
411
+ description: 'Demonstrating PV edge style extension',
412
+ edgeTypes: {
413
+ solid: {
414
+ style: 'solid',
415
+ color: '#8b5cf6',
416
+ directed: true,
417
+ },
418
+ dashed: {
419
+ style: 'dashed',
420
+ color: '#8b5cf6',
421
+ directed: true,
422
+ },
423
+ dotted: {
424
+ style: 'dotted',
425
+ color: '#8b5cf6',
426
+ directed: true,
427
+ },
428
+ animated: {
429
+ style: 'animated',
430
+ color: '#8b5cf6',
431
+ directed: true,
432
+ animation: {
433
+ type: 'flow',
434
+ duration: 1000,
435
+ },
436
+ },
437
+ },
438
+ },
439
+ };
440
+
441
+ export const EdgeStyles: Story = {
442
+ args: {
443
+ canvas: edgeStylesCanvas,
444
+ width: 600,
445
+ height: 550,
446
+ },
447
+ parameters: {
448
+ docs: {
449
+ description: {
450
+ story: `
451
+ **Edge Line Styles (PV Extension)**
452
+
453
+ The \`pv.style\` field controls line rendering:
454
+
455
+ - \`'solid'\` - Continuous line
456
+ - \`'dashed'\` - Dashed line (- - -)
457
+ - \`'dotted'\` - Dotted line (...)
458
+ - \`'animated'\` - Animated flow effect
459
+
460
+ These styles are defined in \`pv.edgeTypes\` at the canvas level.
461
+ `,
462
+ },
463
+ },
464
+ },
465
+ };
466
+
467
+ /**
468
+ * Canvas showing labeled edges
469
+ */
470
+ const edgeLabelsCanvas: ExtendedCanvas = {
471
+ nodes: [
472
+ createNode('user', 100, 150, 'User', 5),
473
+ createNode('api', 320, 80, 'API', 4),
474
+ createNode('db', 320, 220, 'Database', 4),
475
+ createNode('cache', 540, 150, 'Cache', 3),
476
+ ],
477
+ edges: [
478
+ {
479
+ id: 'user-api',
480
+ fromNode: 'user',
481
+ toNode: 'api',
482
+ fromSide: 'right',
483
+ toSide: 'left',
484
+ label: 'HTTP Request',
485
+ pv: { edgeType: 'request' },
486
+ },
487
+ {
488
+ id: 'api-db',
489
+ fromNode: 'api',
490
+ toNode: 'db',
491
+ fromSide: 'bottom',
492
+ toSide: 'top',
493
+ label: 'Query',
494
+ pv: { edgeType: 'data' },
495
+ },
496
+ {
497
+ id: 'api-cache',
498
+ fromNode: 'api',
499
+ toNode: 'cache',
500
+ fromSide: 'right',
501
+ toSide: 'left',
502
+ label: 'Read/Write',
503
+ pv: { edgeType: 'cache' },
504
+ },
505
+ {
506
+ id: 'cache-db',
507
+ fromNode: 'cache',
508
+ toNode: 'db',
509
+ fromSide: 'bottom',
510
+ toSide: 'right',
511
+ label: 'Sync',
512
+ pv: { edgeType: 'sync' },
513
+ },
514
+ ],
515
+ pv: {
516
+ version: '1.0.0',
517
+ name: 'Edge Labels Demo',
518
+ description: 'Demonstrating edge label field',
519
+ edgeTypes: {
520
+ request: {
521
+ style: 'solid',
522
+ color: '#3b82f6',
523
+ directed: true,
524
+ },
525
+ data: {
526
+ style: 'solid',
527
+ color: '#22c55e',
528
+ directed: true,
529
+ },
530
+ cache: {
531
+ style: 'dashed',
532
+ color: '#eab308',
533
+ directed: true,
534
+ },
535
+ sync: {
536
+ style: 'dotted',
537
+ color: '#f97316',
538
+ directed: true,
539
+ },
540
+ },
541
+ },
542
+ };
543
+
544
+ export const EdgeLabels: Story = {
545
+ args: {
546
+ canvas: edgeLabelsCanvas,
547
+ width: 750,
548
+ height: 400,
549
+ },
550
+ parameters: {
551
+ docs: {
552
+ description: {
553
+ story: `
554
+ **Edge Labels**
555
+
556
+ The \`label\` field adds text to an edge:
557
+
558
+ \`\`\`json
559
+ {
560
+ "id": "user-api",
561
+ "fromNode": "user",
562
+ "toNode": "api",
563
+ "label": "HTTP Request"
564
+ }
565
+ \`\`\`
566
+
567
+ Labels are positioned at the center of the edge and help describe the relationship.
568
+ `,
569
+ },
570
+ },
571
+ },
572
+ };
573
+
574
+ /**
575
+ * Canvas showing edge type definitions
576
+ */
577
+ const edgeTypeDefinitionsCanvas: ExtendedCanvas = {
578
+ nodes: [
579
+ createNode('service-a', 100, 100, 'Service A', 5),
580
+ createNode('service-b', 350, 100, 'Service B', 5),
581
+ createNode('service-c', 100, 250, 'Service C', 5),
582
+ createNode('service-d', 350, 250, 'Service D', 5),
583
+ createNode('service-e', 225, 400, 'Service E', 5),
584
+ ],
585
+ edges: [
586
+ {
587
+ id: 'a-b',
588
+ fromNode: 'service-a',
589
+ toNode: 'service-b',
590
+ fromSide: 'right',
591
+ toSide: 'left',
592
+ label: 'depends-on',
593
+ pv: { edgeType: 'depends-on' },
594
+ },
595
+ {
596
+ id: 'c-d',
597
+ fromNode: 'service-c',
598
+ toNode: 'service-d',
599
+ fromSide: 'right',
600
+ toSide: 'left',
601
+ label: 'publishes-to',
602
+ pv: { edgeType: 'publishes-to' },
603
+ },
604
+ {
605
+ id: 'a-c',
606
+ fromNode: 'service-a',
607
+ toNode: 'service-c',
608
+ fromSide: 'bottom',
609
+ toSide: 'top',
610
+ label: 'calls',
611
+ pv: { edgeType: 'calls' },
612
+ },
613
+ {
614
+ id: 'b-d',
615
+ fromNode: 'service-b',
616
+ toNode: 'service-d',
617
+ fromSide: 'bottom',
618
+ toSide: 'top',
619
+ label: 'inherits',
620
+ pv: { edgeType: 'inherits' },
621
+ },
622
+ {
623
+ id: 'd-e',
624
+ fromNode: 'service-d',
625
+ toNode: 'service-e',
626
+ fromSide: 'bottom',
627
+ toSide: 'right',
628
+ label: 'aggregates',
629
+ pv: { edgeType: 'aggregates' },
630
+ },
631
+ ],
632
+ pv: {
633
+ version: '1.0.0',
634
+ name: 'Edge Type Definitions',
635
+ description: 'Showing canvas-level edgeTypes configuration',
636
+ edgeTypes: {
637
+ 'depends-on': {
638
+ label: 'Dependency',
639
+ style: 'solid',
640
+ color: '#ef4444',
641
+ width: 2,
642
+ directed: true,
643
+ },
644
+ 'publishes-to': {
645
+ label: 'Event Publishing',
646
+ style: 'dashed',
647
+ color: '#22c55e',
648
+ width: 2,
649
+ directed: true,
650
+ },
651
+ calls: {
652
+ label: 'RPC Call',
653
+ style: 'solid',
654
+ color: '#3b82f6',
655
+ width: 1,
656
+ directed: true,
657
+ },
658
+ inherits: {
659
+ label: 'Inheritance',
660
+ style: 'dotted',
661
+ color: '#8b5cf6',
662
+ width: 2,
663
+ directed: true,
664
+ },
665
+ aggregates: {
666
+ label: 'Aggregation',
667
+ style: 'solid',
668
+ color: '#f97316',
669
+ width: 3,
670
+ directed: false,
671
+ },
672
+ },
673
+ },
674
+ };
675
+
676
+ export const EdgeTypeDefinitions: Story = {
677
+ args: {
678
+ canvas: edgeTypeDefinitionsCanvas,
679
+ width: 600,
680
+ height: 550,
681
+ },
682
+ parameters: {
683
+ docs: {
684
+ description: {
685
+ story: `
686
+ **Edge Type Definitions (PV Extension)**
687
+
688
+ Define reusable edge types at the canvas level in \`pv.edgeTypes\`:
689
+
690
+ \`\`\`json
691
+ {
692
+ "pv": {
693
+ "edgeTypes": {
694
+ "depends-on": {
695
+ "label": "Dependency",
696
+ "style": "solid",
697
+ "color": "#ef4444",
698
+ "width": 2,
699
+ "directed": true
700
+ }
701
+ }
702
+ }
703
+ }
704
+ \`\`\`
705
+
706
+ **Edge Type Definition Fields:**
707
+ - \`label\` - Display name for the edge type
708
+ - \`style\` - Line style: \`solid\`, \`dashed\`, \`dotted\`, \`animated\`
709
+ - \`color\` - Hex color string
710
+ - \`width\` - Line width in pixels
711
+ - \`directed\` - Whether to show arrow (true/false)
712
+ - \`animation\` - Animation configuration
713
+ - \`labelConfig\` - Label positioning options
714
+ `,
715
+ },
716
+ },
717
+ },
718
+ };
719
+
720
+ /**
721
+ * Interactive comparison template
722
+ */
723
+ const EdgeFieldsComparisonTemplate = () => {
724
+ return (
725
+ <div style={{ padding: 20, fontFamily: 'system-ui' }}>
726
+ <h2 style={{ marginBottom: 20 }}>JSON Canvas Edge Fields Reference</h2>
727
+
728
+ <h3 style={{ marginTop: 24, marginBottom: 12 }}>Standard JSON Canvas Fields</h3>
729
+ <table
730
+ style={{
731
+ width: '100%',
732
+ borderCollapse: 'collapse',
733
+ fontSize: 13,
734
+ marginBottom: 24,
735
+ }}
736
+ >
737
+ <thead>
738
+ <tr style={{ backgroundColor: '#f3f4f6' }}>
739
+ <th style={{ padding: 10, textAlign: 'left', border: '1px solid #e5e7eb' }}>Field</th>
740
+ <th style={{ padding: 10, textAlign: 'left', border: '1px solid #e5e7eb' }}>Type</th>
741
+ <th style={{ padding: 10, textAlign: 'left', border: '1px solid #e5e7eb' }}>Required</th>
742
+ <th style={{ padding: 10, textAlign: 'left', border: '1px solid #e5e7eb' }}>
743
+ Description
744
+ </th>
745
+ </tr>
746
+ </thead>
747
+ <tbody>
748
+ <tr>
749
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>
750
+ <code>id</code>
751
+ </td>
752
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>string</td>
753
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>Yes</td>
754
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>Unique identifier</td>
755
+ </tr>
756
+ <tr>
757
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>
758
+ <code>fromNode</code>
759
+ </td>
760
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>string</td>
761
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>Yes</td>
762
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>Source node ID</td>
763
+ </tr>
764
+ <tr>
765
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>
766
+ <code>toNode</code>
767
+ </td>
768
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>string</td>
769
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>Yes</td>
770
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>Target node ID</td>
771
+ </tr>
772
+ <tr>
773
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>
774
+ <code>fromSide</code>
775
+ </td>
776
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>
777
+ 'top' | 'right' | 'bottom' | 'left'
778
+ </td>
779
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>No</td>
780
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>
781
+ Side of source node to connect from
782
+ </td>
783
+ </tr>
784
+ <tr>
785
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>
786
+ <code>toSide</code>
787
+ </td>
788
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>
789
+ 'top' | 'right' | 'bottom' | 'left'
790
+ </td>
791
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>No</td>
792
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>
793
+ Side of target node to connect to
794
+ </td>
795
+ </tr>
796
+ <tr>
797
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>
798
+ <code>fromEnd</code>
799
+ </td>
800
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>'none' | 'arrow'</td>
801
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>No</td>
802
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>
803
+ Endpoint shape at source (default: 'none')
804
+ </td>
805
+ </tr>
806
+ <tr>
807
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>
808
+ <code>toEnd</code>
809
+ </td>
810
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>'none' | 'arrow'</td>
811
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>No</td>
812
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>
813
+ Endpoint shape at target (default: 'arrow')
814
+ </td>
815
+ </tr>
816
+ <tr>
817
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>
818
+ <code>color</code>
819
+ </td>
820
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>string | 1-6</td>
821
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>No</td>
822
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>
823
+ Hex color or preset (1=red, 2=orange, 3=yellow, 4=green, 5=cyan, 6=purple)
824
+ </td>
825
+ </tr>
826
+ <tr>
827
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>
828
+ <code>label</code>
829
+ </td>
830
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>string</td>
831
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>No</td>
832
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>Text label displayed on edge</td>
833
+ </tr>
834
+ </tbody>
835
+ </table>
836
+
837
+ <h3 style={{ marginTop: 24, marginBottom: 12 }}>Principal View Extension Fields (pv)</h3>
838
+ <table
839
+ style={{
840
+ width: '100%',
841
+ borderCollapse: 'collapse',
842
+ fontSize: 13,
843
+ marginBottom: 24,
844
+ }}
845
+ >
846
+ <thead>
847
+ <tr style={{ backgroundColor: '#ede9fe' }}>
848
+ <th style={{ padding: 10, textAlign: 'left', border: '1px solid #e5e7eb' }}>Field</th>
849
+ <th style={{ padding: 10, textAlign: 'left', border: '1px solid #e5e7eb' }}>Type</th>
850
+ <th style={{ padding: 10, textAlign: 'left', border: '1px solid #e5e7eb' }}>
851
+ Description
852
+ </th>
853
+ </tr>
854
+ </thead>
855
+ <tbody>
856
+ <tr>
857
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>
858
+ <code>pv.edgeType</code>
859
+ </td>
860
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>string</td>
861
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>
862
+ References an edge type defined in canvas pv.edgeTypes
863
+ </td>
864
+ </tr>
865
+ <tr>
866
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>
867
+ <code>pv.style</code>
868
+ </td>
869
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>
870
+ 'solid' | 'dashed' | 'dotted' | 'animated'
871
+ </td>
872
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>Line style override</td>
873
+ </tr>
874
+ <tr>
875
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>
876
+ <code>pv.width</code>
877
+ </td>
878
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>number</td>
879
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>Line width in pixels</td>
880
+ </tr>
881
+ <tr>
882
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>
883
+ <code>pv.animation</code>
884
+ </td>
885
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>object</td>
886
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>
887
+ Animation config: {'{'}type, duration, color{'}'}
888
+ </td>
889
+ </tr>
890
+ <tr>
891
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>
892
+ <code>pv.activatedBy</code>
893
+ </td>
894
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>array</td>
895
+ <td style={{ padding: 10, border: '1px solid #e5e7eb' }}>
896
+ Event triggers for edge activation
897
+ </td>
898
+ </tr>
899
+ </tbody>
900
+ </table>
901
+
902
+ <h3 style={{ marginTop: 24, marginBottom: 12 }}>Canvas-Level Edge Type Definition</h3>
903
+ <div
904
+ style={{
905
+ backgroundColor: '#1e1e1e',
906
+ color: '#d4d4d4',
907
+ padding: 16,
908
+ borderRadius: 8,
909
+ fontSize: 12,
910
+ fontFamily: 'monospace',
911
+ overflow: 'auto',
912
+ }}
913
+ >
914
+ <pre style={{ margin: 0 }}>
915
+ {`{
916
+ "pv": {
917
+ "edgeTypes": {
918
+ "depends-on": {
919
+ "label": "Dependency", // Display name
920
+ "style": "solid", // solid | dashed | dotted | animated
921
+ "color": "#ef4444", // Hex color
922
+ "width": 2, // Line width in pixels
923
+ "directed": true, // Show arrow head
924
+ "animation": { // Optional animation
925
+ "type": "flow", // flow | pulse | particle | glow
926
+ "duration": 1000, // Duration in ms
927
+ "color": "#ff0000" // Animation color
928
+ },
929
+ "labelConfig": { // Label positioning
930
+ "field": "weight", // Data field to display
931
+ "position": "middle" // start | middle | end
932
+ }
933
+ }
934
+ }
935
+ }
936
+ }`}
937
+ </pre>
938
+ </div>
939
+
940
+ <h3 style={{ marginTop: 32, marginBottom: 16 }}>Live Example</h3>
941
+ <GraphRenderer canvas={edgeLabelsCanvas} width={750} height={350} />
942
+
943
+ <div
944
+ style={{
945
+ marginTop: 24,
946
+ padding: 16,
947
+ backgroundColor: '#f5f5f5',
948
+ borderRadius: 8,
949
+ }}
950
+ >
951
+ <h4 style={{ margin: '0 0 12px 0' }}>JSON Canvas Spec</h4>
952
+ <p style={{ fontSize: 13, margin: 0, lineHeight: 1.6 }}>
953
+ Standard edge fields follow the{' '}
954
+ <a
955
+ href="https://jsoncanvas.org/"
956
+ target="_blank"
957
+ rel="noopener noreferrer"
958
+ style={{ color: '#3b82f6' }}
959
+ >
960
+ JSON Canvas specification
961
+ </a>
962
+ . Principal View extensions are stored in the <code>pv</code> field and provide enhanced
963
+ rendering capabilities while maintaining compatibility with standard canvas tools.
964
+ </p>
965
+ </div>
966
+ </div>
967
+ );
968
+ };
969
+
970
+ export const EdgeFieldsReference: Story = {
971
+ render: () => <EdgeFieldsComparisonTemplate />,
972
+ parameters: {
973
+ docs: {
974
+ description: {
975
+ story:
976
+ 'Complete reference for all JSON Canvas edge fields and Principal View extensions.',
977
+ },
978
+ },
979
+ },
980
+ };