dnetwork#

Plotting and styling for directed phylogenetic networks (DirectedPhyNetwork).

Plotting#

phylozoo.viz.dnetwork.plot_dnetwork(network: DirectedPhyNetwork, layout: str = 'pz-cladogram', style: DNetStyle | None = None, ax: Any | None = None, show: bool = False, **layout_kwargs: Any) → Any[source]#

Plot a DirectedPhyNetwork.

This is the main public API function for plotting networks. It handles layout computation, styling, and rendering using matplotlib.

Parameters:
  • network (DirectedPhyNetwork) – The network to plot.

  • layout (str, optional) – Layout algorithm. PhyloZoo: ‘pz-cladogram’ (layered with a tree backbone; rectangular=False for straight edges), ‘pz-layered’ (layered, dot-like), ‘pz-radial’ (circular cladogram) or ‘pz-unrooted’ (unrooted tree of blobs). NetworkX: ‘spring’, ‘circular’, ‘kamada_kawai’, ‘planar’, ‘random’, ‘shell’, ‘spectral’, ‘spiral’, ‘bipartite’. Graphviz: ‘dot’, ‘neato’, ‘fdp’, ‘sfdp’, ‘twopi’, ‘circo’. By default ‘pz-cladogram’.

  • style (DNetStyle, optional) – Styling configuration. If None, uses default style. By default None.

  • ax (matplotlib.axes.Axes, optional) – Existing axes to plot on. If None, creates new figure and axes. By default None.

  • show (bool, optional) – If True, automatically display the plot using plt.show(). By default False.

  • **layout_kwargs – Additional parameters for layout computation.

Returns:

The axes object containing the plot.

Return type:

matplotlib.axes.Axes

Raises:

PhyloZooLayoutError – If layout algorithm is not supported.

Examples

>>> from phylozoo.core.network.dnetwork import DirectedPhyNetwork
>>> from phylozoo.viz import plot
>>>
>>> net = DirectedPhyNetwork(
...     edges=[(3, 1), (3, 2)],
...     nodes=[(1, {'label': 'A'}), (2, {'label': 'B'})]
... )
>>> ax = plot(net)

Layouts#

Each function returns a DNetLayout; the keyword arguments are the layout parameters accepted by plot().

phylozoo.viz.dnetwork.layout.cladogram.compute_pz_cladogram_layout(network: DirectedPhyNetwork, layer_gap: float = 1.0, leaf_gap: float = 1.0, trials: int = 10, seed: int | None = 0, direction: str = 'LR', x_scale: float = 1.0, y_scale: float = 1.0, align_leaves: bool = True, rectangular: bool = True, horizontal_reticulations: bool | None = None) → DNetLayout[source]#

Layout a phylogenetic network (DAG) as a layered drawing with a tree backbone.

This is a custom PhyloZoo layout algorithm (pz-cladogram).

The algorithm:

  1. Assigns every node to a layer (its longest path from the root); leaves are moved to the bottom layer unless align_leaves is False.

  2. Hangs every node below its lowest parent, which gives a spanning tree (the backbone). Edges from the other parents of hybrid nodes are the reticulate edges.

  3. Orders the children of every backbone node: a few barycenter passes pull subtrees towards the partners of their reticulate edges, then a local search swaps adjacent siblings whenever that lowers the exact number of edge crossings (skipped for very large networks). trials restarts of this procedure, each from a random order of the nodes that can affect crossings (ancestors of reticulate-edge endpoints), are compared and the best kept.

  4. Places leaves left to right, internal nodes above the mean of their children, and maps layers to the vertical ('TD') or horizontal ('LR') axis. In rectangular drawings, a node with a single backbone child whose reticulate edge could not be made horizontal is placed half a leaf_gap beyond its hybrid instead (on the side away from that child), so the reticulate edge reaches the hybrid from the opposite side of its other parent rather than running alongside the backbone edge.

