Circular ("fan") dendrograms for Python, in the visual style of R's ape package
Project description
dendrofan
Circular ("fan") dendrograms for Python, in the visual style of R's
ape package
(plot.phylo(type = "fan"), Paradis & Schliep, 2019) — computed and
rendered entirely with NumPy, SciPy, and Matplotlib. No R dependency,
no reimplementation of clustering.
Why this exists
scipy.cluster.hierarchy has no circular/fan layout. The usual
workaround is a one-off script: call
scipy.cluster.hierarchy.dendrogram(..., no_plot=True), take its
rectangular icoord/dcoord output, and manually re-project it into
polar coordinates with Matplotlib. That works for one figure, but it
tends to:
- assume a full 360° circle with no gap for labels at the seam,
- divide by
dcoord.max()without checking for a degenerate, all-zero-height tree, - hardcode SciPy's 10-units-per-leaf spacing convention,
- have no story for trees with 1 or 2 leaves,
- mix clustering, geometry, and plotting into a single script, so none of it is reusable for the next dataset.
dendrofan factors the actually-reusable idea — the rectangular-to-polar coordinate transform — into a small, tested, documented library, and builds a real plotting API on top of it.
Install
pip install -e . # from a source checkout
pip install -e ".[dev]" # + pytest, for running the test suite
Quick start
import numpy as np
from dendrofan import circular_dendrogram
rng = np.random.default_rng(0)
data = rng.normal(size=(20, 6))
labels = [f"sample_{i:02d}" for i in range(20)]
result = circular_dendrogram(
data,
labels=labels,
method="ward", # forwarded to scipy.cluster.hierarchy.linkage
span=350, # leave a small angular gap at the seam
inner_radius=0.15, # small central gap, ape's fan style
)
result.fig.savefig("tree.png", dpi=200, bbox_inches="tight")
circular_dendrogram accepts the same three kinds of input as SciPy's
own dendrogram: raw observations (data=...), a precomputed linkage
matrix (Z=...), or a precomputed condensed distance vector
(condensed_distances=...).
See examples/quickstart.py and
examples/reproduce_station_dendrogram.py
(a fully worked, colored-by-group example) for more.
What it handles that the ad hoc version didn't
| Case | Ad hoc script | dendrofan |
|---|---|---|
| Angular gap at the seam (for label room) | not supported (full circle only) | span=350 (or any value <= 360) |
| All-merge-heights-equal-zero tree | ZeroDivisionError / silent NaNs |
falls back to placing all nodes at the leaf radius |
| 1 or 2 leaves | untested, breaks silently | validated; raises DegenerateTreeError for < 2, works for 2 |
| Mismatched label count | silent misalignment | raises LabelMismatchError |
| Invalid linkage matrix / distance vector | undefined behavior | raises InvalidLinkageError before plotting |
| Root at centre vs. rim, radius scale | hardcoded linear, root-at-centre | inner_radius/outer_radius/invert_radius, plus optional "sqrt" area-preserving radius scale |
| Per-clade coloring | manual, one-off | color_threshold/link_color_func forwarded to SciPy, or a label_colors dict/callable |
| Scale reference / clade highlighting | not present | dendrofan.annotations.add_scale_ring, highlight_sector |
| Reuse across datasets | copy-paste the script | one function call |
API overview
dendrofan.circular_dendrogram(...)— the main entry point; draws the tree and returns aCircularDendrogramResult(figure, axes, layout, and thePolarTransformused, for further annotation).dendrofan.geometry.PolarTransform— the reusable rectangular-to-polar coordinate map, if you want to bend your own geometry onto the same annulus (e.g. a custom decoration).dendrofan.clustering.build_layout(...)— validated wrapper aroundscipy.cluster.hierarchy.linkage/dendrogram, decoupled from plotting.dendrofan.styling.resolve_leaf_colors,legend_handles— color and legend helpers.dendrofan.annotations.add_scale_ring,highlight_sector— optional decorations (a distance-reference ring; shaded clade sectors).
Every public function and class has a full docstring; help(...) in a
Python session is the fastest way to see the complete parameter
reference.
Scope
dendrofan draws circular dendrograms from hierarchical clustering
(SciPy linkage matrices) — it does not parse Newick/phylogenetic tree
files or handle unequal-tip-depth phylogenies the way ape itself
does. If you need that, ape (R) or ete3/Bio.Phylo (Python) are a
better fit; dendrofan specifically fills the "I have a SciPy
dendrogram and want it circular" gap.
Testing
pytest
The test suite specifically exercises the edge cases listed above (degenerate trees, mismatched labels, invalid linkage matrices, partial spans, single/two-leaf trees).
Citation
If you use dendrofan in a manuscript, please cite the software itself
via its Zenodo DOI (see CITATION.cff, or use GitHub's
"Cite this repository" button):
Magallanes Quintanar, R. (2026). dendrofan v0.1.0 [Software]. Zenodo. https://doi.org/10.5281/zenodo.21251841
Please also cite the underlying methods it wraps:
- Virtanen, P. et al. (2020). SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17, 261-272. (hierarchical clustering / linkage, which dendrofan builds on)
- Paradis, E. & Schliep, K. (2019). ape 5.0: an environment for modern phylogenetics and evolutionary analyses in R. Bioinformatics, 35(3), 526-528. (for the fan-plot visual convention dendrofan follows)
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dendrofan-0.1.1.tar.gz.
File metadata
- Download URL: dendrofan-0.1.1.tar.gz
- Upload date:
- Size: 21.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3fe9c1c6ce30886891d820fa8498d9bb274853676b23f3301dfa0d0ffa657c32
|
|
| MD5 |
9324c8d9b5819ad99d88cc4ca530662b
|
|
| BLAKE2b-256 |
3d1ba755c4afd456b0c8a00ca73846dc69036ca13829ab929e43ce9c69cf5c67
|
Provenance
The following attestation bundles were made for dendrofan-0.1.1.tar.gz:
Publisher:
publish.yml on tiquis/dendrofan
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dendrofan-0.1.1.tar.gz -
Subject digest:
3fe9c1c6ce30886891d820fa8498d9bb274853676b23f3301dfa0d0ffa657c32 - Sigstore transparency entry: 2110144016
- Sigstore integration time:
-
Permalink:
tiquis/dendrofan@0ccdc5cd4baa535c08a732f28dbf62d3648b10d9 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/tiquis
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0ccdc5cd4baa535c08a732f28dbf62d3648b10d9 -
Trigger Event:
release
-
Statement type:
File details
Details for the file dendrofan-0.1.1-py3-none-any.whl.
File metadata
- Download URL: dendrofan-0.1.1-py3-none-any.whl
- Upload date:
- Size: 20.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
18eaf1542d36b6468cd371bc7f117154120394e6ccb480ed6b6246ca46196607
|
|
| MD5 |
740c44c86aa3640366775aaf8471a1d4
|
|
| BLAKE2b-256 |
dbc801b9c205621860425e9e53c204af4e2a46ee14d4fc6283145e36b50cbe54
|
Provenance
The following attestation bundles were made for dendrofan-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on tiquis/dendrofan
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dendrofan-0.1.1-py3-none-any.whl -
Subject digest:
18eaf1542d36b6468cd371bc7f117154120394e6ccb480ed6b6246ca46196607 - Sigstore transparency entry: 2110144071
- Sigstore integration time:
-
Permalink:
tiquis/dendrofan@0ccdc5cd4baa535c08a732f28dbf62d3648b10d9 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/tiquis
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0ccdc5cd4baa535c08a732f28dbf62d3648b10d9 -
Trigger Event:
release
-
Statement type: