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

@mmdalipour/graphie

v0.1.0

Published

A powerful and lightweight React graph visualization library with interactive force-directed layouts

Readme

Graphie

A powerful and lightweight React graph visualization library with interactive force-directed layouts built with a custom physics simulation engine and Canvas 2D rendering.

npm version License: MIT

Features

  • 🚀 High Performance: Canvas 2D rendering for smooth interactions with large graphs
  • Force-Directed Layout: Physics-based layout using custom simulation engine
  • 🎨 Fully Configurable: Comprehensive configuration for colors, sizes, styling, and behavior - customize nodes, edges, containers, overlays, and camera settings
  • 🖱️ Interactive: Pan, zoom, click, and hover interactions out of the box with visual feedback
  • 📦 Lightweight: Zero dependencies, only requires React
  • 🎯 TypeScript: Full TypeScript support with type definitions
  • 🔍 Neighborhood Exploration: Click nodes to explore their connected subgraphs
  • ⚙️ Custom Physics: Built-in force simulation with link, charge, center, and collision forces
  • 🔀 Multiple Separated Graphs: Automatically detects and displays disconnected graph components in a single canvas

Installation

npm install graphie
# or
yarn add graphie
# or
pnpm add graphie

Quick Start

import { Graphie, generateSampleGraph } from 'graphie';

function App() {
  const graphData = generateSampleGraph();

  return (
    <Graphie
      data={graphData}
      height={600}
      onNodeClick={(node) => {
        console.log('Node clicked:', node);
      }}
    />
  );
}

API Reference

Graphie Component

The main component for rendering graph visualizations.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | data | GraphData | Required | The graph data containing nodes and edges | | width | string \| number | "100%" | Width of the graph container | | height | number | 600 | Height of the graph container in pixels | | onNodeClick | (node: Node) => void | undefined | Callback fired when a node is clicked | | config | GraphConfig | {} | Visual configuration options for styling nodes, edges, and UI elements | | options | GraphOptions | {} | Additional configuration options | | className | string | "" | Additional CSS classes for the container |

Types

GraphData

type GraphData = {
  nodes: Node[];
  edges: Edge[];
  groups?: GraphGroup[];  // Optional: explicitly define separate graph groups
};

Node

type Node = {
  id: string;           // Unique identifier for the node
  label?: string;       // Display label for the node
  meta?: Record<string, any>;  // Additional metadata
  x?: number;          // Initial x position
  y?: number;          // Initial y position
  color?: string;      // Custom node color (hex or CSS color)
};

Edge

type Edge = {
  source: string;      // ID of the source node
  target: string;      // ID of the target node
  weight?: number;     // Optional edge weight
  meta?: Record<string, any>;  // Additional metadata
};

GraphGroup

type GraphGroup = {
  id: string;                          // Unique identifier for the group
  nodeIds: string[];                   // IDs of nodes in this group
  position?: { x: number; y: number }; // Optional fixed position for the group center
  label?: string;                      // Optional label for the group
};

GraphConfig

Complete visual configuration for customizing the graph appearance and behavior.

