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

@x025me/link-analysis

v1.0.5

Published

A powerful React component for visualizing link analysis graphs with Neo4j-style styling

Downloads

16

Readme

@x025me/link-analysis

A beautiful, interactive React component for visualizing network graphs and relationships. Perfect for displaying connections between people, organizations, phone numbers, or any linked data.

🚀 Quick Start

Step 1: Install

npm install @x025me/link-analysis react react-dom

Step 2: Add Styles

In your main entry file (usually main.tsx or index.tsx):

import 'antd/dist/reset.css'

Step 3: Use the Component

import { GraphVisualization } from '@x025me/link-analysis'

function App() {
  const graphData = {
    nodes: [
      { id: '1', label: 'Alice', type: 'person' },
      { id: '2', label: 'Bob', type: 'person' },
      { id: '3', label: 'Company Inc', type: 'organization' },
    ],
    edges: [
      { id: 'e1', source: '1', target: '2', label: 'knows' },
      { id: 'e2', source: '1', target: '3', label: 'works at' },
    ],
  }

  return (
    <div style={{ width: '100%', height: '600px' }}>
      <GraphVisualization data={graphData} />
    </div>
  )
}

That's it! You now have an interactive graph visualization.

📖 Common Use Cases

1. Displaying Social Networks

Show relationships between people:

import { GraphVisualization } from '@x025me/link-analysis'

const socialNetwork = {
  nodes: [
    { id: 'alice', label: 'Alice', type: 'person' },
    { id: 'bob', label: 'Bob', type: 'person' },
    { id: 'charlie', label: 'Charlie', type: 'person' },
  ],
  edges: [
    { id: 'e1', source: 'alice', target: 'bob', label: 'friends' },
    { id: 'e2', source: 'bob', target: 'charlie', label: 'friends' },
  ],
}

function SocialNetwork() {
  return (
    <div style={{ width: '100%', height: '500px' }}>
      <GraphVisualization data={socialNetwork} />
    </div>
  )
}

2. Phone Call Analysis

Visualize phone call records with phone numbers as nodes:

const callData = {
  nodes: [
    { 
      id: 'phone-1234567890', 
      label: '+1 (234) 567-8900', 
      type: 'phone',
      metadata: { primaryPhone: '1234567890' }
    },
    { 
      id: 'phone-0987654321', 
      label: '+1 (987) 654-3210', 
      type: 'phone',
      metadata: { primaryPhone: '0987654321' }
    },
  ],
  edges: [
    { 
      id: 'call1', 
      source: 'phone-1234567890', 
      target: 'phone-0987654321', 
      label: 'Called (5 times)',
      source_detail: '1234567890',
      target_detail: '0987654321'
    },
  ],
}

3. Organizational Charts

Show company hierarchy:

const orgChart = {
  nodes: [
    { id: 'ceo', label: 'CEO', type: 'person' },
    { id: 'cto', label: 'CTO', type: 'person' },
    { id: 'cfo', label: 'CFO', type: 'person' },
    { id: 'eng', label: 'Engineering', type: 'organization' },
    { id: 'finance', label: 'Finance', type: 'organization' },
  ],
  edges: [
    { id: 'e1', source: 'ceo', target: 'cto', label: 'manages' },
    { id: 'e2', source: 'ceo', target: 'cfo', label: 'manages' },
    { id: 'e3', source: 'cto', target: 'eng', label: 'leads' },
    { id: 'e4', source: 'cfo', target: 'finance', label: 'leads' },
  ],
}

function OrgChart() {
  return (
    <GraphVisualization 
      data={orgChart}
      layout={{ name: 'breadthfirst', directed: true }}
    />
  )
}

4. Interactive Graph with Click Handlers

Respond to user interactions:

import { useState } from 'react'
import { GraphVisualization, GraphData } from '@x025me/link-analysis'

function InteractiveGraph() {
  const [selectedNode, setSelectedNode] = useState<string | null>(null)
  const [graphData, setGraphData] = useState<GraphData>({
    nodes: [
      { id: '1', label: 'Node 1' },
      { id: '2', label: 'Node 2' },
    ],
    edges: [
      { id: 'e1', source: '1', target: '2' },
    ],
  })

  return (
    <div>
      {selectedNode && (
        <div>Selected: {selectedNode}</div>
      )}
      <div style={{ width: '100%', height: '600px' }}>
        <GraphVisualization
          data={graphData}
          onNodeClick={(nodeId) => {
            setSelectedNode(nodeId)
            console.log('Node clicked:', nodeId)
          }}
          onEdgeClick={(edgeId) => {
            console.log('Edge clicked:', edgeId)
          }}
        />
      </div>
    </div>
  )
}

