@principal-ai/principal-view-react 0.14.21 → 0.14.23
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.map +1 -1
- package/dist/components/GraphRenderer.js +23 -10
- package/dist/components/GraphRenderer.js.map +1 -1
- package/dist/components/state-view/PipelineView.d.ts +13 -0
- package/dist/components/state-view/PipelineView.d.ts.map +1 -0
- package/dist/components/state-view/PipelineView.js +195 -0
- package/dist/components/state-view/PipelineView.js.map +1 -0
- package/dist/components/state-view/index.d.ts +14 -0
- package/dist/components/state-view/index.d.ts.map +1 -0
- package/dist/components/state-view/index.js +12 -0
- package/dist/components/state-view/index.js.map +1 -0
- package/dist/components/state-view/types.d.ts +188 -0
- package/dist/components/state-view/types.d.ts.map +1 -0
- package/dist/components/state-view/types.js +10 -0
- package/dist/components/state-view/types.js.map +1 -0
- package/dist/components/state-view/useStateView.d.ts +32 -0
- package/dist/components/state-view/useStateView.d.ts.map +1 -0
- package/dist/components/state-view/useStateView.js +129 -0
- package/dist/components/state-view/useStateView.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/nodes/CustomNode.js +8 -8
- package/dist/nodes/CustomNode.js.map +1 -1
- package/dist/nodes/otel/OtelBoundaryNode.d.ts.map +1 -1
- package/dist/nodes/otel/OtelBoundaryNode.js +5 -3
- package/dist/nodes/otel/OtelBoundaryNode.js.map +1 -1
- package/dist/nodes/otel/OtelEventNode.d.ts.map +1 -1
- package/dist/nodes/otel/OtelEventNode.js +5 -3
- package/dist/nodes/otel/OtelEventNode.js.map +1 -1
- package/dist/nodes/otel/OtelResourceNode.d.ts.map +1 -1
- package/dist/nodes/otel/OtelResourceNode.js +5 -3
- package/dist/nodes/otel/OtelResourceNode.js.map +1 -1
- package/dist/nodes/otel/OtelScopeNode.d.ts.map +1 -1
- package/dist/nodes/otel/OtelScopeNode.js +5 -3
- package/dist/nodes/otel/OtelScopeNode.js.map +1 -1
- package/dist/nodes/otel/OtelSpanConventionNode.d.ts.map +1 -1
- package/dist/nodes/otel/OtelSpanConventionNode.js +5 -3
- package/dist/nodes/otel/OtelSpanConventionNode.js.map +1 -1
- package/package.json +2 -2
- package/src/components/GraphRenderer.tsx +24 -10
- package/src/components/state-view/PipelineView.tsx +347 -0
- package/src/components/state-view/index.ts +14 -0
- package/src/components/state-view/types.ts +261 -0
- package/src/components/state-view/useStateView.ts +205 -0
- package/src/index.ts +36 -0
- package/src/nodes/CustomNode.tsx +8 -8
- package/src/nodes/otel/OtelBoundaryNode.tsx +5 -3
- package/src/nodes/otel/OtelEventNode.tsx +5 -3
- package/src/nodes/otel/OtelResourceNode.tsx +5 -3
- package/src/nodes/otel/OtelScopeNode.tsx +5 -3
- package/src/nodes/otel/OtelSpanConventionNode.tsx +5 -4
- package/src/stories/CanvasEdgeTypes.stories.tsx +23 -27
- package/src/stories/GraphRenderer.stories.tsx +144 -200
- package/src/stories/StateView.stories.tsx +417 -0
- package/src/stories/__traces__/test-run.canvas.json +27 -30
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useStateView Hook
|
|
3
|
+
*
|
|
4
|
+
* Manages state accumulation from events and provides replay controls.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useState, useEffect, useRef, useCallback } from 'react';
|
|
8
|
+
import type {
|
|
9
|
+
StateEvent,
|
|
10
|
+
EventSource,
|
|
11
|
+
StateReducer,
|
|
12
|
+
StateDiff,
|
|
13
|
+
ReplayControls,
|
|
14
|
+
ActiveAnimation,
|
|
15
|
+
TransitionDefinition,
|
|
16
|
+
} from './types';
|
|
17
|
+
|
|
18
|
+
export interface UseStateViewOptions<TState, TEvent extends StateEvent> {
|
|
19
|
+
/** Initial state */
|
|
20
|
+
initialState: TState;
|
|
21
|
+
/** Reducer to apply events */
|
|
22
|
+
reducer: StateReducer<TState, TEvent>;
|
|
23
|
+
/** Event source (live or replay) */
|
|
24
|
+
eventSource: EventSource<TEvent>;
|
|
25
|
+
/** Transition definitions for animations */
|
|
26
|
+
transitions?: TransitionDefinition[];
|
|
27
|
+
/** Callback when state changes */
|
|
28
|
+
onStateChange?: (diff: StateDiff<TState>) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface UseStateViewResult<TState> {
|
|
32
|
+
/** Current state */
|
|
33
|
+
state: TState;
|
|
34
|
+
/** Active animations */
|
|
35
|
+
animations: ActiveAnimation[];
|
|
36
|
+
/** Whether we're in replay mode */
|
|
37
|
+
isReplay: boolean;
|
|
38
|
+
/** Replay controls (if in replay mode) */
|
|
39
|
+
replayControls?: ReplayControls;
|
|
40
|
+
/** Reset state to initial */
|
|
41
|
+
reset: () => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get changed paths between two objects (shallow comparison)
|
|
46
|
+
*/
|
|
47
|
+
function getChangedPaths(prev: unknown, next: unknown, prefix = ''): string[] {
|
|
48
|
+
const paths: string[] = [];
|
|
49
|
+
|
|
50
|
+
if (prev === next) return paths;
|
|
51
|
+
|
|
52
|
+
if (
|
|
53
|
+
typeof prev !== 'object' ||
|
|
54
|
+
typeof next !== 'object' ||
|
|
55
|
+
prev === null ||
|
|
56
|
+
next === null
|
|
57
|
+
) {
|
|
58
|
+
return prefix ? [prefix] : [];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const prevObj = prev as Record<string, unknown>;
|
|
62
|
+
const nextObj = next as Record<string, unknown>;
|
|
63
|
+
|
|
64
|
+
const allKeys = new Set([...Object.keys(prevObj), ...Object.keys(nextObj)]);
|
|
65
|
+
|
|
66
|
+
for (const key of allKeys) {
|
|
67
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
68
|
+
if (prevObj[key] !== nextObj[key]) {
|
|
69
|
+
paths.push(path);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return paths;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Check if a transition should fire based on state change
|
|
78
|
+
*/
|
|
79
|
+
function shouldTriggerTransition(
|
|
80
|
+
transition: TransitionDefinition,
|
|
81
|
+
prev: unknown,
|
|
82
|
+
next: unknown,
|
|
83
|
+
changedPaths: string[]
|
|
84
|
+
): boolean {
|
|
85
|
+
// Check if the watched path changed
|
|
86
|
+
if (!changedPaths.some((p) => p.startsWith(transition.watch))) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const prevValue = getValueAtPath(prev, transition.watch);
|
|
91
|
+
const nextValue = getValueAtPath(next, transition.watch);
|
|
92
|
+
|
|
93
|
+
switch (transition.condition) {
|
|
94
|
+
case 'changed':
|
|
95
|
+
return prevValue !== nextValue;
|
|
96
|
+
case 'increased':
|
|
97
|
+
return typeof nextValue === 'number' && typeof prevValue === 'number' && nextValue > prevValue;
|
|
98
|
+
case 'decreased':
|
|
99
|
+
return typeof nextValue === 'number' && typeof prevValue === 'number' && nextValue < prevValue;
|
|
100
|
+
case 'added':
|
|
101
|
+
return prevValue === undefined && nextValue !== undefined;
|
|
102
|
+
case 'removed':
|
|
103
|
+
return prevValue !== undefined && nextValue === undefined;
|
|
104
|
+
default:
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function getValueAtPath(obj: unknown, path: string): unknown {
|
|
110
|
+
const parts = path.split('.');
|
|
111
|
+
let current = obj;
|
|
112
|
+
for (const part of parts) {
|
|
113
|
+
if (current === null || current === undefined) return undefined;
|
|
114
|
+
current = (current as Record<string, unknown>)[part];
|
|
115
|
+
}
|
|
116
|
+
return current;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function useStateView<TState, TEvent extends StateEvent>({
|
|
120
|
+
initialState,
|
|
121
|
+
reducer,
|
|
122
|
+
eventSource,
|
|
123
|
+
transitions = [],
|
|
124
|
+
onStateChange,
|
|
125
|
+
}: UseStateViewOptions<TState, TEvent>): UseStateViewResult<TState> {
|
|
126
|
+
const [state, setState] = useState<TState>(initialState);
|
|
127
|
+
const [animations, setAnimations] = useState<ActiveAnimation[]>([]);
|
|
128
|
+
const prevStateRef = useRef<TState>(initialState);
|
|
129
|
+
const animationIdRef = useRef(0);
|
|
130
|
+
|
|
131
|
+
const triggerAnimation = useCallback(
|
|
132
|
+
(transition: TransitionDefinition, _event: StateEvent) => {
|
|
133
|
+
const id = `anim-${++animationIdRef.current}`;
|
|
134
|
+
const duration = transition.duration ?? 500;
|
|
135
|
+
|
|
136
|
+
const animation: ActiveAnimation = {
|
|
137
|
+
id,
|
|
138
|
+
type: transition.animate,
|
|
139
|
+
target: transition.target ?? transition.watch,
|
|
140
|
+
startTime: Date.now(),
|
|
141
|
+
duration,
|
|
142
|
+
params: transition.params,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
setAnimations((prev) => [...prev, animation]);
|
|
146
|
+
|
|
147
|
+
// Auto-remove after duration
|
|
148
|
+
setTimeout(() => {
|
|
149
|
+
setAnimations((prev) => prev.filter((a) => a.id !== id));
|
|
150
|
+
}, duration);
|
|
151
|
+
},
|
|
152
|
+
[]
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const handleEvent = useCallback(
|
|
156
|
+
(event: TEvent) => {
|
|
157
|
+
setState((prevState) => {
|
|
158
|
+
const nextState = reducer(prevState, event);
|
|
159
|
+
const changedPaths = getChangedPaths(prevState, nextState);
|
|
160
|
+
|
|
161
|
+
// Trigger animations based on transitions
|
|
162
|
+
for (const transition of transitions) {
|
|
163
|
+
if (shouldTriggerTransition(transition, prevState, nextState, changedPaths)) {
|
|
164
|
+
triggerAnimation(transition, event);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Notify listener
|
|
169
|
+
if (onStateChange && changedPaths.length > 0) {
|
|
170
|
+
onStateChange({
|
|
171
|
+
prev: prevState,
|
|
172
|
+
next: nextState,
|
|
173
|
+
changedPaths,
|
|
174
|
+
event,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
prevStateRef.current = nextState;
|
|
179
|
+
return nextState;
|
|
180
|
+
});
|
|
181
|
+
},
|
|
182
|
+
[reducer, transitions, onStateChange, triggerAnimation]
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
// Subscribe to event source
|
|
186
|
+
useEffect(() => {
|
|
187
|
+
const unsubscribe = eventSource.subscribe(handleEvent);
|
|
188
|
+
return unsubscribe;
|
|
189
|
+
}, [eventSource, handleEvent]);
|
|
190
|
+
|
|
191
|
+
const reset = useCallback(() => {
|
|
192
|
+
setState(initialState);
|
|
193
|
+
setAnimations([]);
|
|
194
|
+
prevStateRef.current = initialState;
|
|
195
|
+
}, [initialState]);
|
|
196
|
+
|
|
197
|
+
const isReplay = eventSource.mode === 'replay';
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
state,
|
|
201
|
+
animations,
|
|
202
|
+
isReplay,
|
|
203
|
+
reset,
|
|
204
|
+
};
|
|
205
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -164,3 +164,39 @@ export type {
|
|
|
164
164
|
SourceLinkProps,
|
|
165
165
|
TimeRangeSelectorProps,
|
|
166
166
|
} from './components/dashboard';
|
|
167
|
+
|
|
168
|
+
// State View components for event-driven state visualization
|
|
169
|
+
export { PipelineView, useStateView } from './components/state-view';
|
|
170
|
+
export type {
|
|
171
|
+
// Core types
|
|
172
|
+
StateEvent,
|
|
173
|
+
EventSource,
|
|
174
|
+
StateReducer,
|
|
175
|
+
StateDiff,
|
|
176
|
+
TransitionDefinition,
|
|
177
|
+
ActiveAnimation,
|
|
178
|
+
AnimationType,
|
|
179
|
+
ReplayControls,
|
|
180
|
+
StateViewDefinition,
|
|
181
|
+
// Pipeline view types
|
|
182
|
+
PipelineState,
|
|
183
|
+
PipelineStage,
|
|
184
|
+
PipelineRepoState,
|
|
185
|
+
PipelineEvent,
|
|
186
|
+
PipelineEventType,
|
|
187
|
+
// Activity view types
|
|
188
|
+
ActivityState,
|
|
189
|
+
RoomState,
|
|
190
|
+
ActivityEvent,
|
|
191
|
+
ActivityEventType,
|
|
192
|
+
// Quota view types
|
|
193
|
+
QuotaDistributionState,
|
|
194
|
+
UserQuotaState,
|
|
195
|
+
QuotaEvent,
|
|
196
|
+
QuotaEventType,
|
|
197
|
+
// Hook types
|
|
198
|
+
UseStateViewOptions,
|
|
199
|
+
UseStateViewResult,
|
|
200
|
+
// Component props
|
|
201
|
+
PipelineViewProps,
|
|
202
|
+
} from './components/state-view';
|
package/src/nodes/CustomNode.tsx
CHANGED
|
@@ -442,8 +442,8 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = (props) =>
|
|
|
442
442
|
}
|
|
443
443
|
|
|
444
444
|
// Color Contract:
|
|
445
|
-
// - scopeColor: Used as
|
|
446
|
-
// - spanColor: Used as
|
|
445
|
+
// - scopeColor: Used as FILL/background color (from library.yaml scopes)
|
|
446
|
+
// - spanColor: Used as BORDER color in workflow context (from .spans.canvas)
|
|
447
447
|
// - For non-event canvases, falls back to legacy behavior (node color or type color)
|
|
448
448
|
|
|
449
449
|
// Get colors from node data (injected by GraphRenderer)
|
|
@@ -458,15 +458,15 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = (props) =>
|
|
|
458
458
|
const stateColor =
|
|
459
459
|
state && (nodeDataStates?.[state]?.color || typeDefinition.states?.[state]?.color);
|
|
460
460
|
|
|
461
|
-
// Fill color priority: state color >
|
|
462
|
-
//
|
|
463
|
-
const baseFillColor =
|
|
461
|
+
// Fill color priority: state color > node data color > scope color > type definition color > default
|
|
462
|
+
// scopeColor provides background color from the scope definition in library.yaml
|
|
463
|
+
const baseFillColor = nodeDataColor || scopeColor || typeDefinition.color || '#888';
|
|
464
464
|
const fillColor = stateColor || baseFillColor;
|
|
465
465
|
|
|
466
|
-
// Stroke/border color priority: explicit stroke >
|
|
467
|
-
//
|
|
466
|
+
// Stroke/border color priority: explicit stroke > span color (workflow context) > fill color
|
|
467
|
+
// spanColor provides border color in workflow context (from .spans.canvas)
|
|
468
468
|
const nodeDataStroke = nodeData.stroke as string | undefined;
|
|
469
|
-
const baseStrokeColor = nodeDataStroke ||
|
|
469
|
+
const baseStrokeColor = nodeDataStroke || spanColor || fillColor;
|
|
470
470
|
|
|
471
471
|
// Apply status-based border styling
|
|
472
472
|
const status = nodeData?.status as 'draft' | 'approved' | 'implemented' | undefined;
|
|
@@ -97,13 +97,15 @@ export const OtelBoundaryNode: React.FC<NodeProps<Node<OtelBoundaryNodeData>>> =
|
|
|
97
97
|
const nodeOpacity = isHidden ? 0.4 : isActive ? 1 : 0.1;
|
|
98
98
|
|
|
99
99
|
// Color resolution
|
|
100
|
+
const scopeColor = nodeData.scopeColor as string | undefined;
|
|
100
101
|
const spanColor = nodeData.spanColor as string | undefined;
|
|
101
102
|
const nodeDataColor = nodeData.color as string | undefined;
|
|
102
|
-
|
|
103
|
+
// Fill color priority: explicit color > scope color > type definition color > default cyan
|
|
104
|
+
const baseFillColor = nodeDataColor || scopeColor || typeDefinition.color || '#06b6d4';
|
|
103
105
|
const fillColor = baseFillColor;
|
|
104
|
-
|
|
106
|
+
// Stroke color priority: explicit stroke > span color (workflow context) > fill color
|
|
105
107
|
const nodeDataStroke = nodeData.stroke as string | undefined;
|
|
106
|
-
const strokeColor = nodeDataStroke ||
|
|
108
|
+
const strokeColor = nodeDataStroke || spanColor || fillColor;
|
|
107
109
|
|
|
108
110
|
// Display info
|
|
109
111
|
const displayName = nodeProps.name;
|
|
@@ -97,13 +97,15 @@ export const OtelEventNode: React.FC<NodeProps<Node<OtelEventNodeData>>> = ({
|
|
|
97
97
|
const nodeOpacity = isHidden ? 0.4 : isActive ? 1 : 0.1;
|
|
98
98
|
|
|
99
99
|
// Color resolution
|
|
100
|
+
const scopeColor = nodeData.scopeColor as string | undefined;
|
|
100
101
|
const spanColor = nodeData.spanColor as string | undefined;
|
|
101
102
|
const nodeDataColor = nodeData.color as string | undefined;
|
|
102
|
-
|
|
103
|
+
// Fill color priority: explicit color > scope color > type definition color > default blue
|
|
104
|
+
const baseFillColor = nodeDataColor || scopeColor || typeDefinition.color || '#3b82f6';
|
|
103
105
|
const fillColor = baseFillColor;
|
|
104
|
-
|
|
106
|
+
// Stroke color priority: explicit stroke > span color (workflow context) > fill color
|
|
105
107
|
const nodeDataStroke = nodeData.stroke as string | undefined;
|
|
106
|
-
const strokeColor = nodeDataStroke ||
|
|
108
|
+
const strokeColor = nodeDataStroke || spanColor || fillColor;
|
|
107
109
|
|
|
108
110
|
// Display info
|
|
109
111
|
const displayName = nodeProps.name;
|
|
@@ -97,13 +97,15 @@ export const OtelResourceNode: React.FC<NodeProps<Node<OtelResourceNodeData>>> =
|
|
|
97
97
|
const nodeOpacity = isHidden ? 0.4 : isActive ? 1 : 0.1;
|
|
98
98
|
|
|
99
99
|
// Color resolution
|
|
100
|
+
const scopeColor = nodeData.scopeColor as string | undefined;
|
|
100
101
|
const spanColor = nodeData.spanColor as string | undefined;
|
|
101
102
|
const nodeDataColor = nodeData.color as string | undefined;
|
|
102
|
-
|
|
103
|
+
// Fill color priority: explicit color > scope color > type definition color > default orange
|
|
104
|
+
const baseFillColor = nodeDataColor || scopeColor || typeDefinition.color || '#f97316';
|
|
103
105
|
const fillColor = baseFillColor;
|
|
104
|
-
|
|
106
|
+
// Stroke color priority: explicit stroke > span color (workflow context) > fill color
|
|
105
107
|
const nodeDataStroke = nodeData.stroke as string | undefined;
|
|
106
|
-
const strokeColor = nodeDataStroke ||
|
|
108
|
+
const strokeColor = nodeDataStroke || spanColor || fillColor;
|
|
107
109
|
|
|
108
110
|
// Display info
|
|
109
111
|
const displayName = nodeProps.name;
|
|
@@ -94,13 +94,15 @@ export const OtelScopeNode: React.FC<NodeProps<Node<OtelScopeNodeData>>> = ({
|
|
|
94
94
|
const nodeOpacity = isHidden ? 0.4 : isActive ? 1 : 0.1;
|
|
95
95
|
|
|
96
96
|
// Color resolution
|
|
97
|
+
const scopeColor = nodeData.scopeColor as string | undefined;
|
|
97
98
|
const spanColor = nodeData.spanColor as string | undefined;
|
|
98
99
|
const nodeDataColor = nodeData.color as string | undefined;
|
|
99
|
-
|
|
100
|
+
// Fill color priority: explicit color > scope color > type definition color > default green
|
|
101
|
+
const baseFillColor = nodeDataColor || scopeColor || typeDefinition.color || '#22c55e';
|
|
100
102
|
const fillColor = baseFillColor;
|
|
101
|
-
|
|
103
|
+
// Stroke color priority: explicit stroke > span color (workflow context) > fill color
|
|
102
104
|
const nodeDataStroke = nodeData.stroke as string | undefined;
|
|
103
|
-
const strokeColor = nodeDataStroke ||
|
|
105
|
+
const strokeColor = nodeDataStroke || spanColor || fillColor;
|
|
104
106
|
|
|
105
107
|
// Display info
|
|
106
108
|
const displayName = nodeProps.name;
|
|
@@ -113,14 +113,15 @@ export const OtelSpanConventionNode: React.FC<
|
|
|
113
113
|
const nodeOpacity = isHidden ? 0.4 : isActive ? 1 : 0.1;
|
|
114
114
|
|
|
115
115
|
// Color resolution
|
|
116
|
+
const scopeColor = nodeData.scopeColor as string | undefined;
|
|
116
117
|
const spanColor = nodeData.spanColor as string | undefined;
|
|
117
118
|
const nodeDataColor = nodeData.color as string | undefined;
|
|
118
|
-
|
|
119
|
+
// Fill color priority: explicit color > scope color > type definition color > default purple
|
|
120
|
+
const baseFillColor = nodeDataColor || scopeColor || typeDefinition.color || '#8b5cf6';
|
|
119
121
|
const fillColor = baseFillColor;
|
|
120
|
-
|
|
121
|
-
const scopeColor = nodeData.scopeColor as string | undefined;
|
|
122
|
+
// Stroke color priority: explicit stroke > span color (workflow context) > fill color
|
|
122
123
|
const nodeDataStroke = nodeData.stroke as string | undefined;
|
|
123
|
-
const strokeColor = nodeDataStroke ||
|
|
124
|
+
const strokeColor = nodeDataStroke || spanColor || fillColor;
|
|
124
125
|
|
|
125
126
|
// Get display info
|
|
126
127
|
const displayName = nodeProps.name;
|
|
@@ -685,19 +685,17 @@ export const EdgeTypeDefinitions: Story = {
|
|
|
685
685
|
story: `
|
|
686
686
|
**Edge Type Definitions (PV Extension)**
|
|
687
687
|
|
|
688
|
-
Define reusable edge types at the canvas level in \`
|
|
688
|
+
Define reusable edge types at the canvas level in \`edgeTypes\`:
|
|
689
689
|
|
|
690
690
|
\`\`\`json
|
|
691
691
|
{
|
|
692
|
-
"
|
|
693
|
-
"
|
|
694
|
-
"
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
"directed": true
|
|
700
|
-
}
|
|
692
|
+
"edgeTypes": {
|
|
693
|
+
"depends-on": {
|
|
694
|
+
"label": "Dependency",
|
|
695
|
+
"style": "solid",
|
|
696
|
+
"color": "#ef4444",
|
|
697
|
+
"width": 2,
|
|
698
|
+
"directed": true
|
|
701
699
|
}
|
|
702
700
|
}
|
|
703
701
|
}
|
|
@@ -917,23 +915,21 @@ const EdgeFieldsComparisonTemplate = () => {
|
|
|
917
915
|
>
|
|
918
916
|
<pre style={{ margin: 0 }}>
|
|
919
917
|
{`{
|
|
920
|
-
"
|
|
921
|
-
"
|
|
922
|
-
"
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
"
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
"
|
|
934
|
-
|
|
935
|
-
"position": "middle" // start | middle | end
|
|
936
|
-
}
|
|
918
|
+
"edgeTypes": {
|
|
919
|
+
"depends-on": {
|
|
920
|
+
"label": "Dependency", // Display name
|
|
921
|
+
"style": "solid", // solid | dashed | dotted | animated
|
|
922
|
+
"color": "#ef4444", // Hex color
|
|
923
|
+
"width": 2, // Line width in pixels
|
|
924
|
+
"directed": true, // Show arrow head
|
|
925
|
+
"animation": { // Optional animation
|
|
926
|
+
"type": "flow", // flow | pulse | particle | glow
|
|
927
|
+
"duration": 1000, // Duration in ms
|
|
928
|
+
"color": "#ff0000" // Animation color
|
|
929
|
+
},
|
|
930
|
+
"labelConfig": { // Label positioning
|
|
931
|
+
"field": "weight", // Data field to display
|
|
932
|
+
"position": "middle" // start | middle | end
|
|
937
933
|
}
|
|
938
934
|
}
|
|
939
935
|
}
|