npm package: @guinetik/graph-js
Description: Graph and Network Analysis for NodeJS and the Browser. Supports Worker-First architecture for parallel computation.
Author: guinetik
Latest version: 0.0.5

# @guinetik/graph-js

A modern, high-performance JavaScript library for network graph analysis with **worker-first architecture** for parallel computation.

## Features

- **Web Worker Parallelism**: All analysis runs in background workers for optimal performance
- **Comprehensive Metrics**: Degree, betweenness, clustering, eigenvector, closeness, and more
- **Community Detection**: Louvain algorithm for finding network communities
- **Graph Layouts**: 11 layout algorithms (force-directed, Kamada-Kawai, spectral, hierarchical, etc.)
- **Graph-Level Statistics**: Density, diameter, average clustering, connectivity metrics
- **TypeScript Support**: Full type definitions included
- **Modern API**: Async/await, clean class-based design
- **Browser & Node.js**: Works in both environments

## Installation

```bash
npm install @guinetik/graph-js
```

## Quick Start

```javascript
import NetworkStats from '@guinetik/graph-js';

// Create analyzer instance
const analyzer = new NetworkStats({ verbose: true });

// Define your network as edges
const network = [
  { source: 'Alice', target: 'Bob', weight: 1 },
  { source: 'Bob', target: 'Carol', weight: 2 },
  { source: 'Carol', target: 'Alice', weight: 1 },
  { source: 'David', target: 'Carol', weight: 1 }
];

// Analyze network (runs in worker)
const results = await analyzer.analyze(network, ['degree', 'betweenness', 'eigenvector']);

console.log(results);
// [
//   { id: 'Alice', degree: 2, betweenness: 0.33, eigenvector: 0.55 },
//   { id: 'Bob', degree: 2, betweenness: 0.33, eigenvector: 0.55 },
//   { id: 'Carol', degree: 3, betweenness: 0.67, eigenvector: 0.78 },
//   { id: 'David', degree: 1, betweenness: 0, eigenvector: 0.32 }
// ]
```

## API Reference

### NetworkStats Class

Main class for analyzing network graphs.

```javascript
const analyzer = new NetworkStats(options);
```

**Options:**
- `verbose` (boolean): Enable detailed logging (default: true)
- `maxWorkers` (number): Maximum number of workers (default: auto-detect)
- `taskTimeout` (number): Task timeout in milliseconds (default: 60000)
- `workerScript` (string): Custom worker script path (for bundlers like Vite)

### analyze(network, features, options)

Analyze a network and compute statistical features.

```javascript
const results = await analyzer.analyze(network, features, options);
```

**Parameters:**
- `network` (Array): Array of edge objects with `source`, `target`, and optional `weight`
- `features` (Array|string): Features to compute (see Available Metrics below)
- `options` (Object):
  - `onProgress` (Function): Progress callback (receives 0-1 value)
  - `includeGraphStats` (boolean): Include graph-level statistics
  - `graphStats` (Array): Specific graph stats to calculate

**Returns:** Promise resolving to:
- Array of node objects with computed metrics, or
- Object with `{ nodes: [...], graph: {...} }` if `includeGraphStats: true`

#### Example: Progress Tracking

```javascript
const results = await analyzer.analyze(network, ['betweenness'], {
  onProgress: (progress) => {
    console.log(`Progress: ${Math.round(progress * 100)}%`);
  }
});
```

#### Example: Graph-Level Statistics

```javascript
const results = await analyzer.analyze(network, ['degree'], {
  includeGraphStats: true,
  graphStats: ['density', 'diameter', 'average_clustering']
});

console.log(results.nodes);  // Node-level metrics
console.log(results.graph);  // { density: 0.67, diameter: 3, average_clustering: 0.45 }
```

### Available Metrics

Access via `NetworkStats.FEATURES`:

```javascript
const metrics = NetworkStats.FEATURES.ALL;
// ['degree', 'betweenness', 'clustering', 'eigenvector',
//  'eigenvector-laplacian', 'cliques', 'closeness', 'ego-density']
```

#### Node-Level Metrics

