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.
Files changed (52) hide show
  1. {phylustrator-0.2.2/src/phylustrator.egg-info → phylustrator-0.2.3}/PKG-INFO +1 -1
  2. {phylustrator-0.2.2 → phylustrator-0.2.3}/pyproject.toml +1 -1
  3. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/genomes/figure.py +10 -6
  4. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/genomes/layout.py +18 -5
  5. {phylustrator-0.2.2 → phylustrator-0.2.3/src/phylustrator.egg-info}/PKG-INFO +1 -1
  6. {phylustrator-0.2.2 → phylustrator-0.2.3}/tests/test_genomes.py +17 -0
  7. {phylustrator-0.2.2 → phylustrator-0.2.3}/LICENSE +0 -0
  8. {phylustrator-0.2.2 → phylustrator-0.2.3}/README.md +0 -0
  9. {phylustrator-0.2.2 → phylustrator-0.2.3}/setup.cfg +0 -0
  10. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/__init__.py +0 -0
  11. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/__main__.py +0 -0
  12. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/cli.py +0 -0
  13. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/color.py +0 -0
  14. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/compose.py +0 -0
  15. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/genomes/__init__.py +0 -0
  16. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/genomes/genome.py +0 -0
  17. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/genomes/io.py +0 -0
  18. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/genomes/layers/__init__.py +0 -0
  19. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/genomes/layers/genes.py +0 -0
  20. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/genomes/layers/guides.py +0 -0
  21. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/genomes/layers/highlight.py +0 -0
  22. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/genomes/layers/synteny.py +0 -0
  23. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/genomes/matrix.py +0 -0
  24. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/genomes/panels.py +0 -0
  25. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/genomes/track.py +0 -0
  26. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/render.py +0 -0
  27. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/style.py +0 -0
  28. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/trees/__init__.py +0 -0
  29. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/trees/figure.py +0 -0
  30. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/trees/io.py +0 -0
  31. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/trees/layers/__init__.py +0 -0
  32. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/trees/layers/clades.py +0 -0
  33. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/trees/layers/coloring.py +0 -0
  34. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/trees/layers/events.py +0 -0
  35. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/trees/layers/guides.py +0 -0
  36. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/trees/layers/labels.py +0 -0
  37. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/trees/layers/tracks.py +0 -0
  38. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/trees/layout.py +0 -0
  39. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/trees/skeleton.py +0 -0
  40. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/trees/tree.py +0 -0
  41. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator/zombi.py +0 -0
  42. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator.egg-info/SOURCES.txt +0 -0
  43. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator.egg-info/dependency_links.txt +0 -0
  44. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator.egg-info/entry_points.txt +0 -0
  45. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator.egg-info/requires.txt +0 -0
  46. {phylustrator-0.2.2 → phylustrator-0.2.3}/src/phylustrator.egg-info/top_level.txt +0 -0
  47. {phylustrator-0.2.2 → phylustrator-0.2.3}/tests/test_cli.py +0 -0
  48. {phylustrator-0.2.2 → phylustrator-0.2.3}/tests/test_figure.py +0 -0
  49. {phylustrator-0.2.2 → phylustrator-0.2.3}/tests/test_io.py +0 -0
  50. {phylustrator-0.2.2 → phylustrator-0.2.3}/tests/test_layout.py +0 -0
  51. {phylustrator-0.2.2 → phylustrator-0.2.3}/tests/test_tree.py +0 -0
  52. {phylustrator-0.2.2 → phylustrator-0.2.3}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: phylustrator
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: A composable plotter for evolutionary figures: phylogenetic trees, genomes, synteny and alignments
5
5
  Author-email: "Adrian A. Davin" <aaredav@gmail.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "phylustrator"
7
- version = "0.2.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
- return Figure(genome, layout=layout, coordinates=coordinates, style=style)
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
- min_arc = min(math.radians(min_deg), 0.9 * sweep / max(n, 1))
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 \
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: phylustrator
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: A composable plotter for evolutionary figures: phylogenetic trees, genomes, synteny and alignments
5
5
  Author-email: "Adrian A. Davin" <aaredav@gmail.com>
6
6
  License: MIT
@@ -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