panel-reactflow 0.2.1__tar.gz → 0.3.0rc0__tar.gz
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.
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/.github/workflows/test.yml +1 -1
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/PKG-INFO +3 -3
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/README.md +2 -2
- panel_reactflow-0.3.0rc0/docs/assets/screenshots/declare-types.png +0 -0
- panel_reactflow-0.3.0rc0/docs/assets/screenshots/define-editors-edge.png +0 -0
- panel_reactflow-0.3.0rc0/docs/assets/screenshots/define-editors-node.png +0 -0
- panel_reactflow-0.3.0rc0/docs/assets/screenshots/define-nodes-edges.png +0 -0
- panel_reactflow-0.3.0rc0/docs/assets/screenshots/embed-views-in-nodes.png +0 -0
- panel_reactflow-0.3.0rc0/docs/assets/screenshots/quickstart.png +0 -0
- panel_reactflow-0.3.0rc0/docs/assets/screenshots/react-to-events.png +0 -0
- panel_reactflow-0.3.0rc0/docs/assets/screenshots/style-nodes-edges.png +0 -0
- panel_reactflow-0.3.0rc0/docs/how-to/declare-types.md +239 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/docs/how-to/define-editors.md +110 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/docs/how-to/define-nodes-edges.md +52 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/docs/how-to/embed-views-in-nodes.md +68 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/docs/how-to/react-to-events.md +13 -7
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/docs/how-to/style-nodes-edges.md +112 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/docs/quickstart.md +0 -6
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/examples/threejs_viewer.py +2 -2
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/pixi.lock +1916 -1912
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/src/panel_reactflow/base.py +48 -9
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/src/panel_reactflow/dist/css/reactflow.css +15 -8
- panel_reactflow-0.3.0rc0/src/panel_reactflow/dist/panel-reactflow.bundle.js +88 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/src/panel_reactflow/models/reactflow.jsx +190 -13
- panel_reactflow-0.2.1/docs/assets/screenshots/declare-types.png +0 -0
- panel_reactflow-0.2.1/docs/assets/screenshots/define-editors-edge.png +0 -0
- panel_reactflow-0.2.1/docs/assets/screenshots/define-editors-node.png +0 -0
- panel_reactflow-0.2.1/docs/assets/screenshots/define-nodes-edges.png +0 -0
- panel_reactflow-0.2.1/docs/assets/screenshots/embed-views-in-nodes.png +0 -0
- panel_reactflow-0.2.1/docs/assets/screenshots/quickstart.png +0 -0
- panel_reactflow-0.2.1/docs/assets/screenshots/react-to-events.png +0 -0
- panel_reactflow-0.2.1/docs/assets/screenshots/style-nodes-edges.png +0 -0
- panel_reactflow-0.2.1/docs/how-to/declare-types.md +0 -125
- panel_reactflow-0.2.1/src/panel_reactflow/dist/panel-reactflow.bundle.js +0 -82
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/.copier-answers.yml +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/.gitattributes +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/.github/CODEOWNERS +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/.github/dependabot.yml +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/.github/workflows/build.yml +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/.github/workflows/docs.yml +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/.gitignore +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/.pre-commit-config.yaml +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/.prettierrc +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/LICENSE.txt +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/MANIFEST.in +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/docs/assets/logo.svg +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/docs/examples.md +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/docs/index.md +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/docs/reference/panel_reactflow.md +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/docs/releases.md +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/examples/advanced.py +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/examples/custom_editor.py +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/examples/edge_editors.py +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/examples/schema_types.py +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/examples/simple.py +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/hatch_build.py +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/pixi.toml +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/pyproject.toml +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/src/panel_reactflow/__init__.py +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/src/panel_reactflow/__version.py +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/src/panel_reactflow/dist/icons/gear.svg +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/src/panel_reactflow/dist/panel-reactflow.bundle.css +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/src/panel_reactflow/py.typed +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/src/panel_reactflow/schema.py +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/tests/__init__.py +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/tests/conftest.py +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/tests/test_api.py +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/tests/test_core.py +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/tests/ui/__init__.py +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/tests/ui/test_ui.py +0 -0
- {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/zensical.toml +0 -0
|
@@ -135,7 +135,7 @@ jobs:
|
|
|
135
135
|
environment: ["test-ui"]
|
|
136
136
|
timeout-minutes: 60
|
|
137
137
|
env:
|
|
138
|
-
|
|
138
|
+
PYTHONUTF8: 1
|
|
139
139
|
PANEL_LOG_LEVEL: info
|
|
140
140
|
FAIL: "--screenshot only-on-failure --full-page-screenshot --output ui_screenshots --tracing retain-on-failure"
|
|
141
141
|
steps:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: panel-reactflow
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0rc0
|
|
4
4
|
Summary: A Panel wrapper for the Reactflow JS library.
|
|
5
5
|
Project-URL: Homepage, https://github.com/panel-extensions/panel-reactflow
|
|
6
6
|
Project-URL: Source, https://github.com/panel-extensions/panel-reactflow
|
|
@@ -59,7 +59,7 @@ A Panel wrapper for the React Flow JS library.
|
|
|
59
59
|
- Panel viewables inside nodes via node ``view`` entries
|
|
60
60
|
- Interactive editing (drag/select/connect/delete) with sync back to Python
|
|
61
61
|
- Optional schema definitions for node/edge properties
|
|
62
|
-
- Event callbacks via `ReactFlow.on(...)` for app-level handling
|
|
62
|
+
- Event callbacks via `ReactFlow.on(...)` for app-level handling (callback signature: `callback(payload, flow)`)
|
|
63
63
|
|
|
64
64
|
## Pin your version!
|
|
65
65
|
|
|
@@ -112,7 +112,7 @@ flow = ReactFlow(
|
|
|
112
112
|
flow
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
For property schemas and richer editors, provide `node_types`/`edge_types` with `PropertySpec` and handle changes via `ReactFlow.on(...)`.
|
|
115
|
+
For property schemas and richer editors, provide `node_types`/`edge_types` with `PropertySpec` and handle changes via `ReactFlow.on(...)`. `.on` callbacks receive the event payload as the first argument and can optionally accept the `ReactFlow` instance as a second argument.
|
|
116
116
|
|
|
117
117
|
## Development
|
|
118
118
|
|
|
@@ -16,7 +16,7 @@ A Panel wrapper for the React Flow JS library.
|
|
|
16
16
|
- Panel viewables inside nodes via node ``view`` entries
|
|
17
17
|
- Interactive editing (drag/select/connect/delete) with sync back to Python
|
|
18
18
|
- Optional schema definitions for node/edge properties
|
|
19
|
-
- Event callbacks via `ReactFlow.on(...)` for app-level handling
|
|
19
|
+
- Event callbacks via `ReactFlow.on(...)` for app-level handling (callback signature: `callback(payload, flow)`)
|
|
20
20
|
|
|
21
21
|
## Pin your version!
|
|
22
22
|
|
|
@@ -69,7 +69,7 @@ flow = ReactFlow(
|
|
|
69
69
|
flow
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
For property schemas and richer editors, provide `node_types`/`edge_types` with `PropertySpec` and handle changes via `ReactFlow.on(...)`.
|
|
72
|
+
For property schemas and richer editors, provide `node_types`/`edge_types` with `PropertySpec` and handle changes via `ReactFlow.on(...)`. `.on` callbacks receive the event payload as the first argument and can optionally accept the `ReactFlow` instance as a second argument.
|
|
73
73
|
|
|
74
74
|
## Development
|
|
75
75
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# Declare Node & Edge Types
|
|
2
|
+
|
|
3
|
+
Node and edge types are lightweight descriptors that define **what data each
|
|
4
|
+
kind of node/edge carries**. A type can provide:
|
|
5
|
+
|
|
6
|
+
- a type name (`type`)
|
|
7
|
+
- a display label (`label`)
|
|
8
|
+
- node handles (`inputs` / `outputs`)
|
|
9
|
+
- a schema for the `data` payload (`schema`)
|
|
10
|
+
|
|
11
|
+
Types are separate from editors. A type defines structure; an editor defines
|
|
12
|
+
the UI used to edit it.
|
|
13
|
+
|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Complete runnable example
|
|
19
|
+
|
|
20
|
+
This script is a minimal, working example that produces the visualization
|
|
21
|
+
shown above.
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
import param
|
|
25
|
+
import panel as pn
|
|
26
|
+
|
|
27
|
+
from panel_reactflow import EdgeType, NodeType, ReactFlow
|
|
28
|
+
|
|
29
|
+
pn.extension("jsoneditor")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Job(param.Parameterized):
|
|
33
|
+
status = param.Selector(objects=["idle", "running", "done"])
|
|
34
|
+
retries = param.Integer(default=0)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
decision_schema = {
|
|
38
|
+
"type": "object",
|
|
39
|
+
"properties": {
|
|
40
|
+
"question": {"type": "string", "title": "Question"},
|
|
41
|
+
"outcome": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"enum": ["yes", "no", "maybe"],
|
|
44
|
+
"title": "Outcome",
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
node_types = {
|
|
50
|
+
"job": NodeType(type="job", label="Job", schema=Job, inputs=["in"], outputs=["out"]),
|
|
51
|
+
"decision": NodeType(
|
|
52
|
+
type="decision",
|
|
53
|
+
label="Decision",
|
|
54
|
+
schema=decision_schema,
|
|
55
|
+
inputs=["in"],
|
|
56
|
+
outputs=["yes", "no"],
|
|
57
|
+
),
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
edge_types = {
|
|
61
|
+
"flow": EdgeType(
|
|
62
|
+
type="flow",
|
|
63
|
+
label="Flow",
|
|
64
|
+
schema={
|
|
65
|
+
"type": "object",
|
|
66
|
+
"properties": {"weight": {"type": "number", "title": "Weight"}},
|
|
67
|
+
},
|
|
68
|
+
),
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
nodes = [
|
|
72
|
+
{
|
|
73
|
+
"id": "j1",
|
|
74
|
+
"type": "job",
|
|
75
|
+
"label": "Fetch Data",
|
|
76
|
+
"position": {"x": 0, "y": 0},
|
|
77
|
+
"data": {"status": "idle", "retries": 0},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"id": "d1",
|
|
81
|
+
"type": "decision",
|
|
82
|
+
"label": "Valid?",
|
|
83
|
+
"position": {"x": 300, "y": 250},
|
|
84
|
+
"data": {"question": "Is data valid?", "outcome": "yes"},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"id": "j2",
|
|
88
|
+
"type": "job",
|
|
89
|
+
"label": "Process",
|
|
90
|
+
"position": {"x": 600, "y": 400},
|
|
91
|
+
"data": {"status": "running", "retries": 1},
|
|
92
|
+
},
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
edges = [
|
|
96
|
+
{"id": "e1", "source": "j1", "target": "d1", "type": "flow", "data": {"weight": 1.0}},
|
|
97
|
+
{"id": "e2", "source": "d1", "target": "j2", "type": "flow", "data": {"weight": 0.8}},
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
TASK_NODE_CSS = """
|
|
101
|
+
.react-flow__node-job {
|
|
102
|
+
background-color: white;
|
|
103
|
+
border-radius: 8px;
|
|
104
|
+
border: 1.5px solid #7c3aed;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.react-flow__node-decision {
|
|
108
|
+
background-color: white;
|
|
109
|
+
border-radius: 8px;
|
|
110
|
+
border: 1.5px solid green;
|
|
111
|
+
}
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
flow = ReactFlow(
|
|
115
|
+
nodes=nodes,
|
|
116
|
+
edges=edges,
|
|
117
|
+
node_types=node_types,
|
|
118
|
+
edge_types=edge_types,
|
|
119
|
+
editor_mode="node",
|
|
120
|
+
sizing_mode="stretch_both",
|
|
121
|
+
stylesheets=[TASK_NODE_CSS]
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
pn.Column(flow, sizing_mode="stretch_both").servable()
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## How this code maps to the visualization
|
|
128
|
+
|
|
129
|
+
- `node_types["job"]` and `node_types["decision"]` define the two node kinds you see.
|
|
130
|
+
- `inputs` and `outputs` define the left/right handles rendered on each node.
|
|
131
|
+
- `edge_types["flow"]` defines the edge payload schema used by both connections.
|
|
132
|
+
- `nodes` controls labels (`Fetch Data`, `Valid?`, `Process`) and positions.
|
|
133
|
+
- `editor_mode="side"` makes selection open the schema-driven editor in the right panel.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Node type snippet
|
|
138
|
+
|
|
139
|
+
Use `NodeType` to define node handles and payload schema.
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from panel_reactflow import NodeType
|
|
143
|
+
|
|
144
|
+
task_schema = {
|
|
145
|
+
"type": "object",
|
|
146
|
+
"properties": {
|
|
147
|
+
"status": {"type": "string", "enum": ["idle", "running", "done"]},
|
|
148
|
+
"priority": {"type": "integer"},
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
node_types = {
|
|
153
|
+
"task": NodeType(
|
|
154
|
+
type="task",
|
|
155
|
+
label="Task",
|
|
156
|
+
schema=task_schema,
|
|
157
|
+
inputs=["in"],
|
|
158
|
+
outputs=["out"],
|
|
159
|
+
),
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Edge type snippet
|
|
164
|
+
|
|
165
|
+
Use `EdgeType` for edge payload schema and label.
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from panel_reactflow import EdgeType
|
|
169
|
+
|
|
170
|
+
edge_types = {
|
|
171
|
+
"pipe": EdgeType(
|
|
172
|
+
type="pipe",
|
|
173
|
+
label="Pipe",
|
|
174
|
+
schema={
|
|
175
|
+
"type": "object",
|
|
176
|
+
"properties": {
|
|
177
|
+
"throughput": {"type": "number"},
|
|
178
|
+
"protocol": {"type": "string", "enum": ["tcp", "udp", "http"]},
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
),
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Schema sources
|
|
188
|
+
|
|
189
|
+
The `schema` field accepts multiple inputs and normalizes them to JSON Schema.
|
|
190
|
+
|
|
191
|
+
| Source | Example |
|
|
192
|
+
|--------|---------|
|
|
193
|
+
| **JSON Schema dict** | `{"type": "object", "properties": {...}}` |
|
|
194
|
+
| **Param class** | A `param.Parameterized` subclass |
|
|
195
|
+
| **Pydantic model** | A `pydantic.BaseModel` subclass |
|
|
196
|
+
|
|
197
|
+
### Param class shorthand
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
import param
|
|
201
|
+
from panel_reactflow import NodeType
|
|
202
|
+
|
|
203
|
+
class Job(param.Parameterized):
|
|
204
|
+
status = param.Selector(objects=["idle", "running", "done"])
|
|
205
|
+
retries = param.Integer(default=0)
|
|
206
|
+
|
|
207
|
+
node_types = {"job": NodeType(type="job", label="Job", schema=Job)}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Pydantic model shorthand
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
from pydantic import BaseModel
|
|
214
|
+
from panel_reactflow import NodeType
|
|
215
|
+
|
|
216
|
+
class Config(BaseModel):
|
|
217
|
+
host: str = "localhost"
|
|
218
|
+
port: int = 8080
|
|
219
|
+
|
|
220
|
+
node_types = {"config": NodeType(type="config", label="Config", schema=Config)}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Register on `ReactFlow`
|
|
226
|
+
|
|
227
|
+
Pass `node_types` and `edge_types` as dictionaries keyed by type name:
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
flow = ReactFlow(
|
|
231
|
+
nodes=nodes,
|
|
232
|
+
edges=edges,
|
|
233
|
+
node_types=node_types,
|
|
234
|
+
edge_types=edge_types,
|
|
235
|
+
)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Types without a schema still work; they just do not get schema-driven
|
|
239
|
+
validation or auto-generated forms.
|
|
@@ -18,6 +18,65 @@ or falls back to a raw JSON editor.
|
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
21
|
+
## Complete runnable example (node editor screenshot)
|
|
22
|
+
|
|
23
|
+
This script is a minimal, working example for the screenshot above. Run it,
|
|
24
|
+
then click the `Start` node to open the schema-driven editor in the side panel.
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
import panel as pn
|
|
28
|
+
|
|
29
|
+
from panel_reactflow import NodeType, ReactFlow
|
|
30
|
+
|
|
31
|
+
pn.extension("jsoneditor")
|
|
32
|
+
|
|
33
|
+
task_schema = {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"properties": {
|
|
36
|
+
"status": {"type": "string", "enum": ["idle", "running", "done"], "title": "Status"},
|
|
37
|
+
"priority": {"type": "integer", "title": "Priority"},
|
|
38
|
+
"notes": {"type": "string", "title": "Notes"},
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
nodes = [
|
|
43
|
+
{
|
|
44
|
+
"id": "start",
|
|
45
|
+
"type": "task",
|
|
46
|
+
"label": "Start",
|
|
47
|
+
"position": {"x": 0, "y": 0},
|
|
48
|
+
"data": {"status": "idle", "priority": 1, "notes": ""},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "finish",
|
|
52
|
+
"type": "task",
|
|
53
|
+
"label": "Finish",
|
|
54
|
+
"position": {"x": 300, "y": 80},
|
|
55
|
+
"data": {"status": "done", "priority": 2, "notes": "All clear"},
|
|
56
|
+
},
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
edges = [{"id": "e1", "source": "start", "target": "finish"}]
|
|
60
|
+
|
|
61
|
+
flow = ReactFlow(
|
|
62
|
+
nodes=nodes,
|
|
63
|
+
edges=edges,
|
|
64
|
+
node_types={"task": NodeType(type="task", label="Task", schema=task_schema)},
|
|
65
|
+
editor_mode="side",
|
|
66
|
+
sizing_mode="stretch_both",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
pn.Column(flow, sizing_mode="stretch_both").servable()
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## How this code maps to the node-editor screenshot
|
|
73
|
+
|
|
74
|
+
- `task_schema` defines fields rendered in the side-panel form.
|
|
75
|
+
- `node_types={"task": ...}` binds that schema to both `task` nodes.
|
|
76
|
+
- Clicking a node selects it and opens its editor because `editor_mode="side"`.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
21
80
|
## Editor signature
|
|
22
81
|
|
|
23
82
|
Every editor — whether a simple function, a lambda, or a class — receives
|
|
@@ -106,6 +165,57 @@ edge type) or `default_edge_editor` for a blanket default.
|
|
|
106
165
|
|
|
107
166
|

|
|
108
167
|
|
|
168
|
+
### Complete runnable example (edge editor screenshot)
|
|
169
|
+
|
|
170
|
+
This script reproduces the edge-editor screenshot. Run it, then click the
|
|
171
|
+
`pipe` edge to open its schema-driven editor.
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
import panel as pn
|
|
175
|
+
|
|
176
|
+
from panel_reactflow import EdgeType, NodeType, ReactFlow
|
|
177
|
+
|
|
178
|
+
pn.extension("jsoneditor")
|
|
179
|
+
|
|
180
|
+
pipe_schema = {
|
|
181
|
+
"type": "object",
|
|
182
|
+
"properties": {
|
|
183
|
+
"throughput": {"type": "number", "title": "Throughput"},
|
|
184
|
+
"protocol": {
|
|
185
|
+
"type": "string",
|
|
186
|
+
"enum": ["tcp", "udp", "http"],
|
|
187
|
+
"title": "Protocol",
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
nodes = [
|
|
193
|
+
{"id": "src", "type": "device", "label": "Source", "position": {"x": 0, "y": 0}, "data": {}},
|
|
194
|
+
{"id": "sink", "type": "device", "label": "Sink", "position": {"x": 400, "y": 0}, "data": {}},
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
edges = [
|
|
198
|
+
{
|
|
199
|
+
"id": "e1",
|
|
200
|
+
"source": "src",
|
|
201
|
+
"target": "sink",
|
|
202
|
+
"type": "pipe",
|
|
203
|
+
"label": "pipe",
|
|
204
|
+
"data": {"throughput": 100.0, "protocol": "tcp"},
|
|
205
|
+
},
|
|
206
|
+
]
|
|
207
|
+
|
|
208
|
+
flow = ReactFlow(
|
|
209
|
+
nodes=nodes,
|
|
210
|
+
edges=edges,
|
|
211
|
+
node_types={"device": NodeType(type="device", label="Device")},
|
|
212
|
+
edge_types={"pipe": EdgeType(type="pipe", label="Pipe", schema=pipe_schema)},
|
|
213
|
+
sizing_mode="stretch_both",
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
pn.Column(flow, sizing_mode="stretch_both").servable()
|
|
217
|
+
```
|
|
218
|
+
|
|
109
219
|
### Schema-driven edge editor
|
|
110
220
|
|
|
111
221
|
If you declare an `EdgeType` with a schema and do not provide an explicit
|
|
@@ -13,6 +13,58 @@ and update data after the graph is live.
|
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
+
## Complete runnable example
|
|
17
|
+
|
|
18
|
+
This script is a minimal, working example that produces the visualization
|
|
19
|
+
shown above.
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
import panel as pn
|
|
23
|
+
|
|
24
|
+
from panel_reactflow import ReactFlow
|
|
25
|
+
|
|
26
|
+
pn.extension("jsoneditor")
|
|
27
|
+
|
|
28
|
+
nodes = [
|
|
29
|
+
{
|
|
30
|
+
"id": "n1",
|
|
31
|
+
"type": "panel",
|
|
32
|
+
"label": "Start",
|
|
33
|
+
"position": {"x": 0, "y": 0},
|
|
34
|
+
"data": {"status": "idle"},
|
|
35
|
+
"view": pn.pane.Markdown("Optional node body"),
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": "n2",
|
|
39
|
+
"type": "panel",
|
|
40
|
+
"label": "End",
|
|
41
|
+
"position": {"x": 300, "y": 80},
|
|
42
|
+
"data": {"status": "done"},
|
|
43
|
+
},
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
edges = [
|
|
47
|
+
{"id": "e1", "source": "n1", "target": "n2", "label": "next"},
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
flow = ReactFlow(
|
|
51
|
+
nodes=nodes,
|
|
52
|
+
edges=edges,
|
|
53
|
+
sizing_mode="stretch_both",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
pn.Column(flow, sizing_mode="stretch_both").servable()
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## How this code maps to the visualization
|
|
60
|
+
|
|
61
|
+
- `nodes` defines the two boxes (`Start`, `End`) and where they appear.
|
|
62
|
+
- `edges` defines the single connection labeled `next`.
|
|
63
|
+
- `view` on `n1` adds inline content inside that node.
|
|
64
|
+
- `ReactFlow(nodes=..., edges=...)` renders the graph from those lists.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
16
68
|
## Define nodes
|
|
17
69
|
|
|
18
70
|
A node dict requires `id`, `position`, and `data`. The display label is a
|
|
@@ -15,6 +15,74 @@ thumbnail on an image-processing node.
|
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
+
## Complete runnable example
|
|
19
|
+
|
|
20
|
+
This script is a minimal, working example that produces the visualization
|
|
21
|
+
shown above.
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
import panel as pn
|
|
25
|
+
|
|
26
|
+
from panel_reactflow import ReactFlow
|
|
27
|
+
|
|
28
|
+
pn.extension("jsoneditor")
|
|
29
|
+
|
|
30
|
+
nodes = [
|
|
31
|
+
{
|
|
32
|
+
"id": "source",
|
|
33
|
+
"type": "panel",
|
|
34
|
+
"label": "Data Source",
|
|
35
|
+
"position": {"x": 0, "y": 0},
|
|
36
|
+
"data": {},
|
|
37
|
+
"view": pn.pane.Markdown("**Status:** connected\n\nRows: 1,204 • Updated 3s ago"),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": "metric",
|
|
41
|
+
"type": "panel",
|
|
42
|
+
"label": "KPI",
|
|
43
|
+
"position": {"x": 320, "y": 0},
|
|
44
|
+
"data": {},
|
|
45
|
+
"view": pn.indicators.Number(
|
|
46
|
+
value=87.3,
|
|
47
|
+
name="Accuracy",
|
|
48
|
+
format="{value}%",
|
|
49
|
+
colors=[(90, "orange"), (100, "green")],
|
|
50
|
+
),
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "output",
|
|
54
|
+
"type": "panel",
|
|
55
|
+
"label": "Output",
|
|
56
|
+
"position": {"x": 160, "y": 180},
|
|
57
|
+
"data": {},
|
|
58
|
+
"view": pn.pane.HTML(
|
|
59
|
+
"<div style='padding:6px;font-size:12px;color:#475569;'>✅ Pipeline healthy</div>"
|
|
60
|
+
),
|
|
61
|
+
},
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
edges = [
|
|
65
|
+
{"id": "e1", "source": "source", "target": "metric"},
|
|
66
|
+
{"id": "e2", "source": "metric", "target": "output"},
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
flow = ReactFlow(
|
|
70
|
+
nodes=nodes,
|
|
71
|
+
edges=edges,
|
|
72
|
+
sizing_mode="stretch_both",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
pn.Column(flow, sizing_mode="stretch_both").servable()
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## How this code maps to the visualization
|
|
79
|
+
|
|
80
|
+
- Each node has a different `view` (`Markdown`, `Number`, `HTML`).
|
|
81
|
+
- Those views are rendered inline inside the three visible nodes.
|
|
82
|
+
- `edges` creates the two visible connections between those nodes.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
18
86
|
## Add a view to a node
|
|
19
87
|
|
|
20
88
|
Set the `view` key when defining a node. The value can be any Panel
|
|
@@ -7,8 +7,9 @@ arbitrary logic in response: update a status bar, log changes, sync to an
|
|
|
7
7
|
external database, or trigger downstream computations.
|
|
8
8
|
|
|
9
9
|
Events are registered with `flow.on(event_type, callback)`. The callback
|
|
10
|
-
receives
|
|
11
|
-
|
|
10
|
+
receives the event payload as its first argument and may optionally accept
|
|
11
|
+
the `ReactFlow` instance as a second argument. You can also listen for
|
|
12
|
+
**all** events at once by subscribing to `"*"`.
|
|
12
13
|
|
|
13
14
|

|
|
14
15
|
|
|
@@ -41,10 +42,13 @@ flow = ReactFlow(nodes=nodes, edges=edges, sizing_mode="stretch_both")
|
|
|
41
42
|
|
|
42
43
|
log = pn.pane.Markdown("Waiting for events…")
|
|
43
44
|
|
|
44
|
-
def on_node_moved(payload):
|
|
45
|
+
def on_node_moved(payload, flow):
|
|
45
46
|
node_id = payload["node_id"]
|
|
46
47
|
pos = payload["position"]
|
|
47
|
-
log.object =
|
|
48
|
+
log.object = (
|
|
49
|
+
f"**Moved** `{node_id}` → ({pos['x']:.0f}, {pos['y']:.0f}) "
|
|
50
|
+
f"(nodes: {len(flow.nodes)})"
|
|
51
|
+
)
|
|
48
52
|
|
|
49
53
|
flow.on("node_moved", on_node_moved)
|
|
50
54
|
|
|
@@ -62,9 +66,9 @@ debugging or building a generic activity log.
|
|
|
62
66
|
history = []
|
|
63
67
|
status = pn.pane.Markdown("")
|
|
64
68
|
|
|
65
|
-
def on_any_event(payload):
|
|
69
|
+
def on_any_event(payload, flow):
|
|
66
70
|
history.append(payload.get("type", "unknown"))
|
|
67
|
-
status.object = f"**Events so far:** {len(history)}"
|
|
71
|
+
status.object = f"**Events so far:** {len(history)} (edges: {len(flow.edges)})"
|
|
68
72
|
|
|
69
73
|
flow.on("*", on_any_event)
|
|
70
74
|
```
|
|
@@ -80,10 +84,12 @@ canvas background. The payload includes lists of selected `nodes` and
|
|
|
80
84
|
```python
|
|
81
85
|
details = pn.pane.JSON({}, depth=2)
|
|
82
86
|
|
|
83
|
-
def on_selection(payload):
|
|
87
|
+
def on_selection(payload, flow):
|
|
84
88
|
details.object = {
|
|
85
89
|
"selected_nodes": payload.get("nodes", []),
|
|
86
90
|
"selected_edges": payload.get("edges", []),
|
|
91
|
+
"total_nodes": len(flow.nodes),
|
|
92
|
+
"total_edges": len(flow.edges),
|
|
87
93
|
}
|
|
88
94
|
|
|
89
95
|
flow.on("selection_changed", on_selection)
|