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

@fortemi/graph

v2026.7.5

Published

Framework-agnostic graph tooling for fortemi: pure TypeScript helpers for community-graph layout, filtering, color assignment, degree sizing, bounds/fit, neighborhood expansion, and static snapshot serialization, plus an optional controller subpath for da

Readme

@fortemi/graph

Framework-agnostic graph projection helpers for rendering Fortemi community graphs anywhere

Turn a CommunityGraph into something you can draw — deterministic layout, filtering, community coloring, degree-based sizing, bounds/fit, neighborhood expansion, and static snapshot serialization. Framework-agnostic (no React). The root graph helpers stay database-free and tree-shakeable for JS-only hosts, and run entirely client-side — no network, no database. Database-backed graph-source selection is available from @fortemi/graph/controller.

pnpm add @fortemi/graph

npm version npm downloads License: AGPL-3.0-only Node Version TypeScript Built with aiwg

Install · Why Fortemi · Quick Start · API · Data Model · Docs · License


What @fortemi/graph Is

@fortemi/graph is the framework-agnostic projection layer for Fortemi relationship graphs. It takes a plain CommunityGraph — the shape produced by @fortemi/core's GraphRepository and AIWG index export — and provides the pure, deterministic helpers needed to lay it out, filter it, color it, size it, fit it to a viewport, and render it as SVG or canvas.

It is an add-on, not a base layer. @fortemi/core remains the foundation and owns graph production (similarity and link graphs built from the PGlite store) and community detection. The @fortemi/graph root entry owns graph projection and has no runtime dependency on @fortemi/core. The optional @fortemi/graph/controller subpath owns live graph-source orchestration (GraphController) and is the only graph entry that touches the PGlite-backed repositories. @fortemi/core never depends on @fortemi/graph.

Why Fortemi Graph

Rendering a knowledge graph usually means re-implementing layout math, community coloring, degree sizing, and viewport fitting in every host. Fortemi centralizes that logic in one package, so a React app and a static site stay visually aligned and share the exact same deterministic projection.

| Need | What Fortemi Graph provides | |---|---| | Deterministic layout | layoutCommunityGraph — a seeded force settlement plus closed-form radial/community/manual positions; identical input always yields identical coordinates | | Visibility control | filterCommunityGraph — filter by community, edge kind, node allow-list, or predicate | | Readable encoding | Degree-based node sizing and deterministic community color assignment | | Viewport math | Bounding box plus a centered fit transform for SVG/canvas | | Interaction | Neighborhood expansion and induced-subgraph helpers for selection | | Portable views | Stable snapshot serialization for static, precomputed graphs | | Zero lock-in | Pure TypeScript, no React, structurally compatible with @fortemi/core data |

What You Can Build

  • Static documentation relationship maps rendered from a precomputed snapshot
  • Custom React, Svelte, or Vue graph views that share Fortemi's projection logic
  • Server- or build-time SVG generation of AIWG relationship graphs
  • Selection and "expand neighborhood" interactions over a community graph
  • Themed community visualizations using a custom color palette

Architecture at a Glance

@fortemi/core produces a CommunityGraph and detects communities. @fortemi/graph consumes that shape; it depends on @fortemi/core but never on React, and @fortemi/core never depends on it. filterCommunityGraph narrows what is shown, layoutCommunityGraph assigns deterministic coordinates, and the color/degree/bounds helpers turn the positioned graph into draw calls. serializeGraphSnapshot freezes a graph to portable JSON for hosts that render without recomputing it.

Installation

pnpm add @fortemi/graph
# or
npm install @fortemi/graph

No peer dependencies. Ships ESM with type declarations and works in browsers, Node, and bundlers.

Quick Start

import {
  filterCommunityGraph,
  layoutCommunityGraph,
  colorForCommunity,
  nodeRadius,
  type CommunityGraph,
} from '@fortemi/graph'

const graph: CommunityGraph = {
  nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
  edges: [{ source: 'a', target: 'b', weight: 1, kind: 'similarity' }],
  communities: [{ id: 'c1', nodes: ['a', 'b'] }],
}

const visible = filterCommunityGraph(graph, { edgeKinds: ['similarity'] })
const { nodes, nodeIndex } = layoutCommunityGraph(visible, {
  algorithm: 'community',
  width: 800,
  height: 600,
})

for (const node of nodes) {
  // deterministic coordinates, degree-based radius, community color
  console.log(node.id, node.x, node.y, nodeRadius(node.degree), colorForCommunity(node.communityId))
}

Data Model

interface GraphNode { id: string }
interface GraphEdge { source: string; target: string; weight: number; kind?: string }
interface GraphCommunity { id: string; nodes: string[] }
interface CommunityGraph {
  nodes: GraphNode[]
  edges: GraphEdge[]
  communities: GraphCommunity[]
}