Parameters:
  • network (DirectedPhyNetwork) – The network to layout.

  • layer_gap (float, optional) – Spacing between layers. By default 1.0.

  • leaf_gap (float, optional) – Spacing between consecutive leaves. By default 1.0.

  • trials (int, optional) – Number of ordering attempts; the first starts from the network’s node order, the others from random orders of the crossing-relevant nodes. Trees need one. By default 10.

  • seed (int | None, optional) – Random seed for the restarts; None draws a fresh seed. By default 0.

  • direction (str, optional) – Layout direction: ‘TD’ (root at the top) or ‘LR’ (root on the left). By default ‘LR’.

  • x_scale (float, optional) – Scaling factor applied to x coordinates. By default 1.0.

  • y_scale (float, optional) – Scaling factor applied to y coordinates. By default 1.0.

  • align_leaves (bool, optional) – Put all leaves on the last layer (right, or bottom for ‘TD’). By default True.

  • rectangular (bool, optional) – Draw edges as orthogonal elbows (backbone edges run along the parent’s layer and then into the child, reticulate edges enter the hybrid node sideways along its layer); False draws straight lines. By default True.

  • horizontal_reticulations (bool | None, optional) – Move the parent of every reticulate edge down to its hybrid’s layer whenever the network allows it (its other descendants are pushed down as needed), so the reticulate edge is horizontal. None (default) means True for rectangular drawings and False otherwise.

Returns:

The computed layout. Positions are centred at the origin and scaled to unit range.

Return type:

DNetLayout

Raises:

Examples

>>> from phylozoo.core.network.dnetwork import DirectedPhyNetwork
>>> from phylozoo.viz.dnetwork.layout import compute_pz_cladogram_layout
>>>
>>> net = DirectedPhyNetwork(
...     edges=[(3, 1), (3, 2)],
...     nodes=[(1, {'label': 'A'}), (2, {'label': 'B'})]
... )
>>> layout = compute_pz_cladogram_layout(net)
>>> len(layout.positions)
3
phylozoo.viz.dnetwork.layout.layered.compute_pz_layered_layout(network: DirectedPhyNetwork, layer_gap: float = 1.0, node_gap: float = 1.0, sweeps: int = 8, direction: str = 'TD', align_leaves: bool = False, x_scale: float = 1.0, y_scale: float = 1.0, rectangular: bool = False) → DNetLayout[source]#

Layout a phylogenetic network (DAG) with the layered (Sugiyama) method of dot.

This is a custom PhyloZoo layout algorithm (pz-layered).

The algorithm:

  1. Assigns every node to a layer, its longest path from the root, so every edge points to a strictly lower layer and hybrid nodes sit right below their lowest parent.

  2. Replaces every edge that spans more than one layer by a chain through dummy nodes, one per intermediate layer, so long edges are ordered and routed like nodes instead of cutting across.

  3. Orders each layer, starting from the pz-cladogram order, by alternating downward and upward barycenter sweeps (a node moves to the mean position of its neighbours in the layer just processed), each followed by swapping adjacent nodes while that lowers the number of crossings with the neighbouring layers. The ordering with the fewest crossings over all sweeps is kept.

  4. Assigns coordinates within layers: a few passes pull every node towards the mean of its neighbours (dummy nodes towards their edge, keeping it straight) while keeping nodes node_gap apart.

Parameters:
  • network (DirectedPhyNetwork) – The network to layout.

  • layer_gap (float, optional) – Spacing between layers. By default 1.0.

  • node_gap (float, optional) – Minimum spacing between nodes of the same layer. By default 1.0.

  • sweeps (int, optional) – Number of down-and-up ordering sweeps. By default 8.

  • direction (str, optional) – Layout direction: ‘TD’ (root at the top) or ‘LR’ (root on the left). By default ‘TD’.

  • align_leaves (bool, optional) – Put all leaves on the last layer (right, or bottom for ‘TD’). By default False (leaves stay at their own depth, as in dot).

  • x_scale (float, optional) – Scaling factor applied to x coordinates. By default 1.0.

  • y_scale (float, optional) – Scaling factor applied to y coordinates. By default 1.0.

  • rectangular (bool, optional) – Draw every edge segment as an orthogonal elbow: along the layer of its upper end and then down; the last segment into a hybrid node instead runs down first and enters the hybrid sideways. By default False.

