phytreon 0.2.0__tar.gz → 0.2.2__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 (49) hide show
  1. {phytreon-0.2.0 → phytreon-0.2.2}/PKG-INFO +3 -2
  2. {phytreon-0.2.0 → phytreon-0.2.2}/README.md +2 -1
  3. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/__init__.py +1 -1
  4. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/plot/figure.py +25 -3
  5. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon.egg-info/PKG-INFO +3 -2
  6. {phytreon-0.2.0 → phytreon-0.2.2}/tests/test_smoke.py +27 -0
  7. {phytreon-0.2.0 → phytreon-0.2.2}/LICENSE +0 -0
  8. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/comparative/__init__.py +0 -0
  9. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/comparative/ace.py +0 -0
  10. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/comparative/stochastic_mapping.py +0 -0
  11. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/core/__init__.py +0 -0
  12. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/core/io.py +0 -0
  13. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/core/tree.py +0 -0
  14. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/datasets.py +0 -0
  15. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/infer/__init__.py +0 -0
  16. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/infer/_search.py +0 -0
  17. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/infer/aa_models.py +0 -0
  18. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/infer/align.py +0 -0
  19. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/infer/bootstrap.py +0 -0
  20. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/infer/distance.py +0 -0
  21. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/infer/expression.py +0 -0
  22. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/infer/lineage.py +0 -0
  23. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/infer/matrix.py +0 -0
  24. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/infer/ml.py +0 -0
  25. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/infer/ml_native.py +0 -0
  26. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/infer/parsimony.py +0 -0
  27. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/infer/pipeline.py +0 -0
  28. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/infer/trim.py +0 -0
  29. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/layout/__init__.py +0 -0
  30. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/layout/base.py +0 -0
  31. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/layout/circular.py +0 -0
  32. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/layout/rectangular.py +0 -0
  33. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/layout/unrooted.py +0 -0
  34. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/plot/__init__.py +0 -0
  35. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/plot/backends.py +0 -0
  36. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/plot/elements.py +0 -0
  37. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/plot/palettes.py +0 -0
  38. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/scene.py +0 -0
  39. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon/treeops.py +0 -0
  40. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon.egg-info/SOURCES.txt +0 -0
  41. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon.egg-info/dependency_links.txt +0 -0
  42. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon.egg-info/requires.txt +0 -0
  43. {phytreon-0.2.0 → phytreon-0.2.2}/phytreon.egg-info/top_level.txt +0 -0
  44. {phytreon-0.2.0 → phytreon-0.2.2}/pyproject.toml +0 -0
  45. {phytreon-0.2.0 → phytreon-0.2.2}/setup.cfg +0 -0
  46. {phytreon-0.2.0 → phytreon-0.2.2}/tests/test_expression.py +0 -0
  47. {phytreon-0.2.0 → phytreon-0.2.2}/tests/test_lineage.py +0 -0
  48. {phytreon-0.2.0 → phytreon-0.2.2}/tests/test_pipeline.py +0 -0
  49. {phytreon-0.2.0 → phytreon-0.2.2}/tests/test_protein_ml.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: phytreon
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Phylogenetic trees and publication figures in Python
5
5
  Author-email: Yiheng Du <yiheng.du@slu.edu>
6
6
  License: MIT License
@@ -130,6 +130,7 @@ tr.join_data(meta, on="name") # attach metadata to tips
130
130
  .tip_labels()
131
131
  .support_labels() # node support values
132
132
  ).save("tree.pdf") # PDF/SVG/PNG -> matplotlib
133
+ # .save("tree.svg") # SVG: editable text -> drop into PowerPoint
133
134
  # .save("tree.html") # HTML -> plotly (zoom/hover)