These shapes are structurally identical to the ones @fortemi/core produces (GraphRepository.buildLinkGraph(), buildSimilarityGraph(), and aiwgFortemiIndexToCommunityGraph()), so a core graph drops straight into these helpers without conversion.

API

| Helper | Purpose | |---|---| | layoutCommunityGraph(graph, opts?) | Deterministic 2D positions — a seeded force settlement (link attraction, charge repulsion, collision spacing, community cohesion, centering, bounds clamping) or closed-form radial/community/manual. Returns per-node x, y, r, degree, community, plus community centroids | | filterCommunityGraph(graph, filter?) | Filter by community, edge kind, node allow-list, or predicate; drops emptied communities | | computeDegrees(graph) / nodeRadius(degree, opts?) | Undirected degree map and degree → render radius | | colorForCommunity(id, palette?) | Deterministic community → color (themeable palette) | | computeGraphBounds(nodes) / fitGraphToViewport(bounds, viewport, opts?) | Bounding box and a centered fit transform | | neighborsOf / expandNeighborhood / subgraphForNodes / neighborhoodSubgraph / buildAdjacency | Selection and BFS neighborhood expansion | | serializeGraphSnapshot / stringifyGraphSnapshot / deserializeGraphSnapshot | Stable, reproducible static snapshots for JS-only hosts | | mapCommunityGraph(graph, opts?) | CommunityGraph → render-ready RenderGraph (labels, degree size, per-community tone, baked positions) shared by every renderer tier | | bakeRenderGraph(graph, opts?) / stringifyRenderGraph | Build-time writer: run layout once, emit a deterministic baked-position snapshot | | loadRenderSnapshot(source, opts?) | Snapshot-first warm start: load a precomputed baked-position graph (URL/object/thunk); null → fall back to a live build |

Layout options (force)

The force algorithm runs a fixed-iteration, seeded settlement — fully synchronous and headless (no animation frames), so identical (graph, options) always yields identical coordinates. Suitable for static SVG generation, SSR, and browser rendering. The radial, community, and manual algorithms are closed-form and honor width/height/boundsPadding only.

| Option | Default | Purpose | |---|---|---| | algorithm | 'force' | force (settlement) · radial · community · manual (closed-form) | | width / height | 760 / 460 | Canvas size | | seed | 1 | PRNG seed for initial jitter (identical seed ⇒ identical output) | | ticks | 300 | Settlement iterations | | nodeRadius | degree-derived | Per-node radius: a fixed number, NodeRadiusOptions, or (degree, node) => number | | linkDistance / linkStrength | 60 / 0.08 | Spring target length and stiffness | | chargeStrength | -240 | Repulsion magnitude (negative pushes apart) | | collisionPadding | 2 | Extra spacing beyond r_i + r_j | | communityStrength | 0.05 | Pull toward the node's community centroid | | boundsPadding | 24 | Keep every node center this many px from each edge |

Each positioned node carries a stable render radius r (degree-derived by default, overrideable via nodeRadius), and the result includes communities centroids over the final positions.

All helpers are pure: they never mutate their inputs and (except for an optional snapshot timestamp) produce identical output for identical input.

What You Get

  • Deterministic, dependency-free graph projection that renders identically across hosts
  • One source of truth for layout, filtering, coloring, and sizing shared with @fortemi/react
  • Portable snapshots so a static site can render an AIWG graph without React or PGlite
  • Full TypeScript types, re-exported CommunityGraph model, ESM output

Produce a Snapshot from @fortemi/core

The core package builds the graph; the graph package serializes it to a static file. Core does not depend on graph — you compose them at the call site:

import { GraphRepository } from '@fortemi/core'
import { stringifyGraphSnapshot } from '@fortemi/graph'
import { writeFileSync } from 'node:fs'

const graph = await new GraphRepository(db).buildLinkGraph()
writeFileSync(
  'public/graph-snapshot.json',
  stringifyGraphSnapshot(graph, { layout: { algorithm: 'community', width: 800, height: 600 } }),
)

Choosing a Renderer

The same CommunityGraph can be drawn by more than one renderer tier. Every tier honors the shared GraphControlContractalgorithm, filters (community / edge-kind / node allow-list / minDegree), selectedNodeId + onSelectNode, onNavigate, labelFor, colors — so switching tiers is mechanical: pass the same options object.

| Tier | Import | Deps | Best for | |---|---|---|---| | JS-only SVG | renderCommunityGraph (@fortemi/graph) | none | Static sites, SSR/snapshot pipelines, no-framework hosts | | React (static) | GraphView (@fortemi/react) | React | React apps wanting the component + hooks ecosystem | | Interactive 2D | SigmaGraphView (@fortemi/react/graph-2d) | sigma + graphology (optional, lazy) | Live force settling, camera focus, LOD labels | | 3D | ForceGraph3DView (@fortemi/react/graph-3d) | react-force-graph-3d (optional, lazy) | Orbitable 3D force graph, 2D/3D toggle |

