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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@tomaszatoo/graph-viewer

v2.8.5

Published

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

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)
  • 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

graphData: GraphData | Graph

Can be:

  • A raw object of the form:
    {
      nodes: { key: string, attributes: GraphNodeAttributes }[],
      edges: { source: string, target: string, attributes: GraphEdgeAttributes }[]
    }
  • Or a ready-to-go Graphology.Graph instance.

nodeRenderer?: (params) => NodeWrapper

Function to customize how a node is rendered. Gets:

{
  node: string,
  attributes: GraphNodeAttributes,
  position: Point
}

Must return a NodeWrapper (your custom Pixi Container subclass).

edgeRenderer?: (params) => EdgeWrapper

Same idea, but for edges:

{
  edge: string,
  source: string,
  target: string,
  attributes: GraphEdgeAttributes
}

Returns an EdgeWrapper.

fullscreen: boolean

If true, canvas will stretch to fit the container via viewport resize.

animate: boolean

Turns on ForceAtlas2 layout animation. Layout runs in update loop.

layoutSettings?: GraphLayoutSettings

Layout tuning object—passed to ForceAtlas2. Includes:

  • gravity
  • scalingRatio
  • slowDown
  • edgeWeightInfluence
  • etc.

options?: GraphVisualizerOptions

Low-level visual config:

{
  backgroundAlpha?: number;
  backgroundColor?: number;
}

toggleNodeSelection: string

Pass a node ID to programmatically toggle node selection.

toggleEdgeSelection: string

Pass a edge ID to programmatically toggle edge selection.

toggleNodeHighlight: string

Pass a node ID to programmatically toggle node highlight.

toggleEdgeHighlight: string

Pass a edge ID to programmatically toggle edge highlight.


🔁 Dynamic Graph Mutations

You can modify the graph reactively after initialization via these inputs:

@Input() addNodes: Record<string, GraphNodeAttributes> | null;

Dynamically adds nodes to the graph. The key is the node ID, and the value is its attributes.

@Input() addEdges: { source: string; target: string; attributes: GraphEdgeAttributes }[] | null;

Adds edges between existing nodes. Each object must define source, target, and attributes.

@Input() dropNodes: string[] | null;

Removes nodes by ID. Associated edges will be removed automatically.

@Input() dropEdges: string[] | null;

Removes edges by key (as defined by graphology). Use graph.edges() to list keys.


🧯 Outputs

onNodeSelectChange: EventEmitter<NodePointerEvent>

{
  node: string,
  attributes: GraphNodeAttributes,
  event: FederatedPointerEvent,
  selected: boolean
}

onNodeHighlightChange: EventEmitter<NodePointerEvent>

{
  node: string,
  attributes: GraphNodeAttributes,
  event: FederatedPointerEvent,
  highlighted: boolean
}

onEdgeSelectChange: EventEmitter<EdgePointerEvent>

{
  edge: string,
  attributes: GraphEdgeAttributes,
  event: FederatedPointerEvent,
  selected: boolean
}

onEdgeHighlightChange: EventEmitter<EdgePointerEvent>

{
  edge: string,
  attributes: GraphEdgeAttributes,
  event: FederatedPointerEvent,
  highlighted: boolean
}

graphInitialised: EventEmitter<Graph>

Fires once the Graphology graph is built, with all nodes rendered.

onDestroy: EventEmitter<boolean>

Fires once the GraphViewerComponent is destroyed.


🛠 Usage

Import it directly into any Angular component (it’s standalone):

import { GraphViewerComponent } from '@tomaszatoo/graph-viewer';

Use it in your HTML:

<graph-viewer
  [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()">
</graph-viewer>

Then in your component:

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.
  • ForceAtlas2 runs on the main thread—can spike with large graphs.

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