134
135
  ```
135
136
 
@@ -284,7 +285,7 @@ fluently — every method returns the figure, so calls chain.
284
285
 
285
286
  | Method | Draws |
286
287
  |---|---|
287
- | `.branches(color=, size=)` | the tree skeleton (e.g. color by lineage) |
288
+ | `.branches(color=, size=)` | the tree skeleton (`size=` sets line width globally; e.g. color by lineage) |
288
289
  | `.tip_labels()` / `.node_labels()` / `.support_labels()` | text labels |
289
290
  | `.tip_points()` / `.node_points()` / `.points()` | markers (color / size / shape mapping) |
290
291
  | `.highlight(node=)` / `.clade_label(...)` | shade / bracket a clade |
@@ -75,6 +75,7 @@ tr.join_data(meta, on="name") # attach metadata to tips
75
75
  .tip_labels()
76
76
  .support_labels() # node support values
77
77
  ).save("tree.pdf") # PDF/SVG/PNG -> matplotlib
78
+ # .save("tree.svg") # SVG: editable text -> drop into PowerPoint
78
79
  # .save("tree.html") # HTML -> plotly (zoom/hover)
79
80
  ```
80
81
 
@@ -229,7 +230,7 @@ fluently — every method returns the figure, so calls chain.
229
230
 
230
231
  | Method | Draws |
231
232
  |---|---|
232
- | `.branches(color=, size=)` | the tree skeleton (e.g. color by lineage) |
233
+ | `.branches(color=, size=)` | the tree skeleton (`size=` sets line width globally; e.g. color by lineage) |
233
234
  | `.tip_labels()` / `.node_labels()` / `.support_labels()` | text labels |
234
235
  | `.tip_points()` / `.node_points()` / `.points()` | markers (color / size / shape mapping) |
235
236
  | `.highlight(node=)` / `.clade_label(...)` | shade / bracket a clade |
@@ -38,7 +38,7 @@ from .treeops import (
38
38
  robinson_foulds, prune_to_taxa,
39
39
  )
40
40
 
41
- __version__ = "0.2.0"
41
+ __version__ = "0.2.2"
42
42
 
43
43
  __all__ = [
44
44
  "core", "layout", "infer", "comparative", "plot", "datasets", "treeops",
@@ -214,7 +214,15 @@ class TreeFigure:
214
214
 
215
215
  # -- composition methods (each returns self so calls chain) ----------
216
216
  def branches(self, color="black", size: float = 1.0) -> "TreeFigure":
217
+ """Draw (or restyle) the tree skeleton. ``size`` is a single width
218
+ applied to every branch in the tree -- there is no per-branch/data
219
+ variation. A figure only ever has one skeleton layer: calling this
220
+ again (e.g. to override the ``skeleton=True`` default added by
221
+ ``__init__``) replaces it in place rather than drawing a second,
222
+ overlapping set of lines.
223
+ """
217
224
  from .elements import _Branches
225
+ self._elements = [e for e in self._elements if not isinstance(e, _Branches)]
218
226
  return self.add(_Branches(color=color, size=size))
219
227
 
220
228
  def tip_labels(self, **kwargs) -> "TreeFigure":
@@ -313,16 +321,30 @@ class TreeFigure:
313
321
  def save(self, path: str, dpi: int = 300, **kwargs) -> str:
314
322
  """Save to ``path``; backend chosen from the file extension.
315
323
 
316
- ``dpi`` controls raster resolution; remaining kwargs (e.g. ``figsize``)
317
- are forwarded to the renderer.
324
+ Supports ``.png`` / ``.jpg`` (raster), ``.pdf`` / ``.svg`` (vector),
325
+ and ``.html`` (interactive plotly). ``dpi`` controls raster
326
+ resolution; remaining kwargs (e.g. ``figsize``) are forwarded to the
327
+ renderer.
328
+
329
+ SVG output keeps every label as a real ``<text>`` element (rather than
330
+ outlined vector paths), so the figure stays fully editable after
331
+ importing it into PowerPoint (Insert → Picture, then Graphics Format →
332
+ Convert to Shape), Illustrator, Inkscape, etc. -- you can recolour,
333
+ move, and re-type the text.
318
334
  """
319
335
  ext = path.lower().rsplit(".", 1)[-1]
320
336
  if ext == "html":
321
337
  self.draw(backend="plotly", **kwargs).write_html(path)
322
338
  else: # pdf/svg/png/jpg -> matplotlib
339
+ import matplotlib as mpl
323
340
  fig = self.draw(backend="mpl", **kwargs)
324
341
  extra = getattr(fig, "_phytreon_extra_artists", None)
325
- fig.savefig(path, bbox_inches="tight", dpi=dpi, bbox_extra_artists=extra)
342
+ # for SVG, emit editable <text> (not glyph outlines) so labels
343
+ # remain real, re-typeable text in PowerPoint / vector editors
344
+ rc = {"svg.fonttype": "none"} if ext == "svg" else {}
345
+ with mpl.rc_context(rc):
346
+ fig.savefig(path, bbox_inches="tight", dpi=dpi,
347
+ bbox_extra_artists=extra)
326
348
  return path
327
349
 
328
350
  def show(self, backend: str = "mpl", **kwargs):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: phytreon
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Phylogenetic trees and publication figures in Python
5
5
  Author-email: Yiheng Du <yiheng.du@slu.edu>
6
6
  License: MIT License
@@ -130,6 +130,7 @@ tr.join_data(meta, on="name") # attach metadata to tips
130
130
  .tip_labels()
131
131
  .support_labels() # node support values
132
132
  ).save("tree.pdf") # PDF/SVG/PNG -> matplotlib
