Persisted layouts and scale¶
On this page¶
Persist positions¶
Enable return_positions=True, deduplicate position events, and store model
coordinates by stable node ID. Persist positions separately from business data
when the layout is user-specific.
import copy
import streamlit as st
from st_graph_workbench import graph_workbench
def apply_positions(elements: dict, position_records: list[dict]) -> dict:
positions = {
str(item["id"]): item["position"]
for item in position_records
if "id" in item and "position" in item
}
updated = copy.deepcopy(elements)
for node in updated["nodes"]:
node_id = str(node["data"]["id"])
if node_id in positions:
node["position"] = positions[node_id]
return updated
event = graph_workbench(
st.session_state.elements,
layout={"name": "preset", "fit": True, "padding": 40},
return_positions=True,
connected_drag={"enabled": False, "depth": 1, "max_nodes": 100},
key="persisted-layout",
)
if isinstance(event, dict) and event.get("action") == "positions":
st.session_state.elements = apply_positions(
st.session_state.elements,
event.get("data", {}).get("positions", []),
)
Restore a layout¶
Attach each saved position to its node and use
layout={"name": "preset", "fit": True, "padding": 40}. Missing positions are
valid during gradual migration, but a complete saved layout produces the most
predictable result.
Command-driven updates¶
import streamlit as st
from st_graph_workbench import (
apply_graph_command,
graph_workbench,
upsert_elements_command,
)
if "elements" not in st.session_state:
st.session_state.elements = {"nodes": [], "edges": []}
if "commands" not in st.session_state:
st.session_state.commands = []
if st.button("Load sighting"):
command = upsert_elements_command(
"load-sighting-1",
nodes=[
{"data": {"id": "ABC123", "label": "VEHICLE", "name": "ABC123"}},
{"data": {"id": "gate-4", "label": "LOCATION", "name": "Gate 4"}},
],
edges={
"data": {
"id": "ABC123-gate-4",
"label": "SEEN_AT",
"source": "ABC123",
"target": "gate-4",
}
},
)
st.session_state.elements = apply_graph_command(st.session_state.elements, command)
st.session_state.commands = [command]
graph_workbench(
st.session_state.elements,
graph_commands=st.session_state.commands,
elements_sync="initial",
key="command-driven-graph",
)
elements_sync="initial" preserves the live Cytoscape viewport while commands
add, update, or remove small batches. Mirror mutation commands in Python so a
future remount starts from the same graph.
For a user-triggered cursor workflow, pass ProgressiveLoadConfig through the
progressive_loading argument. The complete callback and event dictionary are
shown in the progressive loading guide.
Large and dense graphs¶
Choose large when element count and layout cost dominate. Choose dense when
edge count and visual clutter dominate. Combine profiles with progressive
expansion, labels that disappear at low zoom, non-animated updates, and focused
subgraph workflows.
Operational checks¶
Measure node/edge totals, layout duration, command size, expansion latency, browser memory, and drag/search responsiveness. Test repeated expansion, collapse, selection, deselection, and viewport restoration rather than only the initial render.
Conclusion¶
Persisted positions protect analytical context. Incremental commands protect responsiveness. Both depend on stable IDs and synchronized Python state.