type GraphConfig = {
  // Node styling
  node?: {
    defaultColor?: string;         // Default node color (default: "#6366f1")
    selectedColor?: string;        // Color when node is selected (default: "#10b981")
    hoveredColor?: string;         // Color when node is hovered (default: "#f59e0b")
    connectedColor?: string;       // Color for nodes connected to hovered node (default: "#fcd34d")
    radius?: number;               // Node radius in pixels (default: 8)
    borderColor?: string;          // Default border color (default: "#e2e8f0")
    hoveredBorderColor?: string;   // Border color when hovered (default: "#fbbf24")
    selectedBorderColor?: string;  // Border color when selected (default: "#34d399")
    connectedBorderColor?: string; // Border color for connected nodes (default: "#fde68a")
    borderWidth?: number;          // Border width in pixels (default: 1.5)
    fontSize?: number;             // Label font size in pixels (default: 12)
    labelColor?: string;           // Label text color (default: "#1e293b")
    render?: (props: NodeRenderProps) => void;  // Custom node render function
  };
  // Edge styling
  edge?: {
    defaultColor?: string;         // Default edge color (default: "#94a3b8")
    highlightedColor?: string;     // Color for edges connected to hovered node (default: "#fb923c")
    defaultWidth?: number;         // Default edge width in pixels (default: 1.5)
    highlightedWidth?: number;     // Width for highlighted edges (default: 2.5)
  };
  // Container styling
  container?: {
    background?: string;           // Background color (default: "#ffffff")
    border?: string;               // Border style (default: "1px solid #e2e8f0")
  };
  // Stats overlay styling
  stats?: {
    show?: boolean;                // Show/hide stats overlay (default: true)
    background?: string;           // Background color (default: "rgba(255, 255, 255, 0.95)")
    textColor?: string;            // Primary text color (default: "#1e293b")
    secondaryTextColor?: string;   // Secondary text color (default: "#64748b")
    position?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right'; // Position (default: "bottom-left")
  };
  // Back button styling
  backButton?: {
    background?: string;           // Background color (default: "rgba(255, 255, 255, 0.95)")
    textColor?: string;            // Text color (default: "#334155")
    border?: string;               // Border style (default: "1px solid #e2e8f0")
    position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; // Position (default: "top-right")
  };
  // Camera and animation
  camera?: {
    initialZoom?: number;          // Initial zoom level (default: 1)
    minZoom?: number;              // Minimum zoom level (default: 0.1)
    maxZoom?: number;              // Maximum zoom level (default: 5)
    fitPadding?: number;           // Padding when fitting graph to screen in pixels (default: 60)
    animationDuration?: number;    // Camera animation duration in milliseconds (default: 300)
  };
};

GraphOptions

type GraphOptions = {
  maxVisibleNodes?: number;
  animation?: {
    durationMs?: number;
    easing?: string;
  };
};

Utility Functions

generateSampleGraph()

Generates a sample hierarchical graph data for testing and demonstration purposes.

import { generateSampleGraph } from 'graphie';

const sampleData = generateSampleGraph();

generateMultipleGraphs()

Generates multiple separated graphs for demonstration of the multi-graph feature.

import { generateMultipleGraphs } from 'graphie';

const multiGraphData = generateMultipleGraphs();

getNeighborhood(nodeId, data, depth)

Gets the neighborhood subgraph of a specific node up to a certain depth.

import { getNeighborhood } from 'graphie';

const neighborhood = getNeighborhood('node-1', graphData, 2);

Parameters:

  • nodeId (string): The ID of the node to start from
  • data (GraphData): The complete graph data
  • depth (number): How many levels deep to traverse (default: 1)

detectGraphComponents(data)

Automatically detects disconnected graph components using a breadth-first search algorithm.

import { detectGraphComponents } from 'graphie';

const components = detectGraphComponents(graphData);
console.log(`Found ${components.length} separated graphs`);

Parameters:

  • data (GraphData): The graph data to analyze

Returns: Array of GraphGroup objects representing disconnected components

calculateBoundingBox(nodes)

Calculates the bounding box for a set of nodes.

import { calculateBoundingBox } from 'graphie';

const bbox = calculateBoundingBox(nodes);
console.log(`Width: ${bbox.width}, Height: ${bbox.height}`);

Parameters:

  • nodes (Node[]): Array of nodes with x and y positions

Returns: Object containing minX, minY, maxX, maxY, width, height, centerX, centerY

Examples

Basic Usage

import { Graphie } from 'graphie';

function BasicExample() {
  const data = {
    nodes: [
      { id: '1', label: 'Node 1' },
      { id: '2', label: 'Node 2' },
      { id: '3', label: 'Node 3' },
    ],
    edges: [
      { source: '1', target: '2' },
      { source: '2', target: '3' },
    ],
  };

  return <Graphie data={data} height={500} />;
}