| Metric | Description | Complexity | Best For |
|--------|-------------|------------|----------|
| **degree** | Number of connections | O(V) | Hub identification |
| **betweenness** | Shortest path centrality | O(V³) | Bridge detection |
| **clustering** | Triangle density | O(V·d²) | Community structure |
| **eigenvector** | Influence based on connections | O(V²) | Prestige/influence |
| **eigenvector-laplacian** | Laplacian eigenvector centrality | O(V²) | Spectral analysis |
| **closeness** | Average distance to all nodes | O(V²) | Network accessibility |
| **cliques** | Maximal complete subgraphs | O(3^(V/3)) | Dense communities |
| **ego-density** | Neighborhood density | O(V·d²) | Local cohesion |

#### Graph-Level Statistics

Access via `NetworkStats.GRAPH_STATS`:

```javascript
const graphStats = NetworkStats.GRAPH_STATS.ALL;
// ['density', 'diameter', 'average_clustering',
//  'average_shortest_path', 'connected_components', 'average_degree']
```

| Statistic | Description |
|-----------|-------------|
| **density** | Edge density (0-1) |
| **diameter** | Longest shortest path |
| **average_clustering** | Mean clustering coefficient |
| **average_shortest_path** | Mean distance between nodes |
| **connected_components** | Number of disconnected subgraphs |
| **average_degree** | Mean degree across all nodes |

### Community Detection

Detect communities (clusters) in networks using the Louvain algorithm.

```javascript
import { CommunityDetection, LouvainAlgorithm } from '@guinetik/graph-js';

// From NetworkStats result (includes modularity)
const results = await analyzer.analyze(network, ['modularity']);

// Or use CommunityDetection directly
const detector = new CommunityDetection();
const result = await CommunityDetection.detect(graphData, 'louvain', {
  resolution: 1.0,
  onProgress: (p) => console.log(`${Math.round(p * 100)}%`)
});

console.log(result.communities);     // { 'Alice': 0, 'Bob': 0, 'Carol': 1 }
console.log(result.modularity);      // 0.42
console.log(result.numCommunities);  // 2
```

### Graph Layouts

Compute node positions for visualization using various layout algorithms.

```javascript
import {
  Graph,
  CircularLayout,
  ForceDirectedLayout,
  KamadaKawaiLayout,
  SpectralLayout,
  LAYOUT_REGISTRY
} from '@guinetik/graph-js';

// Create graph
const graph = new Graph();
graph.addNode('A');
graph.addNode('B');
graph.addEdge('A', 'B');

// Compute positions
const layout = new KamadaKawaiLayout(graph);
const positions = await layout.getPositions();

console.log(positions);
// { 'A': { x: 0, y: 0 }, 'B': { x: 100, y: 0 } }
```

#### Available Layouts

| Layout | Category | Complexity | Best For |
|--------|----------|------------|----------|
| **RandomLayout** | Simple | O(n) | Testing, initialization |
| **CircularLayout** | Simple | O(n) | Symmetric graphs, rings |
| **SpiralLayout** | Simple | O(n) | Linear structures |
| **ShellLayout** | Simple | O(n) | Hub networks (requires degree) |
| **ForceDirectedLayout** | Physics | O(iter·n²) | General graphs |
| **KamadaKawaiLayout** | Energy | O(n³ + iter·n²) | Small-medium graphs, trees |
| **SpectralLayout** | Spectral | O(n) | Communities (requires eigenvector-laplacian) |
| **BipartiteLayout** | Hierarchical | O(n) | Two-layer graphs |
| **MultipartiteLayout** | Hierarchical | O(n) | DAGs, hierarchies |
| **BFSLayout** | Hierarchical | O(n + m) | Trees, exploration |

#### Layout Registry

Use `LAYOUT_REGISTRY` to discover available layouts programmatically:

```javascript
import { LAYOUT_REGISTRY } from '@guinetik/graph-js';

// Get all layouts
const allLayouts = LAYOUT_REGISTRY.getAll();

// Get physics-based layouts
const physicsLayouts = LAYOUT_REGISTRY.byCategory('physics');

// Get layouts that don't require pre-computed stats
const simpleLayouts = LAYOUT_REGISTRY.withoutStatRequirements();
```

## Architecture

