An Angular standalone component for rendering interactive, animated graphs using Pixi.js and Graphology.
npm install @tomaszatoo/graph-viewerGraphViewerComponentAn Angular standalone component for rendering interactive, animated graphs using Pixi.js and Graphology.
Designed for modularity, performance, and customizability—bring your own renderers, drive your own layout, hack the internals.
---
bash
npm i @tomaszatoo/graph-viewer
` 🧩 TL;DR
- Framework: Angular (standalone component)
- Rendering: Pixi.js +
pixi-viewport
- Layout: ForceAtlas2 (graphology-layout-forceatlas2)
- Graph engine: Graphology
- Customization: Pluggable node/edge renderers---
🧠 Core Concepts
This component is built around the idea of rendering just enough of the graph, as fast as possible, while letting you hook into every meaningful part of the lifecycle.
All rendering is done via Pixi.js on a WebGL canvas. Interaction (hovering, dragging, selection) is mapped using Pixi’s
FederatedPointerEvent system.Graph logic (nodes/edges/attributes/layout) is delegated to Graphology, allowing separation of state and rendering.
---
🧪 Inputs
$3
Can be:
- A raw object of the form:
`ts
{
nodes: { key: string, attributes: GraphNodeAttributes }[],
edges: { source: string, target: string, attributes: GraphEdgeAttributes }[]
}
`
- Or a ready-to-go Graphology.Graph instance.$3
Function to customize how a node is rendered. Gets:
`ts
{
node: string,
attributes: GraphNodeAttributes,
position: Point
}
`
Must return a NodeWrapper (your custom Pixi Container subclass).$3
Same idea, but for edges:
`ts
{
edge: string,
source: string,
target: string,
attributes: GraphEdgeAttributes
}
`
Returns an EdgeWrapper.$3
If true, canvas will stretch to fit the container via viewport resize.$3
Turns on ForceAtlas2 layout animation. Layout runs in update loop.$3
Layout tuning object—passed to ForceAtlas2. Includes:
- gravity
- scalingRatio
- slowDown
- edgeWeightInfluence
- etc.$3
Low-level visual config:
`ts
{
backgroundAlpha?: number;
backgroundColor?: number;
}
`$3
Pass a node ID to programmatically toggle node selection.$3
Pass a edge ID to programmatically toggle edge selection.$3
Pass a node ID to programmatically toggle node highlight.$3
Pass a edge ID to programmatically toggle edge highlight.---
🔁 Dynamic Graph Mutations
You can modify the graph reactively after initialization via these inputs:
`ts
@Input() addNodes: Record | null;
`
Dynamically adds nodes to the graph. The key is the node ID, and the value is its attributes.`ts
@Input() addEdges: { source: string; target: string; attributes: GraphEdgeAttributes }[] | null;
`
Adds edges between existing nodes. Each object must define source, target, and attributes.`ts
@Input() dropNodes: string[] | null;
`
Removes nodes by ID. Associated edges will be removed automatically.`ts
@Input() dropEdges: string[] | null;
`
Removes edges by key (as defined by graphology). Use graph.edges() to list keys.---
🧯 Outputs
$3
`ts
{
node: string,
attributes: GraphNodeAttributes,
event: FederatedPointerEvent,
selected: boolean
}
`$3
`ts
{
node: string,
attributes: GraphNodeAttributes,
event: FederatedPointerEvent,
highlighted: boolean
}
`
$3
`ts
{
edge: string,
attributes: GraphEdgeAttributes,
event: FederatedPointerEvent,
selected: boolean
}
`$3
`ts
{
edge: string,
attributes: GraphEdgeAttributes,
event: FederatedPointerEvent,
highlighted: boolean
}
`$3
Fires once the Graphology graph is built, with all nodes rendered.$3
Fires once the GraphViewerComponent is destroyed.
---
🛠 Usage
Import it directly into any Angular component (it’s standalone):
`ts
import { GraphViewerComponent } from '@tomaszatoo/graph-viewer';
`Use it in your HTML:
`html
[graphData]="myGraphData"
[addNodes]=“newNodesToAdd”
[addEdges]=“newEdgesToAdd”
[dropNodes]=“nodesToDrop”
[dropEdges]=“edgesToDrop”
[nodeRenderer]="myCustomNodeRenderer"
[edgeRenderer]="myCustomEdgeRenderer"
[animate]="true"
[fullscreen]="true"
[layoutSettings]="myLayoutSettings"
(graphInitialised)="computeGraphTheory($event)"
(onNodeSelectChange)="handleNodeSelection($event)"
(onEdgeSelectChange)="handleEdgeSelection($event)"
(onNodeHighlightChange)="handleNodeSelection($event)"
(onEdgeHighlightChange)="handleEdgeSelection($event)"
(onDestroy)="clean()">
`Then in your component:
`ts
newNodesToAdd = {
“a”: { x: 0, y: 0, label: “Alpha” },
“b”: { x: 10, y: 10, label: “Beta” }
};newEdgesToAdd = [
{ source: “a”, target: “b”, attributes: { weight: 1, label: “a → b” } }
];
nodesToDrop = [“a”];
edgesToDrop = [“a=>b”]; // Edge keys as returned by graphology’s .edges()
`---
📚 Internals (aka "You can hack this")
-
NodeWrapper and EdgeWrapper are custom Containers (extendable).
- Node radii are auto-scaled using degree-based heuristics:
`
radius = base + log(degree)
`
- The layout engine is a service wrapping graphology-layout-forceatlas2, updated in the animation loop.
- Selection state is internal but exposed via onSelectionChange.---
🛣️ Roadmap Ideas
- [ ] Demo / Examples
- [x] Selecting eges
- [ ] Error handling
- [ ] Arrowheads and directed edges
- [ ] Cluster folding / node collapsing
- [ ] Tooltip system with hover delay
- [ ] Export to PNG / SVG
- [ ] Mini-map viewport tracker
---
📎 Dependencies
-
pixi.js
- pixi-viewport
- graphology
- graphology-layout-forceatlas2---
🐛 Known Quirks
- Changing
graphData` destroys/rebuilds the graph entirely.---
Built for high-performance graph rendering in web dashboards, internal devtools, and exploratory graph hacking. Designed to be extended, broken, refactored, and shaped by the needs of weird data.
---
MIT – because locking pixels behind walls is boring.