phylustrator 0.2.2__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {phylustrator-0.2.2/src/phylustrator.egg-info → phylustrator-0.2.3}/PKG-INFO +1 -1
- {phylustrator-0.2.2 → phylustrator-0.2.3}/pyproject.toml +1 -1
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/genomes/figure.py +10 -6
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/genomes/layout.py +18 -5
- {phylustrator-0.2.2 → phylustrator-0.2.3/src/phylustrator.egg-info}/PKG-INFO +1 -1
- {phylustrator-0.2.2 → phylustrator-0.2.3}/tests/test_genomes.py +17 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/LICENSE +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/README.md +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/setup.cfg +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/__init__.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/__main__.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/cli.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/color.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/compose.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/genomes/__init__.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/genomes/genome.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/genomes/io.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/genomes/layers/__init__.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/genomes/layers/genes.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/genomes/layers/guides.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/genomes/layers/highlight.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/genomes/layers/synteny.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/genomes/matrix.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/genomes/panels.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/genomes/track.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/render.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/style.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/trees/__init__.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/trees/figure.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/trees/io.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/trees/layers/__init__.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/trees/layers/clades.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/trees/layers/coloring.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/trees/layers/events.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/trees/layers/guides.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/trees/layers/labels.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/trees/layers/tracks.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/trees/layout.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/trees/skeleton.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/trees/tree.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/zombi.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator.egg-info/SOURCES.txt +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator.egg-info/dependency_links.txt +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator.egg-info/entry_points.txt +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator.egg-info/requires.txt +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator.egg-info/top_level.txt +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/tests/test_cli.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/tests/test_figure.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/tests/test_io.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/tests/test_layout.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/tests/test_tree.py +0 -0
- {phylustrator-0.2.2 → phylustrator-0.2.3}/tests/test_version.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "phylustrator"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.3"
|
|
8
8
|
description = "A composable plotter for evolutionary figures: phylogenetic trees, genomes, synteny and alignments"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -25,7 +25,7 @@ _LAYOUTS = {"linear": linear, "circular": circular}
|
|
|
25
25
|
|
|
26
26
|
class Figure:
|
|
27
27
|
def __init__(self, genome, *, layout: str = "linear", coordinates: str = "ordered",
|
|
28
|
-
style: Style | None = None, layers: tuple = ()) -> None:
|
|
28
|
+
style: Style | None = None, layers: tuple = (), **layout_kw) -> None:
|
|
29
29
|
if layout not in _LAYOUTS:
|
|
30
30
|
raise ValueError(f"unknown layout {layout!r}; choose from {sorted(_LAYOUTS)}")
|
|
31
31
|
self.genome = genome
|
|
@@ -33,13 +33,15 @@ class Figure:
|
|
|
33
33
|
self.coordinates = coordinates
|
|
34
34
|
self.style = style or Style()
|
|
35
35
|
self.layers = tuple(layers)
|
|
36
|
+
self.layout_kw = dict(layout_kw) # e.g. scale="shared" on a circular genome
|
|
36
37
|
|
|
37
38
|
def _make_layout(self) -> Layout:
|
|
38
|
-
return _LAYOUTS[self.layout](self.genome, coordinates=self.coordinates, style=self.style
|
|
39
|
+
return _LAYOUTS[self.layout](self.genome, coordinates=self.coordinates, style=self.style,
|
|
40
|
+
**self.layout_kw)
|
|
39
41
|
|
|
40
42
|
def _clone(self, **kw) -> "Figure":
|
|
41
43
|
base = dict(layout=self.layout, coordinates=self.coordinates, style=self.style,
|
|
42
|
-
layers=self.layers)
|
|
44
|
+
layers=self.layers, **self.layout_kw)
|
|
43
45
|
base.update(kw)
|
|
44
46
|
return Figure(self.genome, **base) # type: ignore[arg-type] # kw dict, params are typed
|
|
45
47
|
|
|
@@ -85,9 +87,11 @@ class StackFigure(Figure):
|
|
|
85
87
|
|
|
86
88
|
|
|
87
89
|
def plot(genome, *, layout: str = "linear", coordinates: str = "ordered",
|
|
88
|
-
style: Style | None = None) -> Figure:
|
|
89
|
-
"""Start a figure for one ``genome``. Add layers with ``+``, then :meth:`Figure.save`.
|
|
90
|
-
|
|
90
|
+
style: Style | None = None, **layout_kw) -> Figure:
|
|
91
|
+
"""Start a figure for one ``genome``. Add layers with ``+``, then :meth:`Figure.save`.
|
|
92
|
+
|
|
93
|
+
Extra keywords reach the layout — ``scale="shared"`` on a circular genome, for one."""
|
|
94
|
+
return Figure(genome, layout=layout, coordinates=coordinates, style=style, **layout_kw)
|
|
91
95
|
|
|
92
96
|
|
|
93
97
|
def stack(genomes, *, coordinates: str = "ordered", style: Style | None = None) -> StackFigure:
|
|
@@ -90,27 +90,40 @@ def stacked(genomes, *, coordinates: str = "ordered", gap: float = 0.16,
|
|
|
90
90
|
|
|
91
91
|
|
|
92
92
|
def circular(genome, *, coordinates: str = "ordered", gap: float = 0.16,
|
|
93
|
-
start_deg: float = 90.0, break_deg: float = 0.0,
|
|
93
|
+
start_deg: float = 90.0, break_deg: float = 0.0, scale: str = "each",
|
|
94
94
|
band: float = 0.34, ring_gap: float = 0.10, min_deg: float = 2.2, style=None) -> Layout:
|
|
95
95
|
"""Genes wrapped onto a ring, one concentric ring per chromosome (chromosome 0 outermost).
|
|
96
96
|
|
|
97
97
|
Angles sweep **clockwise** from the top. By default the ring is closed (``break_deg=0``) so genes
|
|
98
98
|
are evenly spaced all the way round; set ``break_deg`` to leave a wedge marking a linear
|
|
99
99
|
chromosome's ends. ``coordinates`` chooses equal angular slots by **rank** (``"ordered"``) or
|
|
100
|
-
base-proportional arcs (``"nucleotide"``).
|
|
100
|
+
base-proportional arcs (``"nucleotide"``).
|
|
101
|
+
|
|
102
|
+
``scale`` decides what a ring's angles mean when a genome has several chromosomes:
|
|
103
|
+
|
|
104
|
+
``"each"`` (default) gives every chromosome the whole circle, so gene *order* is comparable ring
|
|
105
|
+
to ring and a chromosome's size is not. ``"shared"`` gives every gene the same angular width,
|
|
106
|
+
taken from the largest chromosome, so a short chromosome draws a short arc — which is what a
|
|
107
|
+
**karyotype** needs. Under ``"each"`` a three-gene chromosome is a full circle of three enormous
|
|
108
|
+
wedges beside a seventy-gene ring, and the picture says they are the same size."""
|
|
101
109
|
start = math.radians(start_deg)
|
|
102
110
|
sweep = 2.0 * math.pi - math.radians(break_deg)
|
|
103
111
|
boxes, owner, placed, rings, totals = {}, {}, [], [], []
|
|
112
|
+
if scale not in ("each", "shared"):
|
|
113
|
+
raise ValueError(f"unknown scale {scale!r}; choose 'each' or 'shared'")
|
|
114
|
+
widest = max((len(c.genes) for c in genome.chromosomes), default=1) or 1
|
|
104
115
|
for k, chrom in enumerate(genome.chromosomes):
|
|
105
116
|
R = 1.0 - band / 2.0 - k * (band + ring_gap)
|
|
106
117
|
rings.append(R)
|
|
107
118
|
n = len(chrom.genes)
|
|
108
119
|
nuc = coordinates == "nucleotide" and n and chrom.genes[0].start is not None
|
|
109
120
|
total = float(chrom.length or (chrom.genes[-1].end - chrom.genes[0].start) or 1.0) if nuc \
|
|
110
|
-
else float(n or 1)
|
|
121
|
+
else float(widest if scale == "shared" else (n or 1))
|
|
111
122
|
totals.append(total)
|
|
112
|
-
# cap the minimum arc so a gene-dense genome (a real GFF) never forces genes to overlap
|
|
113
|
-
|
|
123
|
+
# cap the minimum arc so a gene-dense genome (a real GFF) never forces genes to overlap.
|
|
124
|
+
# The cap counts the ring's own slots: under "shared" that is the widest chromosome, so a
|
|
125
|
+
# short ring is never inflated back to the width it would have had on its own.
|
|
126
|
+
min_arc = min(math.radians(min_deg), 0.9 * sweep / max(n if nuc else total, 1.0))
|
|
114
127
|
_ = style # (thickness is read from style at draw time via ring_hh below)
|
|
115
128
|
for rank, gene in enumerate(chrom.genes): # rank, so "ordered" is even with no holes
|
|
116
129
|
lo_v, hi_v = (float(gene.start), float(gene.end)) if nuc \
|
|
@@ -22,6 +22,7 @@ from phylustrator.genomes import (
|
|
|
22
22
|
synteny,
|
|
23
23
|
tracks,
|
|
24
24
|
)
|
|
25
|
+
from phylustrator.genomes.layout import circular
|
|
25
26
|
from phylustrator.trees import loads
|
|
26
27
|
from phylustrator.trees import plot as tree_plot
|
|
27
28
|
|
|
@@ -57,6 +58,22 @@ def test_position_axis_on_nucleotide_ring():
|
|
|
57
58
|
assert svg.lstrip().startswith("<")
|
|
58
59
|
|
|
59
60
|
|
|
61
|
+
def test_circular_shared_scale_shortens_the_small_chromosome():
|
|
62
|
+
"""A karyotype: under ``scale="shared"`` a short chromosome draws a short arc."""
|
|
63
|
+
big = _genome("g", list("12345678")).chromosomes[0]
|
|
64
|
+
small = Chromosome(id="c2", genes=_genome("s", ["9", "10"]).chromosomes[0].genes,
|
|
65
|
+
topology="circular", length=200)
|
|
66
|
+
g = Genome(name="k", chromosomes=[big, small])
|
|
67
|
+
arc = {}
|
|
68
|
+
for scale in ("each", "shared"):
|
|
69
|
+
lay = circular(g, scale=scale)
|
|
70
|
+
a0, a1, _ = lay.boxes[id(small.genes[0])]
|
|
71
|
+
arc[scale] = abs(a1 - a0)
|
|
72
|
+
assert arc["shared"] < arc["each"] / 3 # 8 slots wide, not 2
|
|
73
|
+
with pytest.raises(ValueError, match="unknown scale"):
|
|
74
|
+
circular(g, scale="both")
|
|
75
|
+
|
|
76
|
+
|
|
60
77
|
def test_heatmap_panel_beside_tree():
|
|
61
78
|
tree = tree_plot(loads("(a:1,b:1)R;"))
|
|
62
79
|
m = Matrix(rows=["a", "b"], cols=["f1", "f2", "f3"], values=[[1, 0, 2], [0, 1, 1]])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|