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

@nodepls/node-graph-explorer

v0.2.0

Published

An interactive force-directed graph and file explorer for React.

Readme

@nodepls/node-graph-explorer

Interactive force-directed graph and file explorer for React.

Installation

npm install @nodepls/node-graph-explorer

How to use

Import both the component and its stylesheet. The package treats data as controlled state: your application owns the nodes and relationships and passes the latest value back to the explorer.

import { useState } from 'react';
import {
  NodeGraphExplorer,
  type EdgeData,
  type GraphData,
  type NodeData,
} from '@nodepls/node-graph-explorer';
import '@nodepls/node-graph-explorer/styles.css';

const initialData: GraphData = {
  nodes: [
    {
      id: 'home',
      label: 'Home',
      type: 'page',
      path: 'Workspace/Home',
      metadata: { status: 'Published', owner: 'Ada' },
    },
    {
      id: 'notes',
      label: 'Research notes',
      type: 'note',
      path: 'Workspace/Research/Research notes',
    },
  ],
  edges: [
    {
      id: 'home-notes',
      source: 'home',
      target: 'notes',
      type: 'references',
      directional: true,
    },
  ],
};

export function App() {
  const [data, setData] = useState(initialData);

  const addEdge = (edge: EdgeData) => {
    setData((current) => ({
      ...current,
      edges: [...current.edges, edge],
    }));
  };

  const removeEdge = (edgeId: string) => {
    setData((current) => ({
      ...current,
      edges: current.edges.filter((edge) => edge.id !== edgeId),
    }));
  };

  return (
    <NodeGraphExplorer
      data={data}
      height={600}
      nodeTypeColors={{ page: '#5db8a6', note: '#e8a55a' }}
      relationshipConfig={{
        references: {
          color: '#5db8a6',
          directional: true,
        },
        related: {
          color: '#e8a55a',
          style: 'dashed',
        },
      }}
      onEdgeCreate={addEdge}
      onEdgeDelete={removeEdge}
    />
  );
}

The Graph/Explorer and theme controls work internally by default. To control them from your application, pass the corresponding value and change callback:

const [selectedNodeId, setSelectedNodeId] = useState<string | null>(null);
const [viewMode, setViewMode] = useState<'graph' | 'explorer'>('graph');
const [theme, setTheme] = useState<'light' | 'dark' | 'system'>('system');

<NodeGraphExplorer
  data={data}
  selectedNodeId={selectedNodeId}
  onSelectedNodeChange={setSelectedNodeId}
  viewMode={viewMode}
  onViewModeChange={setViewMode}
  theme={theme}
  onThemeChange={setTheme}
/>

Use defaultSelectedNodeId, defaultViewMode, or defaultTheme when you only need an initial value.

FAQ

How do I edit a node's label, type, metadata, color, or path?

Update that node in your application's data.nodes state. The metadata popover is read-only; editing remains under the consumer application's control.

function updateNode(nodeId: string, changes: Partial<NodeData>) {
  setData((current) => ({
    ...current,
    nodes: current.nodes.map((node) =>
      node.id === nodeId ? { ...node, ...changes } : node,
    ),
  }));
}

updateNode('notes', {
  label: 'Reviewed research',
  metadata: { status: 'Reviewed', words: 1200 },
});

Import NodeData from the package when using the typed helper above. Metadata values can be strings, numbers, or booleans.

How do I add or remove nodes?

Add or remove entries in data.nodes. If you remove a node, also remove relationships that reference its ID.

setData((current) => ({
  nodes: current.nodes.filter((node) => node.id !== nodeId),
  edges: current.edges.filter(
    (edge) => edge.source !== nodeId && edge.target !== nodeId,
  ),
}));

Node and edge IDs must be unique.

How does the built-in relationship editor work?

Pass onEdgeCreate to display the Add relationship control. The explorer supplies a generated edge with the selected source, target, and type. Your callback must add it to data.edges; otherwise the relationship will not appear.

The editor allows reverse and self-referential relationships. It rejects missing endpoints and duplicate relationships with the same source, target, and type.

How do I remove a relationship?

Pass onEdgeDelete, select an edge, then use Remove relationship or the Delete key. Your callback receives the edge ID and must remove it from data.edges.

How do I edit an existing relationship?

There is no inline relationship-editing form. Replace or update the matching edge in your application state:

function updateEdge(edgeId: string, changes: Partial<EdgeData>) {
  setData((current) => ({
    ...current,
    edges: current.edges.map((edge) =>
      edge.id === edgeId ? { ...edge, ...changes } : edge,
    ),
  }));
}

updateEdge('home-notes', {
  type: 'related',
  label: 'Background reading',
  directional: false,
});

How do I configure relationship colors, line styles, and arrows?

Use relationshipConfig, keyed by the edge's type. Supported styles are solid and dashed. Direction can be configured for a whole relationship type or overridden on an individual edge with directional.

How do I organize the Explorer hierarchy?

Set a node's path using /-separated folders, such as Workspace/Research/My note. A node's parentId takes precedence when supplied. Nodes without a usable path or parent appear under Unfiled.

How do I assign node colors?

Use nodeTypeColors to color every node of a type. Set color on an individual node to override its type color.

How do I customize the component's appearance?

Pass className or style to the root component and override the namespaced CSS variables from your stylesheet:

.my-graph,
.my-graph[data-theme='dark'] {
  --nge-primary: #9b6cff;
  --nge-canvas: #fffdf8;
  --nge-surface: #f3eee5;
  --nge-accent-teal: #3a9c8a;
}
<NodeGraphExplorer className="my-graph" data={data} height="70vh" />

Does the package persist changes or theme preferences?

No. Persist data, selection, view mode, or theme in your own state, store, API, or browser storage. The package only renders the values it receives and reports user actions through callbacks.

Development

Run npm run dev for the included demo, or npm run check to lint, test, and build the package.