Progressive expansion¶
On this page¶
Initial graph¶
For shared records, nested branches, pagination and reproducible views, use the managed expansion controller. The lower-level recipe below retains legacy callback compatibility; fixed-ID deletion alone is not a shared-branch ownership model.
Start with a meaningful core, such as a case and its primary vehicle. Add
data.expansion only when a query can return additional records. The badge's
next_count must describe the next request, not the current node degree.
Expansion registry¶
For local examples, a dictionary can map a parent node ID to nodes and edges. Production applications usually replace this dictionary with a service call. Track which records were introduced by each expansion so collapse removes only that branch.
import streamlit as st
from st_graph_workbench import graph_workbench, upsert_elements
RELATED = {
"ABC123": {
"nodes": [
{
"data": {
"id": "person-maya",
"label": "PERSON",
"name": "Maya Reed",
}
}
],
"edges": [
{
"data": {
"id": "ABC123-person-maya",
"label": "REGISTERED_TO",
"source": "ABC123",
"target": "person-maya",
}
}
],
}
}
if "graph" not in st.session_state:
st.session_state.graph = {
"nodes": [
{
"data": {
"id": "ABC123",
"label": "VEHICLE",
"name": "ABC123",
"expansion": {
"state": "collapsed",
"next_count": 1,
"total_count": 2,
"depth": 1,
},
}
}
],
"edges": [],
}
def handle_event() -> None:
event = st.session_state.get("expansion-graph")
if not isinstance(event, dict) or event.get("action") != "expand":
return
for node_id in event.get("data", {}).get("node_ids", []):
related = RELATED.get(node_id)
if related:
st.session_state.graph = upsert_elements(
st.session_state.graph,
nodes=related["nodes"],
edges=related["edges"],
)
graph_workbench(
st.session_state.graph,
node_actions=["expand"],
on_change=handle_event,
key="expansion-graph",
)
Handle expand and collapse¶
Deduplicate the event timestamp, verify that the selected node is expandable,
retrieve related records, validate endpoint references, and then upsert. On
collapse, call delete_elements() with the introduced node IDs and
remove_incident_edges=True.
Update expansion metadata after each successful operation. Expanded metadata
should report collapse_count; collapsed metadata should report the next
available count. Remove metadata when no further action exists.
Keep positions stable¶
Capture positions events and apply them to Python nodes. New nodes should be
placed near the expansion origin or added through a layout constrained to the
new neighborhood. Do not replace the entire graph or rerun an animated global
layout after every expansion.
Failure handling¶
- Leave the graph unchanged if retrieval or validation fails.
- Display a Python-side error explaining the source or malformed record.
- Never issue edges before their endpoint nodes are known.
- Give every retry a new command ID only when it represents a new command.
- Do not increment badges optimistically unless rollback is implemented.
Conclusion¶
Progressive expansion is reliable when the application can answer three questions: what will be added, what belongs to this branch, and where the existing graph should remain after the update.