JSON format
The visualizer reads a single JSON document with a top-level
nodes
array. Each node carries its outgoing edges as a list of target node ids. The whole file is
read in the browser — nothing is uploaded.
Schema
type InputGraph = {
nodes: InputNode[];
};
type InputNode = {
/** Unique node id. Required; numbers are coerced to strings. */
id: string | number;
/** Display text in the info panel. Defaults to `id`. */
label?: string;
/** Kind classifier — e.g. "package", "file", "class". */
type?: string;
/** Outgoing edges. */
edges?: InputEdge[];
/** Opaque pass-through, rendered in the info panel. */
meta?: unknown;
};
type InputEdge =
| string
| number
| { nodeId: string | number; edgeType: string };Fields
id— required- Unique identifier for the node. May be a string or number; numbers are coerced to strings internally. Duplicate ids cause a parse error.
label— optional- Display text shown in the info panel when the node is selected. Defaults to
id. type— optional-
Free-form kind classifier — for example
"package","file","class". Distinct values are interned the same way edge types are, and the selected-node panel surfaces the type alongside id and degree counts. edges— optional-
An array of outgoing edges. Each entry is either a bare target id (string or number) or an
object
{ nodeId, edgeType }wherenodeIdis the target andedgeTypeis a string label classifying the relationship ("calls","depends-on", etc.). Both forms can be mixed in the same array. Edges referencing unknown ids are dropped with a console warning; duplicate(from, to)pairs are collapsed (first edge type wins); self-loops are dropped. meta— optional- Any user-defined object. Rendered as a key/value list in the selected node panel. Top-level keys with string/number/boolean values render as text; nested objects are serialized as JSON.
Examples
Minimal
{
"nodes": [
{ "id": "a", "edges": ["b", "c"] },
{ "id": "b", "edges": ["c"] },
{ "id": "c" }
]
}With labels
{
"nodes": [
{ "id": "auth", "label": "Authentication service" },
{ "id": "billing", "label": "Billing service", "edges": ["auth"] },
{ "id": "reports", "label": "Reports", "edges": ["auth", "billing"] }
]
}With meta
{
"nodes": [
{
"id": "src/index.ts",
"label": "index.ts",
"edges": ["src/util.ts", "src/api.ts"],
"meta": { "lines": 142, "owner": "platform" }
},
{
"id": "src/util.ts",
"label": "util.ts",
"meta": { "lines": 38 }
},
{
"id": "src/api.ts",
"label": "api.ts",
"edges": ["src/util.ts"],
"meta": { "lines": 207 }
}
]
}With typed edges
{
"nodes": [
{
"id": "AuthService",
"edges": [
{ "nodeId": "User", "edgeType": "depends-on" },
{ "nodeId": "TokenStore", "edgeType": "depends-on" },
{ "nodeId": "Logger", "edgeType": "calls" }
]
},
{ "id": "User" },
{ "id": "TokenStore", "edges": ["Logger"] },
{ "id": "Logger" }
]
}With node types
{
"nodes": [
{
"id": "@acme/auth",
"type": "package",
"edges": ["@acme/auth/auth-service.ts", "@acme/auth/session.ts"]
},
{
"id": "@acme/auth/auth-service.ts",
"type": "file",
"label": "@acme/auth/auth-service.ts",
"edges": [
{ "nodeId": "@acme/auth/session.ts", "edgeType": "imports" },
{ "nodeId": "@acme/auth/types.ts", "edgeType": "type-imports" }
]
},
{ "id": "@acme/auth/session.ts", "type": "file" },
{ "id": "@acme/auth/types.ts", "type": "file" }
]
}Notes
- The graph is treated as
directed
—
edgeson node A pointing at node B means "A → B". - Communities are computed automatically with Louvain modularity clustering.
- Initial positions are computed with a force-directed simulation (a Rust/WASM port of the d3-force model). Layout runs in a Web Worker and emits ~12 batches before settling.
- The renderer is WebGL2-instanced; 50k+ nodes are practical on modern hardware.