npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tomaszatoo/graph-viewer

v3.1.0

Published

An Angular standalone component for rendering interactive, animated graphs using Pixi.js and Graphology.

Downloads

196

Readme

GraphViewerComponent

An 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.


🐣 Installation

npm i @tomaszatoo/graph-viewer

🧩 TL;DR

  • Framework: Angular (standalone component, signal inputs)
  • Rendering: Pixi.js + pixi-viewport, render-on-demand (the GPU sleeps when nothing moves)
  • Layout: ForceAtlas2 worker (graphology-layout-forceatlas2)
  • Graph engine: Graphology
  • Customization: Pluggable node/edge renderers

🧠 Core Concepts

State goes in through inputs, actions are methods you call on the component instance, and everything that happens comes back out through a small set of outputs.

All rendering is done via Pixi.js on a WebGL/WebGPU 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.

The viewer renders on demand: the ticker suspends itself shortly after the layout worker stops and nothing is being dragged, hovered, or panned.


🧪 Inputs (state)

| Input | Type | Default | Description | | --- | --- | --- | --- | | graphData | GraphData \| Graph \| null | null | Raw {nodes, edges} data or a ready Graphology Graph (it is copied). Can arrive or change at any time — the scene rebuilds automatically. | | graphOptions | GraphInstantiationOptions | {multi: false, allowSelfLoops: true, type: 'directed'} | Forwarded to the Graphology constructor when building from raw data. | | nodeRenderer | NodeRendererFn | built-in | Returns your custom NodeWrapper for a node. | | edgeRenderer | EdgeRendererFn | built-in | Returns your custom EdgeWrapper for an edge. | | animate | boolean | false | Runs the ForceAtlas2 worker layout. Toggling it on/off starts/stops the worker. | | interactive | boolean | true | Enables/disables pointer interaction with nodes and edges (live). | | fullscreen | boolean | false | true requests fullscreen, false exits it. The wheel-zoom plugin is active in fullscreen only. | | layoutSettings | GraphLayoutSettings | inferred | ForceAtlas2 tuning (gravity, scalingRatio, slowDown, …). Changing it restarts a running layout. | | options | GraphVisualizerOptions | black, opaque | backgroundColor / backgroundAlpha, applied live. |

Notes:

  • If every node in graphData already has numeric x/y, the automatic initial layout (circlepack + FA2 warm-up) is skipped and your positions are respected.
  • A user-supplied radius node attribute is respected; otherwise radii are auto-scaled from node degree.

🛠 Methods (actions)

Grab the component instance (@ViewChild(GraphViewerComponent) or a template reference — the component exports itself as graphViewer) and call:

Graph mutations

viewer.addNodes({ a: { label: 'Alpha' }, b: { x: 10, y: 10, label: 'Beta' } });
const keys = viewer.addEdges([{ source: 'a', target: 'b', attributes: { label: 'a → b' } }]);
viewer.dropNodes(['a']);          // connected edges are dropped automatically
viewer.dropEdges(keys);           // by Graphology edge key
viewer.getGraph();                // the internal Graphology graph

Mutations use Graphology's merge* semantics — re-adding an existing node/edge updates it instead of throwing. Nodes added without x/y get a random initial position and are then placed by the layout (when animate is on).

Selection & highlight

viewer.selectNode('a');                  // or selectNode('a', false)
viewer.toggleNodeSelection('a');         // returns the new state
viewer.highlightNode('a');               // or highlightNode('a', false)
viewer.toggleNodeHighlight('a');
viewer.selectEdge(key); viewer.toggleEdgeSelection(key);
viewer.highlightEdge(key); viewer.toggleEdgeHighlight(key);
viewer.clearSelection();                 // 'nodes' | 'edges' | 'all' (default)
viewer.clearHighlight();
viewer.selectedNodes();                  // string[]
viewer.selectedEdges();                  // string[]

Programmatic changes emit the same selectionChange / hoverChange events as pointer interaction, just without the event property. clearSelection / clearHighlight are silent by design.

Viewport

viewer.snapToNode('a');
viewer.snapToCenter();
viewer.setZoom(1.5);            // setZoom(scale, center = true)

🧯 Outputs

selectionChange: GraphSelectionEvent