🎨 Customizing the Layout

Choose from different layout algorithms:

Force-Directed (Default)

Best for showing natural clusters and relationships:

<GraphVisualization
  data={data}
  layout={{
    name: 'cose',
    animate: true,
    padding: 50,
    nodeSpacing: 100,
    edgeLength: 200,
  }}
/>

Circular Layout

Organizes nodes in a circle:

<GraphVisualization
  data={data}
  layout={{
    name: 'circle',
    animate: true,
    padding: 50,
  }}
/>

Grid Layout

Arranges nodes in a grid:

<GraphVisualization
  data={data}
  layout={{
    name: 'grid',
    animate: true,
    padding: 50,
  }}
/>

Hierarchical Layout

Shows top-to-bottom hierarchy:

<GraphVisualization
  data={data}
  layout={{
    name: 'breadthfirst',
    animate: true,
    padding: 50,
    directed: true,
  }}
/>

Neo4j-Style Layout

Physics-heavy layout similar to Neo4j Bloom:

<GraphVisualization
  data={data}
  layout={{
    name: 'cose-bilkent',
    animate: 'end',
    idealEdgeLength: 120,
    nodeRepulsion: 4500,
    gravity: 0.25,
  }}
/>

🔍 Filtering Nodes

Include Specific Nodes

Show only certain nodes:

const [includeFilters, setIncludeFilters] = useState<string[]>(['1234567890', '0987654321'])

<GraphVisualization
  data={data}
  includeFilters={includeFilters}
  onIncludeFiltersChange={setIncludeFilters}
/>

Exclude Specific Nodes

Hide certain nodes:

const [excludeFilters, setExcludeFilters] = useState<string[]>(['unwanted-node-id'])

<GraphVisualization
  data={data}
  excludeFilters={excludeFilters}
  onExcludeFiltersChange={setExcludeFilters}
/>

🎯 Using the Command Center

The component includes a built-in command center with two tabs:

  • Basic Tab: Search, zoom, and navigation controls
  • Advanced Tab: Filters, layout selection, and data management

Users can interact with the graph using:

  • Click: Select a node
  • Ctrl/Cmd + Click: Multi-select nodes
  • Drag: Pan the graph
  • Scroll: Zoom in/out
  • Hover: See node/edge details in tooltips

📊 Data Format

Basic Node Structure

{
  id: 'unique-id',           // Required: Unique identifier
  label: 'Display Name',     // Required: Text shown on node
  type: 'person',            // Optional: Node type (affects color/shape)
  image: '/path/to/image',   // Optional: Node image
  metadata: {                // Optional: Additional data
    category: 'Research',
    description: 'A person',
    tags: ['important'],
    primaryPhone: '1234567890',
  },
  data: {                    // Optional: Raw data
    // Any additional fields
  },
}

Basic Edge Structure

{
  id: 'unique-edge-id',      // Required: Unique identifier
  source: 'node-id-1',       // Required: Source node ID
  target: 'node-id-2',       // Required: Target node ID
  label: 'connects to',      // Optional: Edge label
  weight: 5,                 // Optional: Edge weight
  source_detail: '1234567890', // Optional: Phone number or detail
  target_detail: '0987654321', // Optional: Phone number or detail
}

Complete Example

const completeData = {
  nodes: [
    {
      id: 'person-1',
      label: 'John Doe',
      type: 'person',
      image: '/images/john.jpg',
      metadata: {
        category: 'Employee',
        description: 'Software Engineer',
        tags: ['developer', 'senior'],
        primaryPhone: '+1234567890',
      },
    },
    {
      id: 'org-1',
      label: 'Tech Corp',
      type: 'organization',
      metadata: {
        category: 'Company',
        description: 'Technology company',
      },
    },
  ],
  edges: [
    {
      id: 'edge-1',
      source: 'person-1',
      target: 'org-1',
      label: 'works at',
      weight: 1,
    },
  ],
}

🌓 Theming

Wrap your app with ThemeProvider for dark/light theme support:

import { ThemeProvider, GraphVisualization } from '@x025me/link-analysis'

function App() {
  return (
    <ThemeProvider>
      <GraphVisualization data={data} />
    </ThemeProvider>
  )
}

The component automatically adapts to light and dark themes.

🛠️ Working with Raw Data

If you have data in a different format, use the normalizeToGraphData utility:

import { normalizeToGraphData } from '@x025me/link-analysis'

// Your raw data (can be in various formats)
const rawData = {
  people: [
    { id: 1, name: 'Alice', phone: '123-456-7890' },
    { id: 2, name: 'Bob', phone: '098-765-4321' },
  ],
  connections: [
    { from: 1, to: 2, type: 'knows' },
  ],
}

// Normalize it
const graphData = normalizeToGraphData(rawData)

// Use it
<GraphVisualization data={graphData} />

💡 Tips & Best Practices

1. Container Size

Always set a specific height for the container:

// ✅ Good
<div style={{ width: '100%', height: '600px' }}>
  <GraphVisualization data={data} />
</div>

// ❌ Bad - no height specified
<div>
  <GraphVisualization data={data} />
</div>

2. Large Datasets

For graphs with many nodes (1000+), consider:

  • Using simpler layouts (grid, circle)
  • Filtering data before passing to component
  • Using includeFilters to show only relevant nodes

3. Phone Number Formatting

The component automatically handles phone numbers. You can use:

  • +1234567890
  • 123-456-7890
  • (123) 456-7890
  • 1234567890

All formats are normalized automatically.

4. Node Types

Common node types with built-in styling:

  • person - Blue circle
  • organization - Green rounded rectangle
  • phone - Hexagon shape
  • website - Golden diamond
  • location - Coral octagon

❓ Troubleshooting

Graph not showing?

  1. Check container height: Make sure the parent div has a height set
  2. Check data format: Ensure nodes and edges arrays exist
  3. Check node IDs: Source and target IDs in edges must match node IDs

Styles not working?

  1. Import Ant Design styles: Add import 'antd/dist/reset.css'
  2. Check CSS imports: Ensure styles are imported before the component

Nodes not connecting?

  1. Verify edge source/target: Must exactly match node IDs
  2. Check for typos: IDs are case-sensitive
  3. Use normalizeToGraphData: If your data format is different

Performance issues?

  1. Reduce node count: Filter data before rendering
  2. Use simpler layouts: Grid or circle for large datasets
  3. Disable animations: Set animate: false in layout config

📚 API Reference

GraphVisualization Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | data | GraphData | required | Graph data with nodes and edges | | layout | GraphLayoutOptions | cose | Layout configuration | | style | CSSProperties | - | Custom container styles | | onNodeClick | (id: string) => void | - | Called when node is clicked | | onEdgeClick | (id: string) => void | - | Called when edge is clicked | | includeFilters | string[] | [] | Show only these nodes | | excludeFilters | string[] | [] | Hide these nodes | | onIncludeFiltersChange | (values: string[]) => void | - | Update include filters | | onExcludeFiltersChange | (values: string[]) => void | - | Update exclude filters |

GraphData Type

interface GraphData {
  nodes: Node[]
  edges: Edge[]
}

Node Type

interface Node {
  id: string
  label: string
  type?: string
  image?: string
  metadata?: NodeMetadata
  data?: Record<string, any>
}

Edge Type

interface Edge {
  id: string
  source: string
  target: string
  label?: string
  weight?: number
  source_detail?: string
  target_detail?: string
  data?: Record<string, any>
}

🎓 Examples

Check out these common patterns:

Loading Data from API

import { useState, useEffect } from 'react'
import { GraphVisualization, GraphData, normalizeToGraphData } from '@x025me/link-analysis'

function ApiGraph() {
  const [data, setData] = useState<GraphData>({ nodes: [], edges: [] })
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    fetch('/api/graph-data')
      .then(res => res.json())
      .then(raw => {
        const normalized = normalizeToGraphData(raw)
        setData(normalized)
        setLoading(false)
      })
  }, [])

  if (loading) return <div>Loading...</div>

  return (
    <div style={{ width: '100%', height: '600px' }}>
      <GraphVisualization data={data} />
    </div>
  )
}

Dynamic Updates

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

  const addNode = () => {
    const newNode = {
      id: `node-${Date.now()}`,
      label: `Node ${data.nodes.length + 1}`,
    }
    setData(prev => ({
      ...prev,
      nodes: [...prev.nodes, newNode],
    }))
  }

  return (
    <div>
      <button onClick={addNode}>Add Node</button>
      <div style={{ width: '100%', height: '600px' }}>
        <GraphVisualization data={data} />
      </div>
    </div>
  )
}

📝 License

MIT

🤝 Support

Found a bug or have a question? Open an issue on GitHub.


Happy visualizing! 🎉