Custom Node Colors

Individual node colors can be set using the color property on each node:

import { Graphie } from 'graphie';

function ColoredGraph() {
  const data = {
    nodes: [
      { id: '1', label: 'Red', color: '#ff6b6b' },
      { id: '2', label: 'Blue', color: '#4ecdc4' },
      { id: '3', label: 'Green', color: '#45b7d1' },
    ],
    edges: [
      { source: '1', target: '2' },
      { source: '2', target: '3' },
    ],
  };

  return <Graphie data={data} height={500} />;
}

Configuring Graph Appearance

Use the config prop to customize the entire graph's visual appearance:

import { Graphie, GraphConfig } from 'graphie';

function CustomStyledGraph() {
  const data = {
    nodes: [
      { id: '1', label: 'Node 1' },
      { id: '2', label: 'Node 2' },
      { id: '3', label: 'Node 3' },
    ],
    edges: [
      { source: '1', target: '2' },
      { source: '2', target: '3' },
    ],
  };

  const config: GraphConfig = {
    node: {
      defaultColor: "#8b5cf6",      // Purple nodes
      hoveredColor: "#ec4899",       // Pink on hover
      selectedColor: "#10b981",      // Green when selected
      connectedColor: "#f59e0b",     // Orange for connected nodes
      radius: 12,                    // Larger nodes
      fontSize: 14,                  // Larger labels
    },
    edge: {
      defaultColor: "#94a3b8",       // Gray edges
      highlightedColor: "#ec4899",   // Pink highlighted edges
      defaultWidth: 2,               // Thicker edges
    },
    container: {
      background: "#0f172a",         // Dark background
      border: "2px solid #334155",   // Custom border
    },
    stats: {
      show: true,
      position: "top-left",          // Move stats to top-left
      background: "rgba(15, 23, 42, 0.95)",
      textColor: "#f1f5f9",
      secondaryTextColor: "#94a3b8",
    },
    backButton: {
      position: "top-right",
      background: "rgba(15, 23, 42, 0.95)",
      textColor: "#f1f5f9",
      border: "1px solid #334155",
    },
    camera: {
      minZoom: 0.5,
      maxZoom: 3,
      fitPadding: 80,
      animationDuration: 500,        // Slower animations
    },
  };

  return (
    <Graphie
      data={data}
      config={config}
      height={600}
      onNodeClick={(node) => console.log('Clicked:', node)}
    />
  );
}

Dark Theme Example

import { Graphie, GraphConfig } from 'graphie';

function DarkThemeGraph() {
  const darkConfig: GraphConfig = {
    node: {
      defaultColor: "#60a5fa",
      hoveredColor: "#fbbf24",
      selectedColor: "#34d399",
      labelColor: "#f1f5f9",
    },
    edge: {
      defaultColor: "#475569",
      highlightedColor: "#fbbf24",
    },
    container: {
      background: "#0f172a",
      border: "1px solid #1e293b",
    },
    stats: {
      background: "rgba(15, 23, 42, 0.9)",
      textColor: "#f1f5f9",
      secondaryTextColor: "#94a3b8",
    },
    backButton: {
      background: "rgba(15, 23, 42, 0.9)",
      textColor: "#f1f5f9",
      border: "1px solid #334155",
    },
  };

  const data = generateSampleGraph();

  return <Graphie data={data} config={darkConfig} height={600} />;
}

Custom Node Rendering

You can completely customize how nodes are rendered by providing a custom render function. Since Graphie uses Canvas 2D rendering, your custom function receives the canvas context and node data:

import { Graphie, GraphConfig, NodeRenderProps } from 'graphie';