{
  type: 'node' | 'edge',
  id: string,
  attributes: GraphNodeAttributes | GraphEdgeAttributes,
  position?: Point,            // nodes
  source?: string,             // edges
  target?: string,             // edges
  event?: FederatedPointerEvent, // absent for programmatic changes
  selected: boolean
}

hoverChange: GraphHoverEvent

Same shape with highlighted: boolean instead of selected.

graphInitialised: Graph

Fires after a full scene (re)build — also when graphData changes later.

graphUpdated: Graph

Fires after incremental mutations (addNodes, addEdges, dropNodes, dropEdges).

destroyed: void

Fires when the component is destroyed.


🛠 Usage

import { GraphViewerComponent } from '@tomaszatoo/graph-viewer';
<graph-viewer #viewer="graphViewer"
  [graphData]="myGraphData"
  [nodeRenderer]="myCustomNodeRenderer"
  [edgeRenderer]="myCustomEdgeRenderer"
  [animate]="true"
  [layoutSettings]="myLayoutSettings"
  (graphInitialised)="computeGraphTheory($event)"
  (selectionChange)="handleSelection($event)"
  (hoverChange)="handleHover($event)">
</graph-viewer>

<button (click)="viewer.snapToCenter()">center</button>
@ViewChild('viewer') viewer!: GraphViewerComponent;

addSomething(): void {
  this.viewer.addNodes({ a: { label: 'Alpha' }, b: { label: 'Beta' } });
  this.viewer.addEdges([{ source: 'a', target: 'b', attributes: { weight: 1, label: 'a → b' } }]);
}

handleSelection(e: GraphSelectionEvent): void {
  if (e.type === 'node') console.log('node', e.id, e.selected);
  else console.log('edge', e.id, e.source, '→', e.target, e.selected);
}

🚚 Migrating from v2

v3 replaces the command-style inputs with methods and unifies the events:

| v2 | v3 | | --- | --- | | [addNodes], [addEdges], [dropNodes], [dropEdges] inputs | addNodes(), addEdges(), dropNodes(), dropEdges() methods | | [selectNode], [toggleNodeSelection], [highlightNode], [toggleNodeHighlight] | selectNode(), toggleNodeSelection(), highlightNode(), toggleNodeHighlight() | | [selectEdge], [toggleEdgeSelection], [highlightEdge], [toggleEdgeHighlight] | selectEdge(), toggleEdgeSelection(), highlightEdge(), toggleEdgeHighlight() | | [clearNodesSelection]="{clear: true}" & friends | clearSelection(), clearHighlight() | | [snapToNode], [snapCenter], [zoom] | snapToNode(), snapToCenter(), setZoom() | | (onNodeSelectChange), (onEdgeSelectChange) | (selectionChange) — discriminate on event.type | | (onNodeHighlightChange), (onEdgeHighlightChange) | (hoverChange) | | (onDestroy) | (destroyed) | | NodePointerEvent / EdgePointerEvent | GraphSelectionEvent / GraphHoverEvent |

Behavioural fixes worth knowing about:

  • graphData may now be set/changed at any time (async data just works).
  • Dropped nodes/edges are actually removed from the canvas.
  • Multiple <graph-viewer> instances on one page no longer share layout state.
  • Clicking a node no longer disables viewport panning.
  • fullscreen = false exits fullscreen; the CSS class follows the real fullscreen state (ESC included).

📚 Internals (aka "You can hack this")

  • NodeWrapper and EdgeWrapper are custom Containers (extendable). Label text/style is initialised once; per-frame updates only touch geometry and transforms. Override initNodeGraphics / initEdgeLine / initEdgeArrow / initLabelGraphics / positionLabel / updateLabelStyle to customize.
  • Node radii are auto-scaled using degree-based heuristics (unless you set radius).
  • The layout engine is a per-component service wrapping the graphology-layout-forceatlas2 worker; positions are synced to Pixi in the animation loop while the worker runs.
  • Rendering suspends itself ~half a second after the last activity and wakes on layout/drag/hover/pan/zoom/selection changes.

🛣️ Roadmap Ideas

  • [ ] Demo / Examples
  • [x] Selecting edges
  • [x] Error handling (duplicate-safe merges, missing-node guards)
  • [x] 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 scene entirely.
  • The initial FA2 warm-up (100 iterations) runs on the main thread — can spike with very large graphs; the animated layout itself runs in a worker.

🧬 Author Notes

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.


🕳️ License

MIT – because locking pixels behind walls is boring.