Returns:

The computed layout, with polyline edge routes and algorithm='pz-layered'. Positions are centred and scaled to unit range.

Return type:

DNetLayout

Raises:

Examples

>>> from phylozoo.core.network.dnetwork import DirectedPhyNetwork
>>> from phylozoo.viz.dnetwork.layout import compute_pz_layered_layout
>>>
>>> net = DirectedPhyNetwork(
...     edges=[(5, 3), (5, 4), (3, 2), (4, 2), (2, 1), (3, 6), (4, 7)],
...     nodes=[(1, {'label': 'A'}), (6, {'label': 'B'}), (7, {'label': 'C'})]
... )
>>> layout = compute_pz_layered_layout(net)
>>> layout.algorithm
'pz-layered'
>>> len(layout.positions) == net.number_of_nodes()
True
phylozoo.viz.dnetwork.layout.radial.compute_pz_radial_layout(network: DirectedPhyNetwork, radius: float = 1.0, start_angle: float = 0.0, angle_direction: str = 'clockwise', **kwargs: Any) → DNetLayout[source]#

Compute a radial (circular cladogram) layout for a DirectedPhyNetwork.

This is a custom PhyloZoo layout algorithm (pz-radial). The layered pz-cladogram layout is computed first (tree backbone, hybrid-aware leaf order, leaves on the bottom layer) and then mapped to polar coordinates: the leaf order becomes the angular order (leaves evenly spaced on the outer circle), a node’s layer becomes its radius, and every internal node sits at the mean angle of its backbone children. The sibling order is then re-optimised by adjacent swaps with the crossings counted in the circle, so reticulate edges (straight chords) cross as little as possible there.

Parameters:
  • network (DirectedPhyNetwork) – The network to layout.

  • radius (float, optional) – Radius of the leaf circle. By default 1.0.

  • start_angle (float, optional) – Angle of the first leaf, in radians. By default 0.0.

  • angle_direction (str, optional) – ‘clockwise’ or ‘counterclockwise’. By default ‘clockwise’.

  • **kwargs – Ordering options of compute_pz_cladogram_layout() (trials, seed, horizontal_reticulations).

Returns:

The computed layout, with algorithm='pz-radial'.

Return type:

DNetLayout

Raises:

Examples

>>> from phylozoo.core.network.dnetwork import DirectedPhyNetwork
>>> from phylozoo.viz.dnetwork.layout import compute_pz_radial_layout
>>>
>>> net = DirectedPhyNetwork(
...     edges=[(3, 1), (3, 2)],
...     nodes=[(1, {'label': 'A'}), (2, {'label': 'B'})]
... )
>>> layout = compute_pz_radial_layout(net)
>>> layout.get_position(3)
(0.0, 0.0)
>>> round(math.hypot(*layout.get_position(1)), 6)
1.0
phylozoo.viz.dnetwork.layout.unrooted.compute_pz_unrooted_layout(network: DirectedPhyNetwork, edge_length: float = 1.0, node_spacing: float = 0.7, inner_scale: float = 0.6, start_angle: float = 0.0, refine: int = 50, daylight: int = 0) → DNetLayout[source]#

Compute a tree-of-blobs (unrooted-style) layout for a DirectedPhyNetwork.

This is a custom PhyloZoo layout algorithm (pz-unrooted), identical to the semi-directed version (see phylozoo.viz.sdnetwork.layout.unrooted.compute_pz_unrooted_layout()) applied to the underlying undirected structure: the root is not distinguished and every edge carries an arrowhead when plotted.

