GLIMPSE runs a SocketIO server alongside its HTTP backend. External scripts can connect to it to load graphs and update the live visualization — adding, removing, restyling, hiding, and showing nodes and edges in any connected GLIMPSE frontend.
This document is the contract for those events: what to emit, what shape the data must take, and what you get back.
Working examples for every event live next to this file in
socket-testing/. Start withtest_all.py.
- How it works
- Connecting
- Conventions
- Events
- Data model reference
- Full client examples
- Common pitfalls
- Quick reference
emit "add-node" broadcast "add-node"
your script ───────────────▶ GLIMPSE ───────────────▶ every connected
(a client) ◀─────────────── server ───────────────▶ GLIMPSE frontend
ack {status} broadcast "add-node"
(incl. back to your script)
- Your script connects to the server as a SocketIO client.
- It emits an event (e.g.
update) with a JSON payload. - The server validates the payload and replies with an ack.
- The server broadcasts the event to all connected clients. Every GLIMPSE frontend applies it to its current graph.
Because the server broadcasts to everyone, your script also receives the events
it sends. That's useful for confirmation — see the broadcast loggers in
common.py.
The ack confirms the server accepted and broadcast the event — not that a frontend applied it. Frontends silently ignore operations that reference objects that don't exist in their current graph (a console warning is logged). See Common pitfalls.
| Default URL | http://127.0.0.1:5052 |
| Transport | WebSocket (falls back to HTTP long-polling) |
| Path | /socket.io (the default) |
The port is configurable on the backend via FLASK_PORT. In the Docker setup the
backend is exposed on 5052.
Python (pip install "python-socketio[client]"):
import socketio
sio = socketio.Client()
sio.connect("http://127.0.0.1:5052")Browser / Node (socket.io-client):
import { io } from "socket.io-client";
const socket = io("http://127.0.0.1:5052");CORS (browser only): the backend restricts allowed origins. For a browser script, serve it from an allowed origin (e.g.
http://localhost:5173) or set the backend'sCORS_ORIGINSenv var (comma-separated list, or*). Non-browser clients (Python, Node scripts) are not subject to CORS.
- Acks. Every event replies with a JSON ack. Request one with
sio.call(...)(Python) or an emit callback (JS):- Success:
{ "status": "ok" }(some events add fields, e.g.objectCount). - Failure:
{ "error": "<message>" }.
- Success:
- Object ids are strings. Node/edge ids are matched as strings. Numeric ids
(e.g. from a NetworkX integer-labeled graph) are converted to strings on load,
so reference them as strings (
"0", not0) in later events. - Operations target the current graph.
update,add-edge,delete-*act on whatever graph is loaded. Load a graph first.
Replace the visualized graph. Accepts either the GLIMPSE objects format or a NetworkX node-link data dump — the server detects which and normalizes it.
Emit payload — one of:
GLIMPSE objects format (see socialExample.json):
{
"objects": [
{ "objectType": "person", "elementType": "node",
"attributes": { "id": "John-Doe", "fname": "John", "age": 33 } },
{ "objectType": "friend", "elementType": "edge",
"attributes": { "id": "Jane-John", "from": "Jane-Doe", "to": "John-Doe" } }
]
}NetworkX node-link format (networkx.node_link_data(G) output):
{
"directed": false, "multigraph": false, "graph": {},
"nodes": [ { "id": "A", "type": "person", "name": "A" } ],
"edges": [ { "source": "A", "target": "B", "type": "friend" } ]
}Ack: { "status": "ok", "objectCount": <int> } or { "error": "<message>" }.
Behavior: clears any existing graph and renders the new one. Unknown node/edge types are auto-assigned a color (see Styling & themes).
Restyle a single node or edge — change its color, size, and/or hidden state.
Emit payload:
{
"id": "John-Doe",
"elementType": "node",
"updates": { "color": "#ff0000", "size": 25, "hidden": false }
}| Field | Type | Notes |
|---|---|---|
id |
string | The node or edge id (required). |
elementType |
"node" | "edge" |
Which kind of object (required). |
updates.color |
string | null |
Hex ("#ff0000") or rgba ("rgba(255,0,0,0.7)"). null = leave unchanged. |
updates.size |
number | null |
Node radius or edge thickness. null = leave unchanged. |
updates.hidden |
boolean | null |
true hides the object, false shows it. null = leave unchanged. |
Send only the properties you want to change; set the others to
null. Example — recolor without resizing:"updates": { "color": "#1f77b4", "size": null, "hidden": null }.
Ack: { "status": "ok" }, or { "error": ... } if id/elementType are
missing or elementType isn't "node"/"edge".
Switch/specialized edges: edges drawn with a special renderer (e.g. power-grid
switchedges) use a separate marker color;update'scoloraffects the line, not that marker.
Add a node to the current graph. Uses the same object shape as load-graph.
Emit payload:
{
"objectType": "person",
"elementType": "node",
"attributes": { "id": "Tony-Stark", "name": "Tony Stark", "age": 48 }
}| Field | Type | Notes |
|---|---|---|
objectType |
string | Type/group name; drives styling. Defaults to "node". |
elementType |
"node" |
Optional but recommended. |
attributes.id or attributes.name |
string | Required — used as the node id (id preferred). |
attributes.x, attributes.y |
number | Optional fixed coordinates. Omit to drop it at the graph center. |
attributes.* |
any | Any extra fields are kept and shown in the node's tooltip. |
Ack: { "status": "ok" } (server requires an attributes object).
Frontend rules: ignored if a node with that id already exists. A previously
unseen objectType is auto-assigned a color.
Add an edge between two existing nodes.
Emit payload:
{
"objectType": "friend",
"elementType": "edge",
"attributes": { "id": "Tony-John", "from": "Tony-Stark", "to": "John-Doe",
"relationship": "Acquaintance" }
}| Field | Type | Notes |
|---|---|---|
objectType |
string | Edge type/group; drives styling. Defaults to "edge". |
elementType |
"edge" |
Optional but recommended. |
attributes.from |
string | Required — source node id (must exist). |
attributes.to |
string | Required — target node id (must exist). |
attributes.id |
string | Optional edge id. Defaults to "<from>-><to>". Use it for later update/delete-edge. |
attributes.* |
any | Extra fields are kept and shown in the edge's tooltip. |
Ack: { "status": "ok" } (server requires an attributes object).
Frontend rules: ignored if either endpoint is missing, or if an edge with
that id already exists. A previously unseen objectType is auto-assigned a color.
Remove a node and all of its connected edges.
Emit payload: the node id as a string.
"Tony-Stark"Ack: { "status": "ok" }, or { "error": ... } for an empty id.
Ignored by the frontend if the node doesn't exist.
Remove a single edge.
Emit payload: the edge id as a string.
"Tony-John"Ack: { "status": "ok" }, or { "error": ... } for an empty id.
Ignored by the frontend if the edge doesn't exist.
A GLIMPSE graph is { "objects": [ ... ] }, where each object is a node or edge:
Nodes — attributes must include id (preferred) or name. Everything in
attributes is shown in the node's hover tooltip. Optional x/y pin the node.
Edges — attributes must include from and to (node ids). id is optional
(defaults to "<from>-><to>"). Other fields are kept and shown in the tooltip.
Pass networkx.node_link_data(G) straight to load-graph. The server maps it to
GLIMPSE objects:
| NetworkX | GLIMPSE | Notes |
|---|---|---|
nodes[].id |
node id (attributes.id) |
Coerced to a string. |
nodes[].type |
objectType |
String, or a { "path": [...] } dict joined with -. Defaults to "node". |
other nodes[] keys |
attributes.* |
Carried over. |
edges[] / links[] |
edges | edges (NetworkX ≥ 3.6) or links (older) — both accepted. |
edges[].source / .target |
attributes.from / .to |
Coerced to strings. |
edges[].type |
objectType |
Defaults to "edge". |
edges[].key |
part of the edge id | Multigraphs only → id is "<source>-<target>-<key>", otherwise "<source>-<target>". |
other edges[] keys |
attributes.* |
Carried over. |
A payload is treated as node-link when it has directed, multigraph, nodes,
and either edges or links. Otherwise it's validated as GLIMPSE objects format.
The id you use in update, delete-node, and delete-edge is the id GLIMPSE
assigned when the object was created:
- Node id =
attributes.idif present, elseattributes.name. - Edge id =
attributes.idif present, else"<from>-><to>". - For NetworkX loads, ids are as described in the mapping table above (and always
strings — reference integer-labeled nodes as
"0","1", …).
When a graph loads, GLIMPSE applies its default Power Grid theme. Any
objectType it doesn't recognize is automatically given a color, so arbitrary
domains (social graphs, etc.) just work. To opt into power-grid styling, use the
known group names (e.g. node, load, capacitor, switch, overhead_line,
transformer, regulator, …); the switch edge type renders with its own
marker. update lets you override color/size per object regardless of type.
import socketio, networkx as nx
sio = socketio.Client()
sio.connect("http://127.0.0.1:5052")
# 1) Load a NetworkX graph
G = nx.karate_club_graph()
print(sio.call("load-graph", nx.node_link_data(G))) # {'status': 'ok', 'objectCount': ...}
# 2) Add a node and connect it
sio.call("add-node", {
"objectType": "person", "elementType": "node",
"attributes": {"id": "New", "name": "New Person"},
})
sio.call("add-edge", {
"objectType": "knows", "elementType": "edge",
"attributes": {"id": "New-0", "from": "New", "to": "0"},
})
# 3) Restyle: recolor + enlarge the new node (leave hidden unchanged)
sio.call("update", {
"id": "New", "elementType": "node",
"updates": {"color": "#ff0000", "size": 18, "hidden": None},
})
# 4) Clean up
sio.call("delete-edge", "New-0")
sio.call("delete-node", "New")
sio.disconnect()import { io } from "socket.io-client";
const socket = io("http://127.0.0.1:5052");
socket.emit(
"load-graph",
{ objects: [
{ objectType: "person", elementType: "node", attributes: { id: "A", name: "A" } },
{ objectType: "person", elementType: "node", attributes: { id: "B", name: "B" } },
{ objectType: "friend", elementType: "edge", attributes: { id: "A-B", from: "A", to: "B" } },
] },
(ack) => console.log(ack) // { status: "ok", objectCount: 3 }
);
socket.emit("update", {
id: "A", elementType: "node",
updates: { color: "#2ca02c", size: 20, hidden: null },
});status: okbut nothing changed. The ack means the server broadcast the event; the frontend still ignores it if the target id doesn't exist. Check ids, and make sure a graph is loaded first.- Numeric ids. NetworkX integer node labels become strings on load. Use
"0", not0, in later events. add-edgebefore its nodes exist. Both endpoints must already be in the graph. Add the nodes first (or include them in the originalload-graph).- Duplicate ids.
add-node/add-edgeare ignored if the id already exists. - Custom edge id. If you don't set
attributes.idon an edge, its id is"<from>-><to>"— use that exact string forupdate/delete-edge. - No ack received. Use
sio.call(...)(or an emit callback) to get acks; a plainemitwithout a callback won't surface validation errors. - Browser client blocked. That's CORS — serve from an allowed origin or set
CORS_ORIGINSon the backend.
| Event | Payload | Ack |
|---|---|---|
load-graph |
GLIMPSE { objects: [...] } or NetworkX node-link dict |
{ status, objectCount } |
update |
{ id, elementType: "node"|"edge", updates: { color, size, hidden } } |
{ status } |
add-node |
{ objectType, elementType: "node", attributes: { id|name, x?, y?, ... } } |
{ status } |
add-edge |
{ objectType, elementType: "edge", attributes: { id?, from, to, ... } } |
{ status } |
delete-node |
node id (string) | { status } |
delete-edge |
edge id (string) | { status } |
All acks return { "error": "<message>" } instead on failure. In update, any of
color / size / hidden may be null to leave that property unchanged.
{ "objectType": "person", // type/group name — controls styling & the legend "elementType": "node", // "node" or "edge" "attributes": { /* ... */ } // id/name (nodes) or from/to (edges) + free-form data }