Skip to content

Simple API (fa2.easy)

No numpy knowledge required. One-call functions that accept edge lists and return plain dicts.

Quick Examples

from fa2.easy import layout, visualize

# Edge list → positions
positions = layout([("A", "B"), ("B", "C"), ("A", "C")], mode="community")

# Edge list → PNG image
visualize([("A", "B"), ("B", "C")], output="png", path="graph.png")

Input Formats

All functions accept these edge formats:

edges = [("A", "B"), ("B", "C", 5.0)]  # optional weight
edges = [{"source": "A", "target": "B", "weight": 5.0}]
edges = {"A": ["B", "C"], "B": ["A", "D"]}

Mode Presets

Mode Parameters Set Use Case
"default" Standard FA2 defaults General layout
"community" LinLog + dissuade hubs Community detection
"hub-dissuade" Dissuade hubs + strong gravity Push hubs to periphery
"compact" High gravity, low scaling Tight layouts

Functions

layout

layout(edges: Union[list, dict], iterations: int = 100, dim: int = 2, mode: str = 'default', seed: Optional[int] = None, node_sizes: Optional[dict] = None, node_positions: Optional[dict] = None) -> dict

Compute a graph layout from an edge list. No numpy required.

Parameters:

Name Type Description Default
edges list or dict

Graph edges in any of these formats:

  • Edge tuples: [(0, 1), (1, 2)] or [("A", "B", 5.0), ...]
  • Edge dicts: [{"source": "A", "target": "B", "weight": 5.0}, ...]
  • Adjacency dict: {"A": ["B", "C"], "B": ["A", "D"]}
required
iterations int

Number of layout iterations. Default 100.

100
dim int

Layout dimensions. Default 2.

2
mode str

Layout preset: "default", "community" (LinLog + dissuade hubs), "hub-dissuade" (hubs to borders), "compact" (higher gravity).

'default'
seed int

Random seed for reproducibility.

None
node_sizes dict

Node radii as {node_id: float}. Enables anti-collision.

None
node_positions dict

Initial positions as {node_id: (x, y, ...)}.

None

Returns:

Type Description
dict

Positions as {node_id: (x, y, ...)}.

Source code in fa2/easy.py
def layout(
    edges: Union[list, dict],
    iterations: int = 100,
    dim: int = 2,
    mode: str = "default",
    seed: Optional[int] = None,
    node_sizes: Optional[dict] = None,
    node_positions: Optional[dict] = None,
) -> dict:
    """Compute a graph layout from an edge list. No numpy required.

    Parameters
    ----------
    edges : list or dict
        Graph edges in any of these formats:

        - Edge tuples: ``[(0, 1), (1, 2)]`` or ``[("A", "B", 5.0), ...]``
        - Edge dicts: ``[{"source": "A", "target": "B", "weight": 5.0}, ...]``
        - Adjacency dict: ``{"A": ["B", "C"], "B": ["A", "D"]}``
    iterations : int
        Number of layout iterations. Default 100.
    dim : int
        Layout dimensions. Default 2.
    mode : str
        Layout preset: ``"default"``, ``"community"`` (LinLog + dissuade hubs),
        ``"hub-dissuade"`` (hubs to borders), ``"compact"`` (higher gravity).
    seed : int, optional
        Random seed for reproducibility.
    node_sizes : dict, optional
        Node radii as ``{node_id: float}``. Enables anti-collision.
    node_positions : dict, optional
        Initial positions as ``{node_id: (x, y, ...)}``.

    Returns
    -------
    dict
        Positions as ``{node_id: (x, y, ...)}``.
    """
    if mode not in _MODE_PRESETS:
        raise ValueError(f"Unknown mode {mode!r}. Choose from: {list(_MODE_PRESETS.keys())}")

    node_list, G = _parse_edges(edges)

    if not node_list:
        return {}

    # Build FA2 kwargs from mode preset
    fa2_kwargs = {
        "dim": dim,
        "seed": seed,
        "verbose": False,
        **_MODE_PRESETS[mode],
    }

    # Handle node sizes → adjustSizes
    sizes_array = None
    if node_sizes is not None:
        fa2_kwargs["adjustSizes"] = True
        sizes_array = np.array([float(node_sizes.get(n, 1.0)) for n in node_list])

    # Handle initial positions
    pos_array = None
    if node_positions is not None:
        pos_array = np.array([
            node_positions.get(n, tuple(0.0 for _ in range(dim)))
            for n in node_list
        ], dtype=np.float64)

    # Use inferSettings for auto-tuning, then override with mode preset
    fa2 = ForceAtlas2.inferSettings(G, **fa2_kwargs)

    positions = fa2.forceatlas2(G, pos=pos_array, iterations=iterations, sizes=sizes_array)

    return {node: pos for node, pos in zip(node_list, positions)}