Parameters:
  • network (DirectedPhyNetwork) – The network to layout.

  • edge_length (float, optional) – Length of cut edges. By default 1.0.

  • node_spacing (float, optional) – Target distance between consecutive nodes on a blob’s circle. By default 0.7.

  • inner_scale (float, optional) – Scale of subtrees hanging off interior blob nodes. By default 0.6.

  • start_angle (float, optional) – Rotation of the whole drawing, in radians. By default 0.0.

  • refine (int, optional) – Stress-majorization iterations (0 = raw geometry). By default 50.

  • daylight (int, optional) – Equal-daylight rounds applied before the refinement. By default 0.

Returns:

The computed layout, with algorithm='pz-unrooted'.

Return type:

DNetLayout

Examples

>>> from phylozoo.core.network.dnetwork import DirectedPhyNetwork
>>> from phylozoo.viz.dnetwork.layout import compute_pz_unrooted_layout
>>>
>>> net = DirectedPhyNetwork(
...     edges=[(5, 3), (5, 4), (3, 2), (4, 2), (2, 1), (3, 6), (4, 7)],
...     nodes=[(1, {'label': 'A'}), (6, {'label': 'B'}), (7, {'label': 'C'})]
... )
>>> layout = compute_pz_unrooted_layout(net)
>>> layout.algorithm
'pz-unrooted'
>>> len(layout.positions) == net.number_of_nodes()
True
phylozoo.viz.dnetwork.layout.nx.compute_nx_layout(network: DirectedPhyNetwork, layout: str = 'spring', **kwargs: Any) → DNetLayout[source]#

Compute layout for DirectedPhyNetwork using NetworkX or Graphviz.

Parameters:
  • network (DirectedPhyNetwork) – The network to layout.

  • layout (str, optional) – Layout algorithm name. NetworkX: ‘spring’, ‘circular’, ‘kamada_kawai’, ‘planar’, ‘random’, ‘shell’, ‘spectral’, ‘spiral’, ‘bipartite’. Graphviz (needs pygraphviz): ‘dot’, ‘neato’, ‘fdp’, ‘sfdp’, ‘twopi’, ‘circo’. By default ‘spring’.

  • **kwargs – Additional parameters passed to the layout algorithm.

Returns:

The computed layout.

Return type:

DNetLayout

Raises:
  • PhyloZooLayoutError – If layout algorithm is not supported, network is empty, or layout computation fails.

  • PhyloZooImportError – If Graphviz layout is requested but pygraphviz is not installed.

Examples

>>> from phylozoo.core.network.dnetwork import DirectedPhyNetwork
>>> from phylozoo.viz.dnetwork.layout.nx import compute_nx_layout
>>>
>>> net = DirectedPhyNetwork(edges=[(3, 1), (3, 2)])
>>> layout = compute_nx_layout(net, layout='circular')
>>> len(layout.positions)
3
class phylozoo.viz.dnetwork.layout.base.DNetLayout(network: DirectedPhyNetwork, positions: dict[T, NodePosition], edge_routes: dict[tuple[T, T, int], EdgeRoute], algorithm: str = 'pz-cladogram', parameters: dict[str, Any] = <factory>, backbone_edges: set[tuple[T, T, int]] = <factory>, reticulate_edges: set[tuple[T, T, int]] = <factory>)[source]#

Bases: DMGraphLayout[T]

Layout for DirectedPhyNetwork.

This layout class stores the computed positions and routing information for a DirectedPhyNetwork layout. It is the base layout class for all dnetwork layout algorithms.

Examples

