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.
Files changed (71) hide show
  1. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/.github/workflows/test.yml +1 -1
  2. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/PKG-INFO +3 -3
  3. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/README.md +2 -2
  4. panel_reactflow-0.3.0rc0/docs/assets/screenshots/declare-types.png +0 -0
  5. panel_reactflow-0.3.0rc0/docs/assets/screenshots/define-editors-edge.png +0 -0
  6. panel_reactflow-0.3.0rc0/docs/assets/screenshots/define-editors-node.png +0 -0
  7. panel_reactflow-0.3.0rc0/docs/assets/screenshots/define-nodes-edges.png +0 -0
  8. panel_reactflow-0.3.0rc0/docs/assets/screenshots/embed-views-in-nodes.png +0 -0
  9. panel_reactflow-0.3.0rc0/docs/assets/screenshots/quickstart.png +0 -0
  10. panel_reactflow-0.3.0rc0/docs/assets/screenshots/react-to-events.png +0 -0
  11. panel_reactflow-0.3.0rc0/docs/assets/screenshots/style-nodes-edges.png +0 -0
  12. panel_reactflow-0.3.0rc0/docs/how-to/declare-types.md +239 -0
  13. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/docs/how-to/define-editors.md +110 -0
  14. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/docs/how-to/define-nodes-edges.md +52 -0
  15. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/docs/how-to/embed-views-in-nodes.md +68 -0
  16. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/docs/how-to/react-to-events.md +13 -7
  17. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/docs/how-to/style-nodes-edges.md +112 -0
  18. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/docs/quickstart.md +0 -6
  19. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/examples/threejs_viewer.py +2 -2
  20. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/pixi.lock +1916 -1912
  21. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/src/panel_reactflow/base.py +48 -9
  22. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/src/panel_reactflow/dist/css/reactflow.css +15 -8
  23. panel_reactflow-0.3.0rc0/src/panel_reactflow/dist/panel-reactflow.bundle.js +88 -0
  24. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/src/panel_reactflow/models/reactflow.jsx +190 -13
  25. panel_reactflow-0.2.1/docs/assets/screenshots/declare-types.png +0 -0
  26. panel_reactflow-0.2.1/docs/assets/screenshots/define-editors-edge.png +0 -0
  27. panel_reactflow-0.2.1/docs/assets/screenshots/define-editors-node.png +0 -0
  28. panel_reactflow-0.2.1/docs/assets/screenshots/define-nodes-edges.png +0 -0
  29. panel_reactflow-0.2.1/docs/assets/screenshots/embed-views-in-nodes.png +0 -0
  30. panel_reactflow-0.2.1/docs/assets/screenshots/quickstart.png +0 -0
  31. panel_reactflow-0.2.1/docs/assets/screenshots/react-to-events.png +0 -0
  32. panel_reactflow-0.2.1/docs/assets/screenshots/style-nodes-edges.png +0 -0
  33. panel_reactflow-0.2.1/docs/how-to/declare-types.md +0 -125
  34. panel_reactflow-0.2.1/src/panel_reactflow/dist/panel-reactflow.bundle.js +0 -82
  35. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/.copier-answers.yml +0 -0
  36. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/.gitattributes +0 -0
  37. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/.github/CODEOWNERS +0 -0
  38. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/.github/dependabot.yml +0 -0
  39. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/.github/workflows/build.yml +0 -0
  40. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/.github/workflows/docs.yml +0 -0
  41. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/.gitignore +0 -0
  42. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/.pre-commit-config.yaml +0 -0
  43. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/.prettierrc +0 -0
  44. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/LICENSE.txt +0 -0
  45. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/MANIFEST.in +0 -0
  46. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/docs/assets/logo.svg +0 -0
  47. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/docs/examples.md +0 -0
  48. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/docs/index.md +0 -0
  49. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/docs/reference/panel_reactflow.md +0 -0
  50. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/docs/releases.md +0 -0
  51. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/examples/advanced.py +0 -0
  52. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/examples/custom_editor.py +0 -0
  53. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/examples/edge_editors.py +0 -0
  54. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/examples/schema_types.py +0 -0
  55. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/examples/simple.py +0 -0
  56. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/hatch_build.py +0 -0
  57. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/pixi.toml +0 -0
  58. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/pyproject.toml +0 -0
  59. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/src/panel_reactflow/__init__.py +0 -0
  60. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/src/panel_reactflow/__version.py +0 -0
  61. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/src/panel_reactflow/dist/icons/gear.svg +0 -0
  62. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/src/panel_reactflow/dist/panel-reactflow.bundle.css +0 -0
  63. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/src/panel_reactflow/py.typed +0 -0
  64. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/src/panel_reactflow/schema.py +0 -0
  65. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/tests/__init__.py +0 -0
  66. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/tests/conftest.py +0 -0
  67. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/tests/test_api.py +0 -0
  68. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/tests/test_core.py +0 -0
  69. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/tests/ui/__init__.py +0 -0
  70. {panel_reactflow-0.2.1 → panel_reactflow-0.3.0rc0}/tests/ui/test_ui.py +0 -0
  71. {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
- PYTHONIOENCODING: utf8
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.2.1
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
 
@@ -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
+ ![Screenshot: multiple node types with different schemas](../assets/screenshots/declare-types.png)
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
  ![Screenshot: an edge editor open in the side panel](../assets/screenshots/define-editors-edge.png)
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 a single dict containing the event payload. You can also listen
11
- for **all** events at once by subscribing to `"*"`.
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
  ![Screenshot: a status bar updating in response to graph events](../assets/screenshots/react-to-events.png)
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 = f"**Moved** `{node_id}` → ({pos['x']:.0f}, {pos['y']:.0f})"
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)