Forceatlas2
Fastest Gephi's ForceAtlas2 graph layout algorithm implemented for Python and NetworkX
Install / Use
/learn @bhargavchippada/Forceatlas2README
ForceAtlas2 for Python
The fastest Python implementation of the ForceAtlas2 graph layout algorithm, with Cython optimization for 10-100x speedup. Supports NetworkX, igraph, and raw adjacency matrices.
ForceAtlas2 is a force-directed layout algorithm designed for network visualization. It spatializes weighted undirected graphs in 2D, 3D, or higher dimensions, where edge weights define connection strength. It scales well to large graphs (>10,000 nodes) using Barnes-Hut approximation (O(n log n) complexity).
Documentation · PyPI · Paper
<p align="center"> <img src="https://raw.githubusercontent.com/bhargavchippada/forceatlas2/master/examples/forceatlas2_animation.gif" alt="ForceAtlas2 layout animation — 500 nodes with 7 communities separating over 600 iterations"> </p> <p align="center"><em>500-node stochastic block model (7 communities) laid out with ForceAtlas2 LinLog mode</em></p> <p align="center"> <img width="460" height="300" src="https://raw.githubusercontent.com/bhargavchippada/forceatlas2/master/examples/geometric_graph.png" alt="Random geometric graph laid out with ForceAtlas2"> </p> <p align="center"><em>Random geometric graph (400 nodes) laid out with ForceAtlas2</em></p> <p align="center"> <img src="https://raw.githubusercontent.com/bhargavchippada/forceatlas2/master/examples/forceatlas2_3d_animation.gif" alt="ForceAtlas2 3D layout animation — 1000 nodes with 8 communities separating over 600 iterations"> </p> <p align="center"><em>1000-node stochastic block model (8 communities) laid out in 3D with ForceAtlas2 LinLog mode</em></p>Installation
pip install fa2
For maximum performance, install with Cython (recommended):
pip install cython
pip install fa2 --no-binary fa2
To build from source:
git clone https://github.com/bhargavchippada/forceatlas2.git
cd forceatlas2
pip install cython numpy
pip install -e ".[dev]" --no-build-isolation
Dependencies
| Package | Required | Purpose |
|---------|----------|---------|
| numpy | Yes | Adjacency matrix handling |
| scipy | Yes | Sparse matrix support |
| tqdm | Yes | Progress bar |
| cython | No (recommended) | 10-100x speedup |
| networkx | No | NetworkX graph wrapper |
| igraph | No | igraph graph wrapper |
| matplotlib | No | Visualization (pip install fa2[viz]) |
Python: >= 3.9 (tested on 3.9 through 3.14)
Quick Start
Simplest — No Numpy Required
from fa2.easy import layout, visualize
# Edge list in → positions out
positions = layout([("A", "B"), ("B", "C"), ("A", "C")], mode="community")
# One call to render
visualize([("A", "B"), ("B", "C"), ("A", "C")], output="png", path="graph.png")
CLI
# Layout from JSON edge list
python -m fa2 layout edges.json --mode community -o layout.json
# Render to image
python -m fa2 render edges.csv -o graph.png
# Compute quality metrics
echo '[["A","B"],["B","C"]]' | python -m fa2 metrics
With NetworkX
import networkx as nx
import matplotlib.pyplot as plt
from fa2 import ForceAtlas2
G = nx.random_geometric_graph(400, 0.2)
forceatlas2 = ForceAtlas2(
outboundAttractionDistribution=True, # Dissuade hubs
edgeWeightInfluence=1.0,
jitterTolerance=1.0,
barnesHutOptimize=True,
barnesHutTheta=1.2,
scalingRatio=2.0,
strongGravityMode=False,
gravity=1.0,
verbose=True,
)
positions = forceatlas2.forceatlas2_networkx_layout(G, pos=None, iterations=2000)
nx.draw_networkx_nodes(G, positions, node_size=20, node_color="blue", alpha=0.4)
nx.draw_networkx_edges(G, positions, edge_color="green", alpha=0.05)
plt.axis("off")
plt.show()
With Raw Adjacency Matrix
import numpy as np
from fa2 import ForceAtlas2
# Create a symmetric adjacency matrix
G = np.array([
[0, 1, 1, 0],
[1, 0, 1, 1],
[1, 1, 0, 0],
[0, 1, 0, 0],
], dtype=float)
forceatlas2 = ForceAtlas2(verbose=False, seed=42)
positions = forceatlas2.forceatlas2(G, iterations=1000)
# Returns: [(x1, y1), (x2, y2), ...]
With Scipy Sparse Matrix
import scipy.sparse
from fa2 import ForceAtlas2
# For large graphs, sparse matrices are more memory-efficient
G_sparse = scipy.sparse.csr_matrix(adjacency_matrix)
forceatlas2 = ForceAtlas2(verbose=False)
positions = forceatlas2.forceatlas2(G_sparse, iterations=1000)
With igraph
import igraph
from fa2 import ForceAtlas2
G = igraph.Graph.Famous("Petersen")
forceatlas2 = ForceAtlas2(verbose=False)
layout = forceatlas2.forceatlas2_igraph_layout(G, iterations=1000)
igraph.plot(G, layout=layout)
API Reference
ForceAtlas2(**kwargs)
Create a ForceAtlas2 layout engine with the following parameters:
Behavior
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| outboundAttractionDistribution | bool | False | Dissuade hubs — distributes attraction along outbound edges so hubs are pushed to borders |
| linLogMode | bool | False | Use Noack's LinLog model: F = log(1 + distance) instead of F = distance. Produces tighter community clusters |
| adjustSizes | bool | False | Prevent node overlap using anti-collision forces (Gephi parity). Pass sizes or size_attr to set node radii |
| edgeWeightInfluence | float | 1.0 | How much edge weights matter. 0 = all edges equal, 1 = normal, other values apply weight^influence |
| normalizeEdgeWeights | bool | False | Min-max normalize edge weights to [0, 1]. Applied after inversion |
| invertedEdgeWeightsMode | bool | False | Invert edge weights (w = 1/w). Applied before normalization |
Performance
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| barnesHutOptimize | bool | True | Use Barnes-Hut tree approximation for repulsion. Reduces O(n^2) to O(n log n) |
| barnesHutTheta | float | 1.2 | Barnes-Hut accuracy/speed tradeoff. Lower = more accurate but slower |
| jitterTolerance | float | 1.0 | How much oscillation is tolerated during convergence. Higher = faster but less precise |
| backend | str | "auto" | "auto": Cython if compiled, else vectorized. "cython" / "loop": force loop-based (Cython or pure Python). "vectorized": NumPy (no BH, O(n²)) |
Tuning
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| scalingRatio | float | 2.0 | Repulsion strength. Higher = more spread out graph. Must be > 0 |
| strongGravityMode | bool | False | Distance-independent gravity: constant pull regardless of distance from center |
| gravity | float | 1.0 | Center attraction strength. Prevents disconnected components from drifting. Must be >= 0 |
Layout & Other
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| dim | int | 2 | Number of layout dimensions. Use 3 for 3D layouts, etc. |
| seed | int/None | None | Random seed for reproducible layouts |
| verbose | bool | True | Show progress bar (tqdm) and timing breakdown |
Class Methods
ForceAtlas2.inferSettings(G, **overrides)
Auto-tune parameters based on graph characteristics. Returns a configured ForceAtlas2 instance.
- G: Any supported graph type (ndarray, sparse, networkx.Graph, igraph.Graph)
- **overrides: Override any inferred parameter
- Returns:
ForceAtlas2instance
fa = ForceAtlas2.inferSettings(G, verbose=False, seed=42)
pos = fa.forceatlas2(G, iterations=100)
Methods
forceatlas2(G, pos=None, iterations=100, callbacks=None, sizes=None)
Compute layout from an adjacency matrix.
- G:
numpy.ndarrayorscipy.sparsematrix (must be symmetric) - pos: Initial positions as
(N, dim)array, orNonefor random - iterations: Number of layout iterations (must be >= 1)
- callbacks: List of
callback(iteration, nodes)functions - sizes: Node radii as
(N,)array (foradjustSizes=True) - Returns: List of tuples with
dimelements per node
forceatlas2_networkx_layout(G, pos=None, iterations=100, weight_attr=None, callbacks=None, size_attr=None, store_pos_as=None)
Compute layout for a NetworkX graph. Supports NetworkX 2.7+ and 3.x.
- G:
networkx.Graph(undirected) - pos: Initial positions as
{node: tuple}dict - weight_attr: Edge attribute name for weights
- callbacks: List of
callback(iteration, nodes)functions - size_attr: Node attribute name for sizes (used with
adjustSizes) - store_pos_as: If set, saves positions as node attributes under this key
- Returns: Dict of
{node: tuple}
forceatlas2_igraph_layout(G, pos=None, iterations=100, weight_attr=None, callbacks=None, size_attr=None, store_pos_as=None)
Compute layout for an igraph graph.
- G:
igraph.Graph(must be undirected) - pos: Initial positions as list or
(N, dim)numpy array - weight_attr: Edge attribute name for weights
- callbacks: List of
callback(iteration, nodes)functions - size_attr: Vertex attribute name for sizes
- store_pos_as: If set, saves positions as vertex attributes
- Returns:
igraph.Layout