communityLegend(graph) returns { communityId, color, count }[] — the shared data any tier can render as a legend and use to drive the communityIds show/hide filter. applyControlFilters(graph, filters) is the one filter function every tier runs, so visibility is identical across tiers.

Render in a Vanilla JS Host (no React)

renderCommunityGraph is a batteries-included SVG renderer built on the engine — zoom/pan, hover-neighborhood highlighting, click + keyboard selection, navigation, a labeled popup, and focus(id) search — with no React and no canvas library. It stays pixel-comparable to @fortemi/react's GraphView:

import { renderCommunityGraph, deserializeGraphSnapshot } from '@fortemi/graph'

const graph = deserializeGraphSnapshot(await (await fetch('./graph-snapshot.json')).json())

const view = renderCommunityGraph(document.getElementById('graph'), graph, {
  algorithm: 'community',
  filters: { edgeKinds: ['similarity', 'link'], minDegree: 1 },
  labelFor: (id) => graph.nodes.find((n) => n.id === id)?.id ?? id,
  onSelectNode: (id) => console.log('selected', id),
  onNavigate: (id) => location.assign(`#/note/${id}`),
})

view.update({ filters: { communityIds: ['c1'] } }) // reactive filter change
view.focus('note-42')                               // search / focus a node
// view.destroy()                                   // tear down

The React tier takes the same contract fields, so a consumer can offer both and switch with no rewiring:

import { GraphView } from '@fortemi/react'

<GraphView
  graph={graph}
  layout={{ algorithm: 'community' }}
  filters={{ edgeKinds: ['similarity', 'link'], minDegree: 1 }}
  selectedNodeId={selected}
  onSelectNode={setSelected}
  onNavigate={(id) => navigate(`/note/${id}`)}
  labelFor={(id) => titleOf(id)}
/>

Roll your own projection

If you need a fully custom view, the projection helpers are exposed directly — renderCommunityGraph is just a consumer of them — so you can emit your own SVG/canvas with no PGlite:

import {
  deserializeGraphSnapshot,
  filterCommunityGraph,
  layoutCommunityGraph,
  computeGraphBounds,
  fitGraphToViewport,
  colorForCommunity,
  nodeRadius,
} from '@fortemi/graph'

const width = 800
const height = 600

const snapshot = await (await fetch('./graph-snapshot.json')).json()
const graph = deserializeGraphSnapshot(snapshot)

// 1. filter (e.g. hide a private community, or keep only certain edge kinds)
const visible = filterCommunityGraph(graph, { edgeKinds: ['similarity', 'link'] })

// 2. lay out — deterministic, so the same data always renders identically
const { nodes, edges, nodeIndex } = layoutCommunityGraph(visible, {
  algorithm: 'community',
  width,
  height,
})

// 3. fit the laid-out graph into the viewport
const view = fitGraphToViewport(computeGraphBounds(nodes), { width, height }, { padding: 24 })

// 4. project to SVG
const line = (e) => {
  const a = nodeIndex.get(e.source)
  const b = nodeIndex.get(e.target)
  if (!a || !b) return ''
  const w = Math.max(1, Math.min(5, e.weight))
  return `<line x1="${a.x}" y1="${a.y}" x2="${b.x}" y2="${b.y}" stroke="#9aa0a6" stroke-width="${w}" opacity="0.55" />`
}
const circle = (n) =>
  `<circle cx="${n.x}" cy="${n.y}" r="${nodeRadius(n.degree)}" ` +
  `fill="${colorForCommunity(n.communityId)}" stroke="#fff" stroke-width="1.5">` +
  `<title>${n.id}</title></circle>`

document.getElementById('graph').innerHTML = `
  <svg viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg" width="100%">
    <g transform="translate(${view.offsetX} ${view.offsetY}) scale(${view.scale})">
      ${edges.map(line).join('')}
      ${nodes.map(circle).join('')}
    </g>
  </svg>`

The same helpers back @fortemi/react's GraphView, so a React app and a static site stay visually aligned.

Determinism

layoutCommunityGraph, colorForCommunity, filterCommunityGraph, and serializeGraphSnapshot are deterministic functions of their inputs. That makes them safe for build-time snapshot generation, content-addressable caching, and stable visual diffs. serializeGraphSnapshot sorts nodes, edges, and community members so equal graphs serialize byte-for-byte identically; pass generatedAt only when you want a timestamp in the output.

Community detection lives in @fortemi/core (the base layer); this package only projects graphs it is given.

Documentation

| Guide | Description | |---|---| | API Reference | Full @fortemi/graph, @fortemi/core, and @fortemi/react surface |

React Bindings

Using React? @fortemi/react ships a GraphView component built on these helpers, plus hooks for building and caching graphs from a live archive.

License

AGPL-3.0-only.