133
+ # .save("tree.svg") # SVG: editable text -> drop into PowerPoint
133
134
  # .save("tree.html") # HTML -> plotly (zoom/hover)
134
135
  ```
135
136
 
@@ -284,7 +285,7 @@ fluently — every method returns the figure, so calls chain.
284
285
 
285
286
  | Method | Draws |
286
287
  |---|---|
287
- | `.branches(color=, size=)` | the tree skeleton (e.g. color by lineage) |
288
+ | `.branches(color=, size=)` | the tree skeleton (`size=` sets line width globally; e.g. color by lineage) |
288
289
  | `.tip_labels()` / `.node_labels()` / `.support_labels()` | text labels |
289
290
  | `.tip_points()` / `.node_points()` / `.points()` | markers (color / size / shape mapping) |
290
291
  | `.highlight(node=)` / `.clade_label(...)` | shade / bracket a clade |
@@ -65,6 +65,33 @@ def test_layouts_render_both_backends(tmp_path):
65
65
  assert (tmp_path / "circular.html").exists()
66
66
 
67
67
 
68
+ def test_svg_export_keeps_editable_text(tmp_path):
69
+ # SVG must emit real <text> elements (svg.fonttype="none"), not outlined
70
+ # glyph paths -- so labels stay editable after importing into PowerPoint
71
+ # or a vector editor.
72
+ import re
73
+ tr = pt.datasets.primates()
74
+ out = tmp_path / "tree.svg"
75
+ pt.TreeFigure(tr).tip_labels().save(str(out))
76
+ svg = out.read_text(encoding="utf-8")
77
+ texts = re.findall(r"<text[^>]*>(.*?)</text>", svg, re.S)
78
+ assert texts, "SVG has no <text> elements -- labels were outlined to paths"
79
+ assert "Human" in " ".join(texts) # a real tip label is editable text
80
+
81
+
82
+ def test_branches_size_is_a_single_global_override():
83
+ # __init__(skeleton=True) already draws one branches layer; calling
84
+ # .branches(size=...) again must replace it, not stack a second layer
85
+ # on top (a second, differently-sized layer would leave a fringe of the
86
+ # first layer's colour/width showing through).
87
+ from phytreon.plot.elements import _Branches
88
+ tr = pt.datasets.primates()
89
+ p = pt.TreeFigure(tr).branches(size=0.3, color="red")
90
+ layers = [e for e in p._elements if isinstance(e, _Branches)]
91
+ assert len(layers) == 1
92
+ assert layers[0].size == 0.3 and layers[0].color == "red"
93
+
94
+
68
95
  def test_neighbor_joining():
69
96
  names = ["A", "B", "C", "D"]
70
97
  d = [[0, 5, 9, 9], [5, 0, 10, 10], [9, 10, 0, 8], [9, 10, 8, 0]]
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