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

@graphmother/core

v0.5.0

Published

High-performance graph visualization using WebGPU

Readme

@graphmother/core

WebGPU-accelerated visualization for large graphs.

GraphMother pairs a Rust/WASM graph engine (topology storage, spatial indexing, hierarchy analysis) with a GPU-resident force simulation and renderer. Node and edge state lives in GPU storage buffers that are shared between the compute and render passes, so a running layout costs no per-frame CPU/GPU round trips — the target workload is code-repository graphs in the tens of thousands of nodes.

  • GPU force simulation — a suite of interchangeable force algorithms, all implemented as WGSL compute shaders.
  • GPU rendering — instanced nodes and edges, MSDF text labels that stay sharp at any zoom.
  • Visualization layers — heatmap, metaball, contour, and label layers composited over the graph.
  • Interaction — pan, zoom, hover, click, multi-select, node dragging and pinning, all hit-tested against the WASM spatial index.
  • Typed end to end — the published package ships its own .d.ts and references @webgpu/types.

Requirements

GraphMother requires WebGPU:

| Browser | Version | | ------- | --------------------------------------------- | | Chrome | 113+ | | Edge | 113+ | | Safari | 18+ (macOS Sequoia, iOS 18) | | Firefox | 141+ on Windows; behind a flag on macOS/Linux |

There is no Canvas/WebGL fallback. Probe support before you construct anything — isSupported() is a cheap synchronous check for the navigator.gpu entry point, getSupportInfo() actually requests an adapter and tells you why it failed.

Install

npm install @graphmother/core
// Deno resolves the same npm package; there is no JSR publication.
import { createGraphMother } from "npm:@graphmother/core";

Quick start

import { createGraphMother, getSupportInfo } from "@graphmother/core";

const support = await getSupportInfo();
if (!support.supported) {
  throw new Error(`WebGPU unavailable: ${support.reason}`);
}

const canvas = document.querySelector("#graph") as HTMLCanvasElement;
const graph = await createGraphMother({ canvas });

await graph.load({
  nodes: [
    { id: "a", radius: 8, color: "#4285f4" },
    { id: "b", radius: 8, color: "#ea4335" },
    { id: "c", radius: 8, color: "#fbbc04" },
  ],
  edges: [
    { source: "a", target: "b" },
    { source: "b", target: "c" },
    { source: "c", target: "a" },
  ],
});

graph.on("node:click", (event) => console.log("clicked", event.nodeId));
graph.on("selection:change", (event) => console.log(event.selectedNodes));
graph.on("simulation:end", () => graph.fitToView());

// Later, release every GPU resource the instance owns:
graph.dispose();

Loading starts the render loop and the simulation automatically. For large graphs, load() also accepts typed arrays (positions, edgePairs, nodeRadii, nodeColors, …) so you never materialize per-node objects.

Events

node:click, node:dblclick, node:hoverenter, node:hoverleave, node:dragstart, node:dragmove, node:dragend, edge:click, edge:hoverenter, edge:hoverleave, selection:change, viewport:change, simulation:tick, simulation:end, background:click, plus mutation events for node/edge add and remove. Subscribe with graph.on(name, handler) and release with graph.off(name, handler).

Force algorithms

Every layout is a GPU compute pipeline behind one switch:

graph.setForceAlgorithm("barnes-hut");
graph.setForceConfig({
  repulsionStrength: -50, // negative repels
  springStrength: 0.1,
  springLength: 30,
  centerStrength: 0.01,
  collisionEnabled: true,
});
graph.setAlphaDecay(0.0228); // d3 convention — ~300 ticks to rest
graph.restartSimulation();

| id | Algorithm | Use it for | | ------------------ | ---------------- | ------------------------------------------------------------------ | | n2 | N² direct | Exact all-pairs repulsion; small graphs (< ~10K nodes). | | barnes-hut | Barnes-Hut | GPU binary radix tree, O(N log N). General purpose, 5K–100K nodes. | | force-atlas2 | ForceAtlas2 | Network visualization with degree-weighted forces. | | linlog | LinLog | Logarithmic attraction; emphasizes community structure. | | t-fdp | t-FDP | t-distribution kernel; preserves local neighborhoods. | | density | Density field | O(N) grid approximation for very large graphs (100K+). | | relativity-atlas | Relativity Atlas | Hierarchical O(N+E) layout for DAGs and deep trees. | | tidy-tree | Tidy Tree | Buchheim tree layout — directory trees, org charts. | | community | Community | Louvain detection plus cluster-aware forces. | | codebase | Codebase | Containment-aware layout; files cluster inside their directories. |

Simulation control is on the instance: startSimulation(), pauseSimulation(), stopSimulation(), restartSimulation(), setSimulationAlpha().

Rendering pauses on its own axis: pauseRendering(), resumeRendering(), isRenderingPaused(). Use it when the host knows the canvas is not being seen while the page still is — occluded by other UI, in a collapsed panel — which document.hidden cannot report. The simulation keeps running unless paused too, and only resumeRendering() lifts a host pause.

Layers

graph.enableHeatmap({ colorScale: "viridis", radius: 50, intensity: 1.0 });
graph.enableMetaball({ threshold: 0.5, blendRadius: 20, fillColor: "#4285f4" });
await graph.enableLabels({ fontSize: 14, maxLabels: 100, priority: "degree" });
  • Heatmap — node density as a color field. Scales: viridis, plasma, inferno, magma, cividis, turbo, spectral, coolwarm, blues, reds, greens, greys.
  • Metaball — smooth-union blobs around dense regions.
  • Labels — MSDF text with priority-based culling. The layer fetches ./assets/fonts/roboto-msdf.{json,png} at runtime; those files ship in the package under assets/fonts/, so copy them into whatever directory your app serves as assets/fonts/.
  • Contour — density iso-lines. Experimental: the threshold/marching pipeline is still being tuned and its config shape may change.

Each layer has a matching disable*() and set*Config() method.

Semantic LOD & DOM cards

graph.setLodConfig({ enabled: true, domThreshold: 48, maxCards: 150 });
graph.setDomOverlay({ enabled: true });
graph.setCardProvider({
  mount(container, node) {
    container.textContent = node.label ?? String(node.externalId);
    return null;
  },
  release(container) {
    container.replaceChildren();
  },
});
graph.on("card:error", (event) => report(event.externalId, event.hook, event.error));

Subtrees fold into their ancestors as the camera pulls out; the largest nodes are promoted to real DOM as it pushes in. Core owns which nodes are carded, where each card sits and how big it is — the provider owns everything inside a card, and must remove its own children in release, because containers are pooled.

The four thresholds are two independent pairs over two different metrics, and consumer records must be keyed on node.externalId rather than on the recycled GPU slot. Both are in docs/lod-and-cards.md, which is the authority for this surface.

Framework wrappers

Thin components that own initialization and teardown for you:

License

MIT