@graphty/layout
Version:
graph layout algorithms based on networkx
1,158 lines (879 loc) • 30.9 kB
Markdown
# Layout
[](https://github.com/graphty-org/layout/actions/workflows/ci.yml)
[](https://coveralls.io/github/graphty-org/layout?branch=main)
[](https://badge.fury.io/js/%40graphty%2Flayout)
[](https://opensource.org/licenses/MIT)
[](https://graphty-org.github.io/layout/examples/index.html)
**[View Interactive Examples →](https://graphty-org.github.io/layout/examples/index.html)**
Layout is a TypeScript library for positioning nodes in graphs. It's a TypeScript port of the [layout algorithms](https://networkx.org/documentation/stable/reference/drawing.html) from the Python [NetworkX](https://networkx.org/documentation/stable/) library.
## Features
The library offers various graph layout algorithms, including:
- **Random Layout** - Places nodes randomly in a unit square
- **Circular Layout** - Places nodes on a circle
- **Shell Layout** - Places nodes in concentric circles (shells)
- **Spring Layout (Fruchterman-Reingold)** - Force-directed layout with attractions and repulsions
- **Spectral Layout** - Uses eigenvectors of the graph's Laplacian matrix
- **Spiral Layout** - Places nodes along a spiral
- **Bipartite Layout** - Layout for bipartite graphs in two straight lines
- **Multipartite Layout** - Layout for multipartite graphs in levels
- **BFS Layout** - Layout based on breadth-first search algorithm
- **Planar Layout** - Planar layout without edge crossings
- **Kamada-Kawai Layout** - Layout based on path-length cost functions
- **ForceAtlas2 Layout** - Advanced force-directed algorithm
- **ARF Layout** - Layout with attractive and repulsive forces
Additionally, the library includes:
**Graph Generators** for creating common graph types:
- **Complete Graph** - All nodes connected to each other
- **Cycle Graph** - Nodes connected in a circular path
- **Star Graph** - Central hub connected to all other nodes
- **Wheel Graph** - Hub connected to nodes arranged in a rim cycle
- **Grid Graph** - 2D grid with nodes connected to neighbors
- **Random Graph** - Erdős–Rényi random graph model
- **Bipartite Graph** - Graph with two disjoint node sets
- **Scale-Free Graph** - Barabási–Albert preferential attachment model
**Layout Helpers** for intelligent graph analysis and layout optimization:
- **groupNodes** - Universal node grouping by degree, distance, k-core, or community
- **detectBipartite** - Automatic bipartite graph detection
- **findBestRoot** - Optimal root selection for tree layouts
- **autoConfigureForce** - Smart parameter configuration for force layouts
- **layoutQuality** - Layout quality measurement
- **combineLayouts** - Blend multiple layout algorithms
- **interpolateLayouts** - Smooth animation between layouts
## Installation
```bash
npm install @graphty/layout
```
## How to Use
Import the library in your TypeScript/JavaScript project:
```typescript
import {
// Layout algorithms
randomLayout,
circularLayout,
springLayout,
fruchtermanReingoldLayout,
spectralLayout,
spiralLayout,
bipartiteLayout,
multipartiteLayout,
bfsLayout,
planarLayout,
kamadaKawaiLayout,
forceatlas2Layout,
arfLayout,
rescaleLayout,
// Graph generators
completeGraph,
cycleGraph,
starGraph,
wheelGraph,
gridGraph,
randomGraph,
bipartiteGraph,
scaleFreeGraph,
// Layout helpers
groupNodes,
detectBipartite,
findBestRoot,
autoConfigureForce,
layoutQuality,
combineLayouts,
interpolateLayouts
} from '@graphty/layout'
```
## Quick Start
```typescript
// Generate a graph
const graph = scaleFreeGraph(30, 2, 42)
// Auto-configure and layout
const config = autoConfigureForce(graph)
const positions = springLayout(graph, config.k, null, null, config.iterations)
// Or use specialized layouts
const bipartite = detectBipartite(graph)
if (bipartite) {
const positions = bipartiteLayout(graph, bipartite.setA)
}
// Or use shell layout with automatic grouping
const shells = groupNodes(graph, 'degree', 3)
const positions = shellLayout(graph, shells)
```
## Graph Structure
The module accepts graphs in two formats:
### 1. Graph Object with methods (preferred)
```typescript
const graph = {
nodes: () => [0, 1, 2, 3],
edges: () => [
[0, 1],
[1, 2],
[2, 3],
[3, 0]
],
getEdgeData: (source, target, attr) => number // optional for edge weights
}
```
### 2. Simple array of nodes
```typescript
const nodes = [0, 1, 2, 3]
```
## Graph Generation
The library includes utilities to generate common graph types for testing and demonstration:
### Complete Graph
Creates a complete graph with all possible edges between nodes.
```typescript
const graph = completeGraph(5)
// Creates a graph with 5 nodes (0-4) and 10 edges (all pairs connected)
```
### Cycle Graph
Creates a cycle graph where nodes form a closed loop.
```typescript
const graph = cycleGraph(6)
// Creates a graph with 6 nodes (0-5) connected in a cycle: 0-1-2-3-4-5-0
```
### Star Graph
Creates a star graph with one central hub connected to all other nodes.
```typescript
const graph = starGraph(7)
// Creates a graph with 7 nodes where node 0 is connected to all others (1-6)
```
### Wheel Graph
Creates a wheel graph - a hub connected to all nodes of a rim cycle.
```typescript
const graph = wheelGraph(6)
// Creates a graph with 6 nodes: hub (0) connected to rim cycle (1-2-3-4-5-1)
```
### Grid Graph
Creates a 2D grid graph with specified rows and columns.
```typescript
const graph = gridGraph(3, 4)
// Creates a 3x4 grid with nodes named "row,col" (e.g., "0,0", "0,1", etc.)
// Nodes are connected to their horizontal and vertical neighbors
```
### Random Graph
Creates a random graph with specified edge probability.
```typescript
const graph = randomGraph(10, 0.3, 42)
// Creates a graph with 10 nodes (0-9)
// Each possible edge has 30% chance of existing
// Seed 42 ensures reproducible results
```
### Bipartite Graph
Creates a bipartite graph with two sets of nodes.
```typescript
const graph = bipartiteGraph(3, 4, 0.5, 123)
// Creates two sets: A0,A1,A2 and B0,B1,B2,B3
// Each edge between sets has 50% chance of existing
// Returns graph with additional setA and setB properties
```
### Scale-Free Graph
Creates a scale-free graph using the Barabási-Albert preferential attachment model.
```typescript
const graph = scaleFreeGraph(20, 2, 456)
// Creates a graph with 20 nodes
// Each new node connects to 2 existing nodes (preferential attachment)
// Results in a power-law degree distribution with some high-degree hubs
```
### Using Generated Graphs with Layouts
All generated graphs work seamlessly with the layout algorithms:
```typescript
// Generate a complete graph and apply circular layout
const graph = completeGraph(8)
const positions = circularLayout(graph)
// Generate a grid with auto-configured spring layout
const grid = gridGraph(5, 5)
const config = autoConfigureForce(grid)
const gridPositions = springLayout(
grid,
config.k,
null,
null,
config.iterations
)
// Generate a scale-free network with optimized ForceAtlas2
const network = scaleFreeGraph(50, 3, 42)
const networkConfig = autoConfigureForce(network)
const networkPositions = forceatlas2Layout(
network,
null,
networkConfig.iterations,
1.0,
networkConfig.scalingRatio,
networkConfig.gravity
)
// Use bipartite graph with automatic detection
const bipartite = bipartiteGraph(5, 7, 0.4, 123)
const bipartitePositions = bipartiteLayout(bipartite, bipartite.setA)
```
## Layout Helpers
The library includes helper functions to simplify working with complex layouts:
### `groupNodes()` - Universal Node Grouping
Groups nodes for shell, multipartite, or custom layouts based on various metrics:
```typescript
// Group by degree (connectivity) - great for shell layouts
const shells = groupNodes(graph, 'degree', 3)
const positions = shellLayout(graph, shells)
// Group by distance from root - perfect for hierarchical layouts
const layers = groupNodes(graph, 'bfs', 0, { root: 'A' })
const positions = multipartiteLayout(graph, layers)
// Group by k-core (dense subgraphs) - ideal for social networks
const cores = groupNodes(graph, 'k-core')
const positions = shellLayout(graph, cores)
// Group by community detection - useful for modular networks
const communities = groupNodes(graph, 'community', 5)
```
### `detectBipartite()` - Automatic Bipartite Detection
Automatically detects if a graph is bipartite and finds the two sets:
```typescript
const result = detectBipartite(graph)
if (result) {
// Graph is bipartite! Use specialized layout
const positions = bipartiteLayout(graph, result.setA)
} else {
// Not bipartite, use general layout
const positions = springLayout(graph)
}
```
### `findBestRoot()` - Optimal Root Node Selection
Finds the best starting node for tree-like layouts (BFS, hierarchical):
```typescript
const root = findBestRoot(graph)
const positions = bfsLayout(graph, root)
```
### `autoConfigureForce()` - Smart Force Layout Configuration
Automatically configures parameters based on graph properties:
```typescript
const config = autoConfigureForce(graph)
// Use with Fruchterman-Reingold
const positions = springLayout(graph, config.k, null, null, config.iterations)
// Use with ForceAtlas2
const positions = forceatlas2Layout(
graph,
null,
config.iterations,
1.0,
config.scalingRatio,
config.gravity
)
```
### `layoutQuality()` - Layout Quality Metrics
Measure and compare layout quality:
```typescript
const circular = circularLayout(graph)
const spring = springLayout(graph)
const metricsC = layoutQuality(graph, circular)
const metricsS = layoutQuality(graph, spring)
console.log('Circular layout - avg edge length:', metricsC.avgEdgeLength)
console.log('Spring layout - avg edge length:', metricsS.avgEdgeLength)
console.log('Spring layout - min node distance:', metricsS.minNodeDistance)
```
### `combineLayouts()` - Blend Multiple Layouts
Create hybrid layouts by combining different algorithms:
```typescript
const circular = circularLayout(graph)
const spring = springLayout(graph)
// 30% circular structure, 70% force-directed
const hybrid = combineLayouts([circular, spring], [0.3, 0.7])
```
### `interpolateLayouts()` - Smooth Layout Transitions
Create animation frames between different layouts:
```typescript
const startLayout = circularLayout(graph)
const endLayout = springLayout(graph)
// Generate 30 frames for smooth animation
const frames = interpolateLayouts(startLayout, endLayout, 30)
// Use frames[0] through frames[30] for animation
```
### Helper Usage Patterns
#### Smart Shell Layout
```typescript
// Automatically choose best grouping method based on graph density
const n = graph.nodes().length
const m = graph.edges().length
const density = (2 * m) / (n * (n - 1))
const method = density < 0.1 ? 'bfs' : density > 0.5 ? 'k-core' : 'degree'
const shells = groupNodes(graph, method)
const positions = shellLayout(graph, shells)
```
#### Adaptive Layout Selection
```typescript
// Choose layout based on graph properties
let positions
if (detectBipartite(graph)) {
const { setA } = detectBipartite(graph)
positions = bipartiteLayout(graph, setA)
} else if (graph.nodes().length > 100) {
// Large graph - use fast layout
positions = circularLayout(graph)
} else {
// Default to auto-configured force layout
const config = autoConfigureForce(graph)
positions = springLayout(graph, config.k, null, null, config.iterations)
}
```
#### Progressive Layout Refinement
```typescript
// Start with fast layout, progressively refine
const initial = circularLayout(graph)
const refined = springLayout(graph, null, initial, null, 50)
const final = kamadaKawaiLayout(graph, null, refined)
```
### Layout Helper Quick Reference
| Helper Function | Purpose | Best Use Case |
| -------------------------------- | ------------------------------- | ------------------------------------- |
| `groupNodes(graph, 'degree')` | Group by connectivity | Shell layouts for scale-free networks |
| `groupNodes(graph, 'bfs')` | Group by distance from root | Hierarchical/tree layouts |
| `groupNodes(graph, 'k-core')` | Group by subgraph density | Social network analysis |
| `groupNodes(graph, 'community')` | Group by detected communities | Modular network visualization |
| `detectBipartite(graph)` | Check if graph is bipartite | Matching problems, assignments |
| `findBestRoot(graph)` | Find optimal tree root | BFS layout, hierarchical layout |
| `autoConfigureForce(graph)` | Auto-configure force parameters | Any force-directed layout |
| `layoutQuality(graph, pos)` | Measure layout quality | Comparing different layouts |
| `combineLayouts([...], [...])` | Blend multiple layouts | Custom hybrid visualizations |
| `interpolateLayouts(from, to)` | Create animation frames | Interactive transitions |
## Usage Examples
### Circular Layout
```typescript
// Use our graph generator instead of manual construction
const graph = cycleGraph(8)
const positions = circularLayout(graph)
// Nodes arranged in a perfect circle
```
### Spring Layout (Fruchterman-Reingold)
```typescript
// Generate a grid and apply force-directed layout with auto-configured parameters
const graph = gridGraph(5, 5)
const config = autoConfigureForce(graph)
const positions = springLayout(
graph,
config.k, // optimal distance
null, // initial positions
null, // fixed nodes
config.iterations // iterations
)
// Or use fruchtermanReingoldLayout (same function)
const positions2 = fruchtermanReingoldLayout(graph, config.k)
```
### Bipartite graph layout
```typescript
// Generate a bipartite graph and detect sets automatically
const graph = bipartiteGraph(4, 6, 0.5, 42)
// Option 1: Use the built-in sets
const positions = bipartiteLayout(graph, graph.setA, 'vertical')
// Option 2: Auto-detect bipartite structure
const detected = detectBipartite(graph)
if (detected) {
const positions2 = bipartiteLayout(graph, detected.setA, 'horizontal')
}
```
## 3D Support
All layout algorithms support 3D positioning by setting the `dim` parameter to 3:
```typescript
// Any layout algorithm in 3D
const positions3D = springLayout(graph, null, null, null, 50, 1, [0, 0, 0], 3)
// Returns: { node1: [x, y, z], node2: [x, y, z], ... }
// ForceAtlas2 in 3D
const fa3D = forceatlas2Layout(graph, null, 100, 1, 2, 1, false, false, null, null, null, false, false, 42, 3)
// Circular layout in 3D (creates a sphere)
const circular3D = circularLayout(graph, 1, [0, 0, 0], 3)
```
When using 3D layouts, positions will have three coordinates `[x, y, z]` instead of two.
## Common Parameters
Most layout functions share these parameters:
- **scale** (number): Scale factor for positions (default: 1)
- **center** (number[]): Center coordinates around which to center the layout (default: [0, 0] for 2D, [0, 0, 0] for 3D)
- **dim** (number): Layout dimension - 2D or 3D (default: 2)
- **seed** (number): Seed for random generation (for reproducible layouts)
## TypeScript Types
```typescript
type Node = string | number
type Edge = [Node, Node]
type PositionMap = Record<Node, number[]>
interface Graph {
nodes?: () => Node[]
edges?: () => Edge[]
getEdgeData?: (source: Node, target: Node, attr: string) => any
}
```
## Utilities
### Layout Rescaling
```typescript
import { rescaleLayout } from './layout.js'
// Rescale existing positions
const scaledPositions = rescaleLayout(positions, 2.0, [10, 10])
```
## Available Algorithms
### Force-Directed Layouts
- `springLayout()` / `fruchtermanReingoldLayout()` - Classic force-directed algorithm
- `forceatlas2Layout()` - Advanced algorithm with many configuration options
- `arfLayout()` - Attractive and repulsive forces
- `kamadaKawaiLayout()` - Based on shortest-path distances
### Geometric Layouts
- `randomLayout()` - Random placement
- `circularLayout()` - Circular arrangement
- `shellLayout()` - Concentric circles
- `spiralLayout()` - Spiral arrangement
### Specialized Layouts
- `spectralLayout()` - Based on eigenvectors of the Laplacian matrix
- `bipartiteLayout()` - For bipartite graphs
- `multipartiteLayout()` - For multi-level graphs
- `bfsLayout()` - Based on breadth-first search
- `planarLayout()` - For planar graphs without crossings
### Utilities
- `rescaleLayout()` - Rescale and recenter positions
- `rescaleLayoutDict()` - Rescale a dictionary of positions
## Detailed Examples for each Algorithm
### Random Layout
```typescript
// Generate any graph and apply random layout
const graph = completeGraph(10)
const positions = randomLayout(graph, [0, 0], 2, 42)
// Nodes randomly placed in unit square with seed 42
```
### Circular Layout
```typescript
// Perfect for cyclic or complete graphs
const graph = cycleGraph(12)
const positions = circularLayout(graph)
// 12 nodes evenly spaced on a circle
```
### Shell Layout
```typescript
// Use automatic node grouping for shell layout
const graph = scaleFreeGraph(30, 2, 42)
// Group nodes by degree (hubs in center)
const shells = groupNodes(graph, 'degree', 3)
const positions = shellLayout(graph, shells)
// Or group by k-core for social networks
const kCoreShells = groupNodes(graph, 'k-core')
const positions2 = shellLayout(graph, kCoreShells)
```
### Spring Layout (Fruchterman-Reingold)
```typescript
// Auto-configure parameters based on graph size
const graph = randomGraph(20, 0.2, 42)
const config = autoConfigureForce(graph)
const positions = springLayout(
graph,
config.k, // optimal distance
null, // initial positions
null, // fixed nodes
config.iterations // iterations
)
```
### Spectral Layout
```typescript
// Great for revealing graph structure
const graph = gridGraph(6, 6)
const positions = spectralLayout(graph)
// Grid structure preserved in spectral embedding
```
### Spiral Layout
```typescript
// Perfect for sequential or time-based data
const graph = cycleGraph(50)
const positions = spiralLayout(
graph,
1, // scale
[0, 0], // center
2, // dim
0.35, // resolution
true // equidistant points
)
```
### Bipartite Layout
```typescript
// Generate bipartite graph and layout automatically
const graph = bipartiteGraph(5, 7, 0.4, 42)
const positions = bipartiteLayout(
graph,
graph.setA, // first group nodes (auto-generated)
'vertical', // align: 'vertical' or 'horizontal'
1, // scale
[0, 0], // center
4 / 3 // aspectRatio
)
```
### Multipartite Layout
```typescript
// Use automatic layer detection with groupNodes
const graph = scaleFreeGraph(20, 2, 42)
const layers = groupNodes(graph, 'bfs', 0, { root: findBestRoot(graph) })
// Convert to multipartite format
const layerMap = {}
layers.forEach((nodes, i) => {
layerMap[i] = nodes
})
const positions = multipartiteLayout(
graph,
layerMap, // subsetKey: layer mapping
'vertical', // align
1, // scale
[0, 0] // center
)
```
### BFS Layout
```typescript
// Use automatic root detection for tree-like graphs
const graph = starGraph(10)
const root = findBestRoot(graph) // Automatically finds node 0 (hub)
const positions = bfsLayout(
graph,
root, // start: best root node
'vertical', // align
1, // scale
[0, 0] // center
)
```
### Planar Layout
```typescript
// Create a planar graph (grid is always planar)
const graph = gridGraph(4, 4)
const positions = planarLayout(graph, 1, [0, 0], 2)
// Note: throws error if graph is not planar
// For unknown graphs, check planarity first
if (isPlanar(graph)) {
const positions = planarLayout(graph)
} else {
// Fall back to non-planar layout
const positions = springLayout(graph)
}
```
### Kamada-Kawai Layout
```typescript
// Great for small to medium graphs
const graph = wheelGraph(8)
const positions = kamadaKawaiLayout(
graph,
null, // dist: distance matrix (auto)
null, // pos: initial positions (auto)
'weight', // weight: edge weight attribute
1, // scale
[0, 0], // center
2 // dim
)
```
### ForceAtlas2 Layout
```typescript
// Auto-configure for your graph type
const graph = scaleFreeGraph(50, 3, 42)
const config = autoConfigureForce(graph)
const positions = forceatlas2Layout(
graph,
null, // pos: initial positions
config.iterations, // maxIter: auto-configured
1.0, // jitterTolerance
config.scalingRatio, // scalingRatio: auto-configured
config.gravity, // gravity: auto-configured
false, // distributedAction
false, // strongGravity
null, // nodeMass: node masses
null, // nodeSize: node sizes
null, // weight: weight attribute
true, // dissuadeHubs: good for scale-free
false, // linlog: logarithmic attraction
42, // seed
2 // dim: 2 for 2D, 3 for 3D
)
// 3D layout example
const positions3D = forceatlas2Layout(
graph,
null,
100,
1.0,
2.0,
1.0,
false,
false,
null,
null,
null,
false,
false,
42,
3 // 3D mode - returns [x, y, z] coordinates
)
```
### ARF Layout
```typescript
// Layout with attractive and repulsive forces
const graph = completeGraph(10)
const positions = arfLayout(
graph,
null, // pos: initial positions
1, // scaling
1.1, // a: spring force (must be > 1)
1000, // maxIter
42 // seed
)
```
## Advanced Examples: Combining Generators and Helpers
### Example 1: Community-Based Visualization
```typescript
// Generate a scale-free network (hubs and communities)
const graph = scaleFreeGraph(100, 3, 42)
// Detect communities and use them for shell layout
const communities = groupNodes(graph, 'community', 4)
const positions = shellLayout(graph, communities)
// Or use communities for coloring in your visualization
const communityMap = new Map()
communities.forEach((nodes, idx) => {
nodes.forEach(node => communityMap.set(node, idx))
})
```
### Example 2: Adaptive Layout Selection
```typescript
function chooseOptimalLayout(graph) {
const n = graph.nodes().length
const m = graph.edges().length
const density = (2 * m) / (n * (n - 1))
// Check for special graph types
const bipartite = detectBipartite(graph)
if (bipartite) {
return bipartiteLayout(graph, bipartite.setA)
}
// Choose based on graph properties
if (n > 100) {
// Large graph - use fast circular layout
return circularLayout(graph)
} else if (density < 0.1) {
// Sparse graph - use spring layout
const config = autoConfigureForce(graph)
return springLayout(graph, config.k, null, null, config.iterations)
} else {
// Dense graph - use spectral or kamada-kawai
return n < 50 ? kamadaKawaiLayout(graph) : spectralLayout(graph)
}
}
// Usage
const graph = randomGraph(30, 0.3, 42)
const positions = chooseOptimalLayout(graph)
```
### Example 3: Animated Layout Transitions
```typescript
// Start with circular layout
const graph = completeGraph(15)
const startLayout = circularLayout(graph)
// Optimize with force-directed
const config = autoConfigureForce(graph)
const endLayout = springLayout(graph, config.k, null, null, config.iterations)
// Create smooth animation frames
const frames = interpolateLayouts(startLayout, endLayout, 60)
// Use frames[0] through frames[60] for animation
function animate(frameIndex) {
const positions = frames[frameIndex]
// Update your visualization with these positions
}
```
### Example 4: Hierarchical Network Analysis
```typescript
// Generate a preferential attachment network
const graph = scaleFreeGraph(50, 2, 42)
// Find natural hierarchy using k-core decomposition
const kCores = groupNodes(graph, 'k-core')
// Layout with most connected nodes in center
const positions = shellLayout(graph, kCores)
// Or create a tree-like view
const root = findBestRoot(graph)
const bfsPositions = bfsLayout(graph, root)
```
### Example 5: Quality-Driven Layout
```typescript
// Try multiple layouts and pick the best
function findBestLayout(graph) {
const candidates = [
{ name: 'circular', positions: circularLayout(graph) },
{ name: 'spectral', positions: spectralLayout(graph) },
{ name: 'spring', positions: springLayout(graph) }
]
let best = candidates[0]
let bestScore = Infinity
candidates.forEach(candidate => {
const quality = layoutQuality(graph, candidate.positions)
const score = quality.edgeLengthStdDev / quality.avgEdgeLength
if (score < bestScore) {
bestScore = score
best = candidate
}
})
console.log(`Best layout: ${best.name} (score: ${bestScore.toFixed(3)})`)
return best.positions
}
```
### Example 6: Hybrid Layouts
```typescript
// Create a graph with clear structure
const graph = gridGraph(6, 6)
// Get geometric and force-based layouts
const grid = circularLayout(graph)
const force = springLayout(graph)
// Blend them: 40% geometric structure, 60% force optimization
const hybrid = combineLayouts([grid, force], [0.4, 0.6])
// The result preserves some grid structure while optimizing edge lengths
```
## Error Handling
```typescript
try {
const positions = planarLayout(nonPlanarGraph)
} catch (error) {
console.error('Graph is not planar:', error.message)
}
try {
const positions = arfLayout(graph, null, 1, 0.5) // a <= 1
} catch (error) {
console.error('Invalid parameter a:', error.message)
}
```
## Installation
```bash
npm install @graphty/layout
```
## Building from Source
If you want to build the TypeScript module from source:
1. **Clone the repository:**
```bash
git clone https://github.com/graphty-org/layout.git
cd layout
```
2. **Install dependencies:**
```bash
npm install
```
3. **Compile TypeScript to JavaScript:**
```bash
npm run build
```
This will compile the `layout.ts` file to JavaScript and generate type declarations in the `dist/` directory.
4. **For development with automatic compilation:**
```bash
npm run dev
```
This will watch for changes and automatically recompile the TypeScript files.
## Development Server
The project includes a Vite development server for testing and viewing examples.
### Starting the Development Server
```bash
npm run serve
```
This will:
- Start a development server on port 3000
- Automatically open your browser to `/examples/`
- Provide hot module reloading for development
### Configuring the Development Server
You can customize the server configuration using environment variables:
1. **Create a `.env` file** (copy from `.env.example`):
```bash
cp .env.example .env
```
2. **Configure server options in `.env`:**
```bash
# Server host (defaults to true for network exposure)
HOST=localhost # For local-only access
HOST=0.0.0.0 # For network access
HOST=my.server.com # For custom domain
# Server port (defaults to 3000)
PORT=3000
PORT=8080 # Custom port
```
3. **Start the server with your configuration:**
```bash
npm run serve
```
### Alternative: Build and Serve
To build the project and then serve the examples:
```bash
npm run examples
```
This command:
1. Builds the TypeScript files to JavaScript
2. Starts the Vite development server
**Note:** The compiled JavaScript files will be available in the `dist/` directory. You can import from the compiled JavaScript files or directly use the TypeScript source files in a TypeScript project.
## Implementation
The module includes complete implementations of:
- **Random Number Generator** with seed support for reproducible results
- **Mathematical utilities** similar to NumPy for multidimensional array operations
- **Force-directed algorithms** with L-BFGS optimization for Kamada-Kawai
- **Planarity algorithms** including Left-Right test for planar graphs
- **Auto-scaling system** to automatically normalize positions
## Performance Tips
1. **Large Graphs (>1000 nodes)**:
```typescript
// Use fast layouts first
const initial = circularLayout(graph)
// Then refine with limited iterations
const refined = springLayout(graph, null, initial, null, 50)
```
2. **Dense Graphs**:
```typescript
// Use spectral layout for dense graphs
const density = (2 * m) / (n * (n - 1))
if (density > 0.5) {
const positions = spectralLayout(graph)
}
```
3. **Real-time Updates**:
```typescript
// Pre-calculate layout quality
const quality = layoutQuality(graph, positions)
// Use interpolation for smooth updates
const frames = interpolateLayouts(oldPositions, newPositions, 30)
```
4. **Memory Optimization**:
```typescript
// For very large graphs, use generators
function* layoutInChunks(graph, chunkSize = 100) {
const nodes = graph.nodes()
for (let i = 0; i < nodes.length; i += chunkSize) {
const chunk = nodes.slice(i, i + chunkSize)
// Process chunk...
yield chunk
}
}
```
## Development
### Building the Project
The project uses a unified build system:
```bash
# Build TypeScript to JavaScript
npm run build
# Build the bundled ES module (dist/layout.js)
npm run build:bundle
# Build everything
npm run build:all
```
### Running Examples Locally
```bash
# Start development server with examples
npm run examples
# or
npm run serve
```
This will:
1. Build the bundled `dist/layout.js`
2. Start a Vite dev server at http://localhost:3000
3. Automatically redirect imports to use the bundled version
### Building for GitHub Pages
To build a static site for GitHub Pages deployment:
```bash
# Build static site in gh-pages/ directory
npm run build:gh-pages
```
This creates a `gh-pages/` directory with:
- All example HTML files
- The bundled `layout.js`
- All necessary assets
To deploy to GitHub Pages, see `gh-pages/DEPLOY.md` after building.
### Development Workflow
1. **Make changes** to TypeScript source files in `src/`
2. **Test locally** with `npm run examples`
3. **Run tests** with `npm test`
4. **Build for production** with `npm run build:all`
5. **Deploy examples** with `npm run build:gh-pages`
## Recent Updates
### Version 1.2.7 (Latest)
- **Fixed**: ForceAtlas2 now correctly returns numeric Z coordinates in 3D mode (previously returned NaN)
- **Fixed**: NPM package now uses the correct bundled entry point (`dist/layout.js`)
- **Added**: Full 3D support documentation and examples
## Contributing
This project is a TypeScript port of the NetworkX Python library. For contributions and issues, visit the [GitHub repository](https://github.com/graphty-org/layout).
## License
MIT License - see LICENSE file for details.