function CustomNodesGraph() {
  const config: GraphConfig = {
    node: {
      radius: 12,
      render: ({ ctx, node, state, config, colors }: NodeRenderProps) => {
        const { isHovered, isSelected, isConnected } = state;
        
        // Choose color based on state
        let fillColor = node.color || colors.defaultColor;
        if (isHovered) fillColor = colors.hoveredColor;
        else if (isSelected) fillColor = colors.selectedColor;
        else if (isConnected) fillColor = colors.connectedColor;
        
        // Draw custom hexagon shape
        ctx.beginPath();
        for (let i = 0; i < 6; i++) {
          const angle = (Math.PI / 3) * i;
          const x = node.x + config.radius * Math.cos(angle);
          const y = node.y + config.radius * Math.sin(angle);
          if (i === 0) ctx.moveTo(x, y);
          else ctx.lineTo(x, y);
        }
        ctx.closePath();
        ctx.fillStyle = fillColor;
        ctx.fill();
        
        // Optional: Add a border for selected nodes
        if (isSelected) {
          ctx.strokeStyle = colors.selectedBorderColor;
          ctx.lineWidth = 2 / config.scale;
          ctx.stroke();
        }
        
        // Draw label
        if (node.label) {
          ctx.font = `${config.fontSize}px Inter, sans-serif`;
          ctx.textAlign = "center";
          ctx.textBaseline = "top";
          ctx.fillStyle = colors.labelColor;
          ctx.fillText(node.label, node.x, node.y + config.radius + 4);
        }
      }
    }
  };

  const data = generateSampleGraph();

  return <Graphie data={data} config={config} height={600} />;
}

NodeRenderProps Interface:

interface NodeRenderProps {
  ctx: CanvasRenderingContext2D;  // Canvas 2D context for drawing
  node: Node & { x: number; y: number };  // Node data with position
  state: {
    isHovered: boolean;    // True if mouse is over this node
    isSelected: boolean;   // True if node is selected (clicked)
    isConnected: boolean;  // True if node is connected to hovered node
  };
  config: {
    radius: number;        // Configured node radius
    fontSize: number;      // Configured font size (already scaled)
    scale: number;         // Current zoom scale factor
  };
  colors: {
    defaultColor: string;
    selectedColor: string;
    hoveredColor: string;
    connectedColor: string;
    borderColor: string;
    hoveredBorderColor: string;
    selectedBorderColor: string;
    connectedBorderColor: string;
    labelColor: string;
  };
}

With custom rendering, you can create:

  • Different shapes (squares, hexagons, stars, etc.)
  • Icons or images rendered on nodes
  • Complex visual effects
  • Data-driven visualizations based on node metadata
  • Animated nodes

Interactive Graph with Callbacks

import { Graphie, Node } from 'graphie';
import { useState } from 'react';

function InteractiveGraph() {
  const [selectedNode, setSelectedNode] = useState<Node | null>(null);

  const data = {
    nodes: [
      { id: '1', label: 'Node 1' },
      { id: '2', label: 'Node 2' },
      { id: '3', label: 'Node 3' },
    ],
    edges: [
      { source: '1', target: '2' },
      { source: '2', target: '3' },
    ],
  };

  return (
    <div>
      <Graphie
        data={data}
        height={500}
        onNodeClick={(node) => setSelectedNode(node)}
      />
      {selectedNode && (
        <div>
          <h3>Selected Node</h3>
          <p>ID: {selectedNode.id}</p>
          <p>Label: {selectedNode.label}</p>
        </div>
      )}
    </div>
  );
}

Dynamic Graph Updates

import { Graphie, GraphData } from 'graphie';
import { useState } from 'react';

function DynamicGraph() {
  const [data, setData] = useState<GraphData>({
    nodes: [{ id: '1', label: 'Node 1' }],
    edges: [],
  });

  const addNode = () => {
    const newId = String(data.nodes.length + 1);
    setData({
      nodes: [...data.nodes, { id: newId, label: `Node ${newId}` }],
      edges: data.edges,
    });
  };

  return (
    <div>
      <button onClick={addNode}>Add Node</button>
      <Graphie data={data} height={500} />
    </div>
  );
}

