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

synapse-graph

v1.0.0

Published

Interactive canvas-based graph visualization for exploring relationships between entities

Readme

Synapse — Interactive Graph Visualization

npm version Bundle Size TypeScript

Synapse Demo

Canvas-based graph navigation tool for exploring relationships between entities. Activate a node and it animates to center with the graph re-laying out around it.

Features

  • Animated navigation — activate any node to bring it to center with smooth transitions
  • Automatic layout — nodes arrange themselves intelligently in five zones around the active node
  • Relationship curves — Bezier curves connect nodes with configurable styles
  • Hover interactions — nodes and curves highlight on hover
  • Parent-controlled UI — callbacks expose hover, activation, and navigation events without owning your app chrome
  • Search helpers — find nodes by label or a custom matcher over your source objects
  • HiDPI / Retina ready — crisp rendering on high-density displays
  • Zero dependencies — lightweight, tree-shakeable, works in any modern browser
  • TypeScript — full type declarations included

Quick Start

Using the library

npm install synapse-graph
import { Cortex, Renderer, InputHandler, loadGraphData } from 'synapse-graph';
import type { GraphData } from 'synapse-graph';

const canvas = document.getElementById('canvas') as HTMLCanvasElement;
const cortex = new Cortex(window.innerWidth / 2, window.innerHeight / 2, {
  onNavigate: ({ previousNode, currentNode, source }) => {
    console.log(`Moved from ${previousNode.label} to ${currentNode.label}`, source);
  },
});
const renderer = new Renderer(canvas);
new InputHandler(cortex, renderer, canvas, {
  onNodeHover: ({ node }) => console.log('Hovered node:', node.source),
  onCurveClick: ({ edges }) => console.log('Activated relationship:', edges[0]?.source),
});

const data: GraphData = { /* your graph data */ };
loadGraphData(cortex, data);

function loop(time: number) {
  cortex.update(0.016);
  renderer.render(
    cortex.centralNode,
    cortex.getChildNodes(),
    cortex.getAllNodeCurves(),
    window.innerWidth,
    window.innerHeight,
  );
  requestAnimationFrame(loop);
}
requestAnimationFrame(loop);

Running the demo app

git clone https://github.com/juliandicks/synapse.git
cd synapse
npm install
npm run dev

Open http://localhost:5173 in your browser.

The demo keeps keyboard behavior in parent code: / focuses search, Escape clears it, Tab and Shift+Tab focus all visible nodes including the center, Enter navigates to the focused node, and [ / ] move through history. The graph canvas sits to the right of the controls and can be resized to exercise embedded layouts.

Data Format

Nodes and edges are provided as plain objects with any domain-specific data:

interface RawNode {
  id: string;
  label: string;
  // Your domain data here
}

interface RawEdge {
  from: string;        // source node id
  to: string;          // target node id
  type: 'child' | 'peer';  // only two edge types supported
  // Your domain data here
}

The graph configuration is wrapped in a GraphData object:

interface GraphData {
  central: string;           // initial central node id
  nodes: RawNode[];
  edges: RawEdge[];
}

Mapping edge types: the library only accepts 'child' and 'peer' edge types. Normalize your domain-specific data before loading so Synapse stays focused on graph display and interaction:

const graphData: GraphData = {
  central: domainData.central,
  nodes: domainData.nodes,
  edges: domainData.edges.map((edge) => ({
    ...edge,
    domainType: edge.type,
    type: edge.type === 'influence' ? 'child' : 'peer',
  })),
};

loadGraphData(cortex, graphData);

API Reference

Cortex

The core graph engine — manages nodes, edges, layout, and animation.

| Method | Description | |--------|-------------| | constructor(centerX, centerY, config?) | Create a new graph centered at given coordinates with optional layout and navigation config | | addGraphNode(id, label, source?) | Add a node with optional source object reference | | addEdge(sourceId, targetId, type, source?) | Add a directed edge with optional source object reference | | navigateTo(id) | Animate to make the given node the new center | | findNodes(query, options?) | Return nodes matching a label search or parent-provided matcher | | loadGraphData(cortex, data) | Bulk-load nodes and edges from a GraphData object | | update(dt) | Step the animation by dt seconds | | resize(centerX, centerY, layout?) | Update center coordinates and optional viewport-aware layout bounds | | setLayoutConfig(layout) | Update responsive layout options without changing the center | | getAllNodes() | Return all nodes | | getChildNodes() | Return all non-central nodes | | getVisibleNodes() | Return nodes currently visible in the active view | | getConnectedNodes(id?) | Return all nodes connected to the given node, defaulting to the center | | getIncomingNodes(id?) | Return incoming child nodes for the given node | | getOutgoingNodes(id?) | Return outgoing child nodes for the given node | | getPeerNodes(id?) | Return peer nodes connected to the given node | | getAllNodeCurves() | Return bezier curves for all visible connections | | hitTest(x, y) | Return the node id at the given canvas coordinates | | removeNode(id) | Remove a node and its edges |

navigateTo accepts an optional source label so parent code can distinguish UI actions:

cortex.navigateTo('kant', { source: 'search' });

findNodes performs a case-insensitive label search by default. Use match when your app wants to search source data that Synapse does not model:

const matches = cortex.findNodes('stoic', {
  limit: 8,
  match: (node, query) => {
    const source = node.source as { tradition?: string };
    return (
      node.label.toLocaleLowerCase().includes(query) ||
      source.tradition?.toLocaleLowerCase().includes(query) === true
    );
  },
});

Renderer

Canvas drawing engine with configurable styles. Style callbacks receive the source objects you provided when adding nodes/edges:

const renderer = new Renderer(canvas, {
  nodeStyle: (source) => ({
    fillColor: (source as MyNode).color,
    strokeColor: '#533483',
    strokeWidth: 2,
  }),
  edgeStyle: (source) => ({
    color: (source as MyEdge).color,
    strokeWidth: 1,
  }),
  hover: {
    color: '#fff',
    strokeWidth: 3,
  },
});

InputHandler

Handles pointer activation and hover events for mouse, touch, and stylus input.

const input = new InputHandler(cortex, renderer, canvas, {
  navigateOnClick: true,
  onNodeClick: ({ node }) => {
    console.log(node.source);
  },
  onNodeHover: ({ node }) => {
    console.log(node.label);
  },
});

input.destroy();

Customization

Node Styling

Supply your own nodeStyle callback to style nodes based on your domain data. The callback receives the source object you provided when adding the node.

Layout

By default, Synapse uses the same fixed layout distances as earlier versions. For embedded canvases or smaller windows, pass viewport bounds so layout offsets and zone spacing can clamp to available space:

function resize() {
  const width = canvas.clientWidth;
  const height = canvas.clientHeight;
  cortex.resize(width / 2, height / 2, {
    width,
    height,
    padding: 32,
    labelWidth: 96,
    minNodeSpacing: 44,
    minHorizontalOffset: 72,
    minRightZoneDistance: 72,
  });
}

You can still tune layout distances explicitly:

cortex.setLayoutConfig({
  horizontalOffset: 240,
  verticalOffset: 160,
  rightZoneDistance: 220,
  nodeVerticalSpacing: 56,
});

When width and height are provided, Synapse squishes horizontal offsets, right-zone distance, vertical offsets, and vertical node spacing toward the configured minimums while keeping visible nodes inside the padded bounds where possible. Horizontal scaling also reserves each child node's radius, label gap, and labelWidth so typical side labels do not clip at the canvas edge.

Examples

See the examples/ directory for example applications:

Contributing

See CONTRIBUTING.md.

License

MIT