@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
npm install @guinetik/graph-jsQuick Start
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.
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.
const results = await analyzer.analyze(network, features, options);Parameters:
network(Array): Array of edge objects withsource,target, and optionalweightfeatures(Array|string): Features to compute (see Available Metrics below)options(Object):onProgress(Function): Progress callback (receives 0-1 value)includeGraphStats(boolean): Include graph-level statisticsgraphStats(Array): Specific graph stats to calculate
Returns: Promise resolving to:
- Array of node objects with computed metrics, or
- Object with
{ nodes: [...], graph: {...} }ifincludeGraphStats: true
Example: Progress Tracking
const results = await analyzer.analyze(network, ['betweenness'], {
onProgress: (progress) => {
console.log(`Progress: ${Math.round(progress * 100)}%`);
}
});Example: Graph-Level Statistics
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:
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:
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.
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); // 2Graph Layouts
Compute node positions for visualization using various layout algorithms.
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:
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:
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
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
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
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:
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:
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