>>> from phylozoo.core.network.dnetwork import DirectedPhyNetwork
>>> from phylozoo.viz.dnetwork.layout import compute_pz_cladogram_layout
>>>
>>> net = DirectedPhyNetwork(
...     edges=[(3, 1), (3, 2)],
...     nodes=[(1, {'label': 'A'}), (2, {'label': 'B'})]
... )
>>> layout = compute_pz_cladogram_layout(net)
>>> layout.get_position(1)  # leaves on the right layer, root centred on the left
(0.5, 0.5)
>>> layout.get_position(3)
(-0.5, 0.0)
>>> layout.algorithm
'pz-cladogram'
network#

The directed phylogenetic network this layout is for (read-only).

Type:

DirectedPhyNetwork

positions#

Node positions mapping node ID to (x, y) coordinates (read-only).

Type:

dict[T, tuple[float, float]]

edge_routes#

Edge routing information mapping (u, v, key) to EdgeRoute (read-only).

Type:

dict[tuple[T, T, int], EdgeRoute]

backbone_edges#

Edges in the backbone tree (read-only).

Type:

set[tuple[T, T, int]]

reticulate_edges#

Hybrid edges not in the backbone tree (read-only).

Type:

set[tuple[T, T, int]]

algorithm#

Name of the layout algorithm used (read-only).

Type:

str

parameters#

Parameters used to generate this layout (read-only).

Type:

dict[str, Any]

Styling#

Styling for DirectedPhyNetwork plots.

This module provides styling configuration for DirectedPhyNetwork visualizations. The defaults match those of semi-directed network plots (white tree nodes, black leaves, pink hybrid nodes, red hybrid edges, labels aligned with their edge).

class phylozoo.viz.dnetwork.style.DNetStyle(node_color: str = 'white', node_size: float = 80.0, node_edge_color: str = 'black', node_edge_width: float = 1.5, edge_color: str = 'gray', edge_width: float = 2.0, arrow_head_size: float = 12.0, with_labels: bool = True, label_offset: float = 0.015, label_font_size: float = 10.0, label_color: str = 'black', leaf_color: str = '#0a0a0a', hybrid_color: str = '#fcc0bc', leaf_size: float | None = 100.0, hybrid_edge_color: str = 'red', label_rotation: float | None = None, arrows: str | None = None, root_color: str | None = None, root_size: float | None = None)[source]#

Bases: DMGraphStyle

Styling configuration for DirectedPhyNetwork plots.

This class extends DMGraphStyle with DirectedPhyNetwork-specific options, including support for leaves, hybrid nodes, and hybrid edges.

Notes

arrows selects which edges get an arrowhead: 'all', 'hybrid' (only edges into hybrid nodes) or 'none'; None (default) chooses 'hybrid' for the layered and radial layouts, where the direction is implied by the drawing, and 'all' otherwise. label_rotation of None (default) aligns each label with the direction of its edge; a float applies that fixed rotation (in degrees) to all labels. root_color and root_size style the root node; None (default) draws it like any tree node, except in the pz-unrooted layout, where the root is otherwise indistinguishable and gets a gold, larger marker.

Examples

>>> style = DNetStyle(node_color='blue', leaf_color='green')
>>> style.node_color
'blue'
arrow_head_size: float = 12.0#
arrows: str | None = None#
copy() → DNetStyle[source]#

Create a copy of this style.

Returns:

A new DNetStyle instance with the same values.

Return type:

DNetStyle

Examples

>>> style = DNetStyle(node_color='blue')
>>> style2 = style.copy()
>>> style2.node_color
'blue'
hybrid_color: str = '#fcc0bc'#
hybrid_edge_color: str = 'red'#
label_offset: float = 0.015#
label_rotation: float | None = None#
leaf_color: str = '#0a0a0a'#
leaf_size: float | None = 100.0#
node_color: str = 'white'#
node_size: float = 80.0#
root_color: str | None = None#
root_size: float | None = None#
phylozoo.viz.dnetwork.style.default_style() → DNetStyle[source]#

Get the default style configuration for DirectedPhyNetwork.

Returns:

Default style configuration.

Return type:

DNetStyle

Examples

>>> style = default_style()
>>> style.node_color
'white'