visualize

visualize(edges: Union[list, dict], iterations: int = 100, dim: int = 2, mode: str = 'default', output: str = 'matplotlib', path: Optional[str] = None, seed: Optional[int] = None, title: Optional[str] = None, **kwargs)

Layout a graph and render it in one call.

Parameters:

Name Type Description Default
edges list or dict

Graph edges (same formats as layout()).

required
iterations int

Number of layout iterations. Default 100.

100
dim int

Layout dimensions. Default 2.

2
mode str

Layout preset (see layout()).

'default'
output str

Output format: "matplotlib" (returns Figure), "png", "svg", "json".

'matplotlib'
path str

File path to write output. If None, returns the result.

None
seed int

Random seed for reproducibility.

None
title str

Title for the plot (image outputs only).

None
**kwargs

Extra arguments passed to plot_layout() for image outputs.

{}

Returns:

Type Description
matplotlib.figure.Figure, bytes, or dict

Depends on output format.

Source code in fa2/easy.py
def visualize(
    edges: Union[list, dict],
    iterations: int = 100,
    dim: int = 2,
    mode: str = "default",
    output: str = "matplotlib",
    path: Optional[str] = None,
    seed: Optional[int] = None,
    title: Optional[str] = None,
    **kwargs,
):
    """Layout a graph and render it in one call.

    Parameters
    ----------
    edges : list or dict
        Graph edges (same formats as ``layout()``).
    iterations : int
        Number of layout iterations. Default 100.
    dim : int
        Layout dimensions. Default 2.
    mode : str
        Layout preset (see ``layout()``).
    output : str
        Output format: ``"matplotlib"`` (returns Figure), ``"png"``,
        ``"svg"``, ``"json"``.
    path : str, optional
        File path to write output. If None, returns the result.
    seed : int, optional
        Random seed for reproducibility.
    title : str, optional
        Title for the plot (image outputs only).
    **kwargs
        Extra arguments passed to ``plot_layout()`` for image outputs.

    Returns
    -------
    matplotlib.figure.Figure, bytes, or dict
        Depends on ``output`` format.
    """
    # Parse edges once, compute layout, then reuse for viz
    edge_list, nodes = _collect_edges(edges)
    node_list = sorted(nodes, key=str)
    node_list, G = _edges_to_sparse(node_list, edge_list)

    if not node_list:
        return {} if output == "json" else None

    # Compute layout
    if mode not in _MODE_PRESETS:
        raise ValueError(f"Unknown mode {mode!r}. Choose from: {list(_MODE_PRESETS.keys())}")
    fa2_kwargs = {"dim": dim, "seed": seed, "verbose": False, **_MODE_PRESETS[mode]}
    fa2 = ForceAtlas2.inferSettings(G, **fa2_kwargs)
    raw_positions = fa2.forceatlas2(G, iterations=iterations)
    positions = {node: pos for node, pos in zip(node_list, raw_positions)}

    # Build a NetworkX graph for viz (handles node IDs natively)
    try:
        import networkx as nx
        G_viz = nx.Graph()
        G_viz.add_nodes_from(node_list)
        for src, tgt, w in edge_list:
            if src in positions and tgt in positions:
                G_viz.add_edge(src, tgt, weight=w)
    except ImportError:
        G_viz = G
        positions = {i: positions[n] for i, n in enumerate(node_list)}

    if output == "json":
        from .viz import export_layout
        return export_layout(G_viz, positions, fmt="json", path=path)

    if output in ("png", "svg"):
        from .viz import export_layout
        return export_layout(G_viz, positions, fmt=output, path=path, title=title, **kwargs)

    if output == "matplotlib":
        from .viz import plot_layout
        return plot_layout(G_viz, positions, title=title, **kwargs)

    raise ValueError(f"Unknown output format {output!r}. Use 'matplotlib', 'png', 'svg', or 'json'.")