Multiple Separated Graphs

Graphie automatically detects and displays disconnected graph components in a single canvas. Each component is positioned in its own region and maintains independent physics simulation.

import { Graphie, generateMultipleGraphs, detectGraphComponents } from 'graphie';

function MultiGraphExample() {
  const graphData = generateMultipleGraphs();
  const components = detectGraphComponents(graphData);

  return (
    <div>
      <p>Detected {components.length} separated graphs</p>
      <Graphie data={graphData} height={700} />
    </div>
  );
}

You can also manually define graph groups with custom positions:

import { Graphie, GraphData } from 'graphie';

function CustomGroupedGraphs() {
  const data: GraphData = {
    nodes: [
      // Graph 1
      { id: '1', label: 'A', color: '#ff6b6b' },
      { id: '2', label: 'B', color: '#ff6b6b' },
      // Graph 2
      { id: '3', label: 'C', color: '#4ecdc4' },
      { id: '4', label: 'D', color: '#4ecdc4' },
    ],
    edges: [
      { source: '1', target: '2' },
      { source: '3', target: '4' },
    ],
    groups: [
      {
        id: 'group1',
        nodeIds: ['1', '2'],
        label: 'Team A',
        position: { x: 200, y: 300 } // Optional fixed position
      },
      {
        id: 'group2',
        nodeIds: ['3', '4'],
        label: 'Team B',
        position: { x: 600, y: 300 }
      }
    ]
  };

  return <Graphie data={data} height={600} />;
}

Features in Detail

Pan and Zoom

Users can:

  • Pan: Click and drag the canvas to move around
  • Zoom: Use mouse wheel to zoom in/out
  • The zoom is centered around the cursor position for intuitive navigation

Node Interaction

  • Click: Click on nodes to trigger the onNodeClick callback and explore neighborhoods
  • Hover: Visual feedback on hover with color changes and cursor updates

Force-Directed Layout

The graph uses a custom physics simulation engine with:

  • Link force: Keeps connected nodes at a comfortable distance using spring-like forces
  • Charge force: Applies repulsion between nodes to prevent overlap
  • Center force: Pulls nodes toward the center of the canvas
  • Collision force: Prevents nodes from overlapping with configurable collision radius

Neighborhood Exploration

When a node is clicked, the graph automatically:

  1. Saves the current state to history
  2. Finds all nodes within 2 degrees of separation
  3. Smoothly transitions to show only the neighborhood
  4. Shows a "Back" button to return to the previous view

Multiple Separated Graphs

Graphie intelligently handles disconnected graph components:

Automatic Detection

  • Uses breadth-first search to identify disconnected components
  • No configuration needed - works automatically with any graph data
  • Components are detected when there are no edges connecting different groups of nodes

Smart Layout

  • Multiple graphs are arranged in a grid layout for optimal viewing
  • Grid dimensions automatically calculated based on number of components
  • Each component gets its own space with configurable padding

Independent Physics

  • Each graph component has its own force simulation
  • Nodes within a component interact with each other
  • Components are kept separated by repulsion forces
  • Maintains the hexagonal layout pattern within each component

Manual Control (Optional)

  • Optionally specify graph groups explicitly via the groups property
  • Define custom positions for each component
  • Add labels to identify different graph groups

Browser Support

Graphie works in all modern browsers that support:

  • Canvas 2D
  • ES6+
  • React 18+

Performance

  • Optimized Canvas 2D rendering
  • Efficient custom force simulation with optimized physics calculations
  • Handles graphs with hundreds of nodes smoothly
  • RequestAnimationFrame for smooth 60fps animations
  • No external dependencies for maximum performance

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT © Graphie

Acknowledgments

Built with:

  • React
  • Canvas 2D API
  • Custom physics simulation engine