### Worker-First Design

All computational work happens in Web Workers for optimal performance:

```
Main Thread                Worker Pool
-----------                -----------
NetworkStats  ──(task)──>  Worker 1
                          Worker 2
                          Worker 3
              <─(result)──
```

**Benefits:**
- Non-blocking UI during analysis
- Parallel computation for large graphs
- Automatic task distribution
- Progress tracking

### Bundle Integration

For Vite, Webpack, or Rollup, import the worker URL:

```javascript
import NetworkStats from '@guinetik/graph-js';
import workerUrl from '@guinetik/graph-js/worker-url';

const analyzer = new NetworkStats({
  workerScript: workerUrl
});
```

## Advanced Usage

### Custom Progress Tracking

```javascript
let lastProgress = 0;

const results = await analyzer.analyze(network, ['betweenness', 'eigenvector'], {
  onProgress: (progress) => {
    const percent = Math.round(progress * 100);
    if (percent > lastProgress) {
      console.log(`Analysis: ${percent}% complete`);
      lastProgress = percent;
    }
  }
});
```

### Combining Metrics and Layouts

```javascript
import NetworkStats, { KamadaKawaiLayout, Graph } from '@guinetik/graph-js';

// 1. Analyze network
const analyzer = new NetworkStats();
const nodeMetrics = await analyzer.analyze(network, ['degree', 'betweenness']);

// 2. Build graph for layout
const graph = new Graph();
for (const edge of network) {
  graph.addNode(edge.source);
  graph.addNode(edge.target);
  graph.addEdge(edge.source, edge.target, edge.weight || 1);
}

// 3. Compute layout
const layout = new KamadaKawaiLayout(graph);
const positions = await layout.getPositions();

// 4. Merge metrics and positions
const visualizationData = nodeMetrics.map(node => ({
  ...node,
  x: positions[node.id].x,
  y: positions[node.id].y
}));
```

### Using with D3.js

```javascript
import * as d3 from 'd3';
import NetworkStats from '@guinetik/graph-js';

const analyzer = new NetworkStats();
const results = await analyzer.analyze(network, ['degree', 'eigenvector']);

// Create D3 visualization
const svg = d3.select('svg');
const nodes = results.map(r => ({ id: r.id, ...r }));
const links = network.map(e => ({ source: e.source, target: e.target }));

// Size nodes by eigenvector centrality
const sizeScale = d3.scaleSqrt()
  .domain(d3.extent(results, d => d.eigenvector))
  .range([5, 20]);

svg.selectAll('circle')
  .data(nodes)
  .enter()
  .append('circle')
  .attr('r', d => sizeScale(d.eigenvector))
  .attr('fill', d => d.degree > 3 ? 'red' : 'blue');
```

## TypeScript

Full TypeScript definitions are included:

```typescript
import NetworkStats, { Graph, CommunityDetection } from '@guinetik/graph-js';

interface Edge {
  source: string;
  target: string;
  weight?: number;
}

const network: Edge[] = [
  { source: 'A', target: 'B', weight: 1 }
];

const analyzer = new NetworkStats({ verbose: false });
const results = await analyzer.analyze(network, ['degree']);
// results is typed as Array<{ id: string, [metric: string]: number }>
```

## Performance

Benchmarks on a MacBook Pro (M1, 8 cores):

| Graph Size | Metric | Time (Worker) | Time (Main Thread) |
|------------|--------|---------------|---------------------|
| 100 nodes | Degree | 5ms | 8ms |
| 100 nodes | Eigenvector | 45ms | 120ms |
| 1000 nodes | Betweenness | 850ms | 2400ms |
| 5000 nodes | Clustering | 1200ms | 4100ms |

Workers provide 2-3x speedup for complex metrics on large graphs.

## Cleanup

Always dispose of the analyzer when done to terminate the worker pool:

```javascript
await analyzer.dispose();
```

## License

MIT

## Contributing

Contributions welcome! Please open an issue or pull request.

## Credits

Built with:
- Web Workers for parallel computation
- Graph theory algorithms (betweenness, eigenvector, etc.)
- Louvain community detection
- Fruchterman-Reingold and Kamada-Kawai layouts