ggtree-py 0.1.0__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.
- ggtree_py-0.1.0/.gitignore +11 -0
- ggtree_py-0.1.0/CHANGELOG.md +14 -0
- ggtree_py-0.1.0/CONDUCT.md +24 -0
- ggtree_py-0.1.0/CONTRIBUTING.md +40 -0
- ggtree_py-0.1.0/LICENSE +164 -0
- ggtree_py-0.1.0/PKG-INFO +198 -0
- ggtree_py-0.1.0/README.md +173 -0
- ggtree_py-0.1.0/docs/annotations_and_compositions.md +147 -0
- ggtree_py-0.1.0/docs/api_reference.md +94 -0
- ggtree_py-0.1.0/docs/getting_started.md +103 -0
- ggtree_py-0.1.0/docs/index.md +26 -0
- ggtree_py-0.1.0/docs/layouts_and_manipulation.md +91 -0
- ggtree_py-0.1.0/docs/r_to_python_migration.md +53 -0
- ggtree_py-0.1.0/examples/run_all_examples.py +352 -0
- ggtree_py-0.1.0/ggtree/__init__.py +569 -0
- ggtree_py-0.1.0/ggtree/aes.py +385 -0
- ggtree_py-0.1.0/ggtree/compose.py +1351 -0
- ggtree_py-0.1.0/ggtree/dataframe.py +1439 -0
- ggtree_py-0.1.0/ggtree/extdata/FluA_H3_AA.fas +684 -0
- ggtree_py-0.1.0/ggtree/extdata/Genotype.txt +77 -0
- ggtree_py-0.1.0/ggtree/extdata/MCC_FluA_H3.tree +165 -0
- ggtree_py-0.1.0/ggtree/extdata/NAG_inHA1.txt +151 -0
- ggtree_py-0.1.0/ggtree/extdata/RAxML_bipartitions.H3 +1 -0
- ggtree_py-0.1.0/ggtree/extdata/ggdensitree_example.tree +12 -0
- ggtree_py-0.1.0/ggtree/extdata/mtCDNA_timetree.nex +27 -0
- ggtree_py-0.1.0/ggtree/extdata/pa.fas +784 -0
- ggtree_py-0.1.0/ggtree/extdata/pa.nwk +1 -0
- ggtree_py-0.1.0/ggtree/extdata/pa_subs.csv +28 -0
- ggtree_py-0.1.0/ggtree/extdata/sample.jplace +14 -0
- ggtree_py-0.1.0/ggtree/extdata/sample.nwk +1 -0
- ggtree_py-0.1.0/ggtree/extdata/sites.txt +82 -0
- ggtree_py-0.1.0/ggtree/geoms.py +1640 -0
- ggtree_py-0.1.0/ggtree/layout.py +1652 -0
- ggtree_py-0.1.0/ggtree/manipulate.py +836 -0
- ggtree_py-0.1.0/ggtree/plot.py +1679 -0
- ggtree_py-0.1.0/ggtree/themes_scales.py +667 -0
- ggtree_py-0.1.0/ggtree/tidytree.py +735 -0
- ggtree_py-0.1.0/ggtree/tree.py +1615 -0
- ggtree_py-0.1.0/pyproject.toml +64 -0
- ggtree_py-0.1.0/tests/test_examples.py +390 -0
- ggtree_py-0.1.0/tests/test_ggtree.py +970 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (2026-11-14)
|
|
4
|
+
|
|
5
|
+
Initial Python release (`ggtree-py`, imported as `ggtree`), ported from Bioconductor `ggtree` (`4.3.2`).
|
|
6
|
+
|
|
7
|
+
### Highlights
|
|
8
|
+
- **Tree parsers & structures (`ggtree.tree`)**: Newick (`read_tree`), BEAST NEXUS (`read_beast`), NHX (`read_nhx`), jplace (`read_jplace`), FASTA (`read_fasta`), `Phylo`, `TreeData`, `MultiPhylo`, `TreeDataList`, `Hclust`/`hclust`, and bundled sample datasets (`system_file`).
|
|
9
|
+
- **Tidy tree operations (`ggtree.tidytree`)**: `MRCA`, `groupOTU`, `groupClade`, `offspring`, `ancestor`, `parent`, `child`, `sibling`, `nodeid`, `nodelab`, `tree_subset`, `td_filter`, `td_mutate`, and `td_unnest`.
|
|
10
|
+
- **12+ tree layouts (`ggtree.layout`)**: `rectangular`, `slanted`, `circular`, `fan`, `inward_circular`, `dendrogram`, `equal_angle`, `daylight` (`unrooted`), `tidy`, `ellipse`, `roundrect`, `radial`, `ape`, and `tree_and_leaf`, plus time-scaled MRSD coordinates (`Date2decimal`, `decimal2Date`).
|
|
11
|
+
- **Grammar-of-graphics geoms & scales (`ggtree.aes`, `ggtree.geoms`, `ggtree.themes_scales`)**: Symbolic expressions (`aes`, `isTip`, `parent`), `geom_tree`, `geom_tiplab`, `geom_tiplab2`, `geom_nodelab`, `geom_point2`, `geom_tippoint`, `geom_nodepoint`, `geom_rootpoint`, `geom_rootedge`, `geom_aline`, `geom_hilight`, `geom_balance`, `geom_cladelab`, `geom_strip`, `geom_taxalink`, `geom_tanglelink`, `geom_range`, `geom_treescale`, `new_scale_fill`, `new_scale_color`, Viridis/Magma/Plasma/Inferno/Cividis/Brewer/gradient scales, and `theme_tree`, `theme_tree2`, `theme_dendrogram`.
|
|
12
|
+
- **Manipulation & composite plots (`ggtree.manipulate`, `ggtree.compose`)**: `collapse`, `expand`, `rotate`, `flip`, `scaleClade`, `viewClade`, `zoomClade`, `open_tree`, `rotate_tree`, `gheatmap`, `msaplot`, `geom_facet`/`facet_plot`, `nodepie`/`nodebar`/`inset`, `ggdensitree`, `as_tanglegram`/`ggdoubletree`, `multiplot`, `plot_list`, and `|`/`/` patchwork composition.
|
|
13
|
+
- **Zero-external-dependency SVG & PNG renderer (`ggtree.plot`)**: Direct vector SVG rendering and PNG export via `ggsave()`, `to_svg()`, and `to_png()` (using `rsvg-convert` when present, with pure-NumPy RGBA fallback).
|
|
14
|
+
- **Verified**: All 49 tests pass (`tests/test_examples.py` + `tests/test_ggtree.py`).
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Contributor Code of Conduct
|
|
2
|
+
|
|
3
|
+
As contributors and maintainers of this project, we pledge to respect all people who
|
|
4
|
+
contribute through reporting issues, posting feature requests, updating documentation,
|
|
5
|
+
submitting pull requests or patches, and other activities.
|
|
6
|
+
|
|
7
|
+
We are committed to making participation in this project a harassment-free experience for
|
|
8
|
+
everyone, regardless of level of experience, gender, gender identity and expression,
|
|
9
|
+
sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
|
|
10
|
+
|
|
11
|
+
Examples of unacceptable behavior by participants include the use of sexual language or
|
|
12
|
+
imagery, derogatory comments or personal attacks, trolling, public or private harassment,
|
|
13
|
+
insults, or other unprofessional conduct.
|
|
14
|
+
|
|
15
|
+
Project maintainers have the right and responsibility to remove, edit, or reject comments,
|
|
16
|
+
commits, code, wiki edits, issues, and other contributions that are not aligned to this
|
|
17
|
+
Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed
|
|
18
|
+
from the project team.
|
|
19
|
+
|
|
20
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
|
|
21
|
+
opening an issue or contacting one or more of the project maintainers.
|
|
22
|
+
|
|
23
|
+
This Code of Conduct is adapted from the Contributor Covenant
|
|
24
|
+
(http://contributor-covenant.org), version 1.0.0.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Contributing to `ggtree-py`
|
|
2
|
+
|
|
3
|
+
Thank you for considering contributing to `ggtree-py`!
|
|
4
|
+
|
|
5
|
+
## Development Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone <repo-url>
|
|
9
|
+
cd ggtree.py
|
|
10
|
+
python3 -m pip install -e ".[dev]"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Only `numpy>=1.22` is required at runtime (Python `>= 3.10`). Installing `rsvg-convert` (from `librsvg`) on your `PATH` enables high-resolution vector-to-PNG rasterization in `ggsave("out.png")`; without it, `ggtree` falls back to its built-in pure-NumPy PNG rasterizer.
|
|
14
|
+
|
|
15
|
+
## Running Tests and Examples
|
|
16
|
+
|
|
17
|
+
Run the full unit test suite (all 49 tests covering ported R manual examples and `testthat` suites):
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
python3 -s -m unittest discover -s tests -v
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Generate all 14 composite example figures (SVG + PNG) in `examples/output/`:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
python3 -s examples/run_all_examples.py
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Building Distributions
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
uv build
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Submitting Pull Requests
|
|
36
|
+
|
|
37
|
+
- Keep core runtime dependencies restricted to the Python standard library and `numpy`.
|
|
38
|
+
- Add or update tests in `tests/test_ggtree.py` or `tests/test_examples.py`.
|
|
39
|
+
- Update the relevant documentation under `docs/` when adding or modifying public APIs.
|
|
40
|
+
- Follow the [Contributor Code of Conduct](CONDUCT.md).
|
ggtree_py-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
The Artistic License 2.0
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2014-2026 Guangchuang Yu (upstream R/Bioconductor ggtree authors)
|
|
4
|
+
|
|
5
|
+
Everyone is permitted to copy and distribute verbatim copies
|
|
6
|
+
of this license document, but changing it is not allowed.
|
|
7
|
+
|
|
8
|
+
Preamble
|
|
9
|
+
|
|
10
|
+
This license establishes the terms under which a given free software
|
|
11
|
+
Package may be copied, modified, distributed, and/or redistributed.
|
|
12
|
+
The intent is that the Copyright Holder maintains some artistic
|
|
13
|
+
control over the development of that Package while still keeping the
|
|
14
|
+
Package available as open source and free software.
|
|
15
|
+
|
|
16
|
+
You are always permitted to make arrangements wholly outside of this
|
|
17
|
+
license directly with the Copyright Holder of a given Package. If the
|
|
18
|
+
terms of this license do not permit the full use that you propose to
|
|
19
|
+
make of the Package, you should contact the Copyright Holder and seek
|
|
20
|
+
a different licensing arrangement.
|
|
21
|
+
|
|
22
|
+
Definitions
|
|
23
|
+
|
|
24
|
+
"Copyright Holder" means the individual(s) or organization(s)
|
|
25
|
+
named in the copyright notice for the entire Package.
|
|
26
|
+
|
|
27
|
+
"Contributor" means any party that has contributed code or other
|
|
28
|
+
material to the Package, in accordance with the Copyright Holder's
|
|
29
|
+
procedures.
|
|
30
|
+
|
|
31
|
+
"You" and "your" means any person who would like to copy,
|
|
32
|
+
distribute, or modify the Package.
|
|
33
|
+
|
|
34
|
+
"Package" means the collection of files distributed by the
|
|
35
|
+
Copyright Holder, and derivatives of that collection and/or
|
|
36
|
+
of those files. A given Package may consist of either the Standard
|
|
37
|
+
Version, or a Modified Version.
|
|
38
|
+
|
|
39
|
+
"Distribute" means providing a copy of the Package or making it
|
|
40
|
+
accessible to anyone else, or in the case of a company or
|
|
41
|
+
organization, to others outside of your company or organization.
|
|
42
|
+
|
|
43
|
+
"Distributor Fee" means any fee that you charge for Distributing
|
|
44
|
+
this Package or providing support for this Package to another
|
|
45
|
+
party. It does not mean branding.
|
|
46
|
+
|
|
47
|
+
"Standard Version" refers to the Package if it has not been
|
|
48
|
+
modified, or has been modified only in ways explicitly requested
|
|
49
|
+
by the Copyright Holder.
|
|
50
|
+
|
|
51
|
+
"Modified Version" means the Package, when it has been changed,
|
|
52
|
+
and such changes were not explicitly requested by the Copyright
|
|
53
|
+
Holder.
|
|
54
|
+
|
|
55
|
+
"Original License" means this Artistic License as Distributed with
|
|
56
|
+
the Standard Version of the Package, in its current version or as
|
|
57
|
+
it may be modified by The Perl Foundation in the future.
|
|
58
|
+
|
|
59
|
+
"Source" form means the source code, documentation source, and
|
|
60
|
+
configuration files for the Package.
|
|
61
|
+
|
|
62
|
+
"Compiled" form means the compiled binary or object code or
|
|
63
|
+
executable, including generated binary files, generated
|
|
64
|
+
computer code, or translated or converted binary files.
|
|
65
|
+
|
|
66
|
+
"Application" means the problem solving program, application, or
|
|
67
|
+
other business function or application to which the Package is
|
|
68
|
+
linked.
|
|
69
|
+
|
|
70
|
+
"Aggregation" means a compilation of a number of separate
|
|
71
|
+
independent programs, not based on the Package, which are
|
|
72
|
+
not specifically written for the purpose of combining the Package
|
|
73
|
+
with the independent programs.
|
|
74
|
+
|
|
75
|
+
"Distribution" means the collection of files that constitute the
|
|
76
|
+
Package as it is distributed by you.
|
|
77
|
+
|
|
78
|
+
"Derivative Work" means any work derived from the Package,
|
|
79
|
+
either through copyright or contract.
|
|
80
|
+
|
|
81
|
+
Permission for Use and Modification Without Distribution
|
|
82
|
+
|
|
83
|
+
(1) You are permitted to use the Standard Version and create and use
|
|
84
|
+
Modified Versions for any purpose without restriction, provided that
|
|
85
|
+
you do not Distribute the Modified Version.
|
|
86
|
+
|
|
87
|
+
Permissions for Redistribution of the Standard Version
|
|
88
|
+
|
|
89
|
+
(2) You may Distribute verbatim copies of the Source form of the
|
|
90
|
+
Standard Version of this Package in any medium without restriction,
|
|
91
|
+
either gratis or for a Distributor Fee, provided that you duplicate
|
|
92
|
+
all of the original copyright notices and associated disclaimers. At
|
|
93
|
+
your discretion, such binary files may be included under the conditions
|
|
94
|
+
that you may Distribute them as provided in (3).
|
|
95
|
+
|
|
96
|
+
Permissions for Redistribution of Modified Versions of the Source
|
|
97
|
+
Form as a Standard Version
|
|
98
|
+
|
|
99
|
+
(3) You may Distribute any Modified Version in Source form under the
|
|
100
|
+
conditions of section (2) above, provided that you include prominent
|
|
101
|
+
notices stating how and when you changed the Package, and provided that
|
|
102
|
+
the Modified Version is clearly distinguished from the Standard Version
|
|
103
|
+
in one of the following ways:
|
|
104
|
+
|
|
105
|
+
(a) make the Modified Version available as the Standard Version,
|
|
106
|
+
with the Modified Version clearly named, so that the User
|
|
107
|
+
can easily find the source of the Modified Version, and the
|
|
108
|
+
Package name and version number are clearly present.
|
|
109
|
+
|
|
110
|
+
(b) make sure that the Modified Version has a different package
|
|
111
|
+
name than the Standard Version.
|
|
112
|
+
|
|
113
|
+
(c) make sure that any non-Standard Version clearly carries a
|
|
114
|
+
different version number or name from the Standard Version.
|
|
115
|
+
|
|
116
|
+
Permissions for Redistribution of Modified Versions of the Source
|
|
117
|
+
Form as a Non-Standard Version
|
|
118
|
+
|
|
119
|
+
(4) You may Distribute any Modified Version in Source form without
|
|
120
|
+
distinguishing it from the Standard Version as required by section (3)
|
|
121
|
+
above, if you distribute the Modified Version exclusively in your own
|
|
122
|
+
organization or company, and do not distribute it to anyone outside your
|
|
123
|
+
organization or company.
|
|
124
|
+
|
|
125
|
+
Permissions for Redistribution of Non-Source Forms of Modified Versions
|
|
126
|
+
|
|
127
|
+
(5) You may Distribute Non-Source Forms of a Modified Version
|
|
128
|
+
provided that you do the following with each Non-Source Form:
|
|
129
|
+
|
|
130
|
+
(a) do at least one of the following:
|
|
131
|
+
|
|
132
|
+
(i) make the Source form of the Modified Version available
|
|
133
|
+
under the Original License, and include instructions on
|
|
134
|
+
how to acquire the Source form from the Copyright Holder;
|
|
135
|
+
|
|
136
|
+
(ii) ensure that the Source form of the Modified Version is
|
|
137
|
+
made available under the Original License as provided
|
|
138
|
+
by the Copyright Holder;
|
|
139
|
+
|
|
140
|
+
(iii) make the Source form available from the same place
|
|
141
|
+
as the Non-Source Form;
|
|
142
|
+
|
|
143
|
+
(b) keep intact all copyright notices and disclaimers;
|
|
144
|
+
|
|
145
|
+
(c) clearly distinguish the Modified Version from the Standard
|
|
146
|
+
Version.
|
|
147
|
+
|
|
148
|
+
Permissions for Aggregation
|
|
149
|
+
|
|
150
|
+
(6) You may Aggregate the Package with other independent programs
|
|
151
|
+
and distribute the Aggregation provided that you do not state or imply
|
|
152
|
+
that the Package is your own, or that the Aggregation is your own.
|
|
153
|
+
|
|
154
|
+
Permissions for Linking
|
|
155
|
+
|
|
156
|
+
(7) You may link the Package with other files to produce an
|
|
157
|
+
Application, and distribute the Application without restriction under
|
|
158
|
+
the terms of this license.
|
|
159
|
+
|
|
160
|
+
Disclaimer of Warranty
|
|
161
|
+
|
|
162
|
+
(8) THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
|
163
|
+
IMPLIED WARRANTIES. INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
|
164
|
+
WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
ggtree_py-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: ggtree-py
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Grammar of Graphics for Phylogenetic Trees in Python — an R-compatible port of Bioconductor ggtree.
|
|
5
|
+
Project-URL: Homepage, https://yulab-smu.top/treedata-book/
|
|
6
|
+
Project-URL: Upstream, https://github.com/YuLab-SMU/ggtree
|
|
7
|
+
Author: Guangchuang Yu, Tommy Tsan-Yuk Lam, Shuangbin Xu
|
|
8
|
+
License-Expression: Artistic-2.0
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: beast,bioinformatics,evolution,ggplot2,ggtree,newick,phylogenetics,phylogeny,tree-visualization
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: numpy>=1.22
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# ggtree-py
|
|
27
|
+
|
|
28
|
+
**Grammar of Graphics for Phylogenetic Trees in Python** — an R-compatible Python port of the Bioconductor [`ggtree`](https://github.com/YuLab-SMU/ggtree) ecosystem (`treeio`, `tidytree`, `treelayout`, `ggtree`).
|
|
29
|
+
|
|
30
|
+
`ggtree-py` provides the `ggtree` Python package (`import ggtree`) with layered `+` plot composition, symbolic `aes(...)` expressions, 12+ phylogenetic tree layouts, evolutionary time-scale mapping (`mrsd`, `revts()`), clade manipulation, heatmaps (`gheatmap`), multiple sequence alignments (`msaplot`), multi-panel facets (`geom_facet`), node pie/bar insets (`nodepie`, `nodebar`, `inset`), tanglegrams (`ggdoubletree`), and built-in vector SVG and PNG rendering with **only `numpy` required at runtime**.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install ggtree-py
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Requirements
|
|
41
|
+
|
|
42
|
+
- **Python** `>= 3.10`
|
|
43
|
+
- **Runtime dependency**: `numpy >= 1.22`
|
|
44
|
+
- **Optional external rasterizer**: [`rsvg-convert`](https://wiki.gnome.org/Projects/LibRsvg) (`librsvg`) on `PATH` for high-resolution rasterization when calling `ggsave("plot.png")` or `plot.to_png()` (falls back automatically to a built-in pure-NumPy RGBA PNG rasterizer when `rsvg-convert` is not installed).
|
|
45
|
+
|
|
46
|
+
### Local Development Install
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
git clone <repo-url>
|
|
50
|
+
cd ggtree.py
|
|
51
|
+
pip install -e ".[dev]"
|
|
52
|
+
python3 -s -m unittest discover -s tests -v
|
|
53
|
+
python3 -s examples/run_all_examples.py
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Quickstart
|
|
59
|
+
|
|
60
|
+
### 1. Annotated Tree (`+` Layers & Clade Highlights)
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
import ggtree as gg
|
|
64
|
+
|
|
65
|
+
tree = gg.read_tree(gg.system_file("extdata", "sample.nwk", package="treeio"))
|
|
66
|
+
mrca_il = gg.MRCA(tree, "I", "L")
|
|
67
|
+
|
|
68
|
+
p = (
|
|
69
|
+
gg.ggtree(tree)
|
|
70
|
+
+ gg.geom_tippoint(color="steelblue", size=2)
|
|
71
|
+
+ gg.geom_nodepoint(gg.aes(subset=~gg.isTip), color="orange", size=2)
|
|
72
|
+
+ gg.geom_text2(gg.aes(subset=~gg.isTip, label=gg.node), hjust=-0.3, size=2.5)
|
|
73
|
+
+ gg.geom_tiplab(align=True, linetype="dotted", offset=0.2)
|
|
74
|
+
+ gg.geom_hilight(node=mrca_il, fill="steelblue", alpha=0.35, to_bottom=True)
|
|
75
|
+
+ gg.geom_cladelab(node=mrca_il, label="italic('Clade IL')", parse=True, offset=2.0)
|
|
76
|
+
+ gg.geom_strip("A", "C", label="Strip AC", offset=2.0, angle="auto")
|
|
77
|
+
+ gg.geom_taxalink("A", "E", color="firebrick")
|
|
78
|
+
+ gg.geom_treescale()
|
|
79
|
+
+ gg.theme_tree2()
|
|
80
|
+
+ gg.ggtitle("Annotated Sample Tree")
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
gg.ggsave("annotated_tree.svg", plot=p, width=9.5, height=6.5)
|
|
84
|
+
gg.ggsave("annotated_tree.png", plot=p, width=9.5, height=6.5, dpi=100)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 2. Time-Scaled BEAST Tree + HPD Intervals + Genotype Heatmap
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from ggtree import *
|
|
91
|
+
|
|
92
|
+
beast_tree = read_beast(system_file("extdata/MCC_FluA_H3.tree", package="ggtree"))
|
|
93
|
+
genotype = read_table(system_file("extdata/Genotype.txt", package="ggtree"), sep="\t")
|
|
94
|
+
|
|
95
|
+
p_beast = (
|
|
96
|
+
ggtree(beast_tree, mrsd="2013-01-01")
|
|
97
|
+
+ geom_range("height_0.95_HPD", color="red", size=1.5, alpha=0.5)
|
|
98
|
+
+ geom_tiplab(size=2)
|
|
99
|
+
+ theme_tree2()
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
p_hm = (
|
|
103
|
+
gheatmap(p_beast, genotype, offset=5, width=0.5, font_size=3, colnames_angle=-45, hjust=0)
|
|
104
|
+
+ ggtitle("FluA H3 BEAST Tree + Genotype Heatmap")
|
|
105
|
+
)
|
|
106
|
+
ggsave("beast_gheatmap.png", plot=p_hm, width=11, height=11, dpi=100)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 3. Multi-Scale Circular Heatmaps (`new_scale_fill`)
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from ggtree import *
|
|
113
|
+
|
|
114
|
+
set_seed(2026)
|
|
115
|
+
tr = read_tree(system_file("extdata", "sample.nwk", package="treeio"))
|
|
116
|
+
df1 = DataFrame(
|
|
117
|
+
{
|
|
118
|
+
"first": ["a", "b", "a", "c", "d", "d", "a", "b", "e", "e", "f", "c", "f"],
|
|
119
|
+
"second": ["z", "z", "z", "z", "y", "y", "y", "y", "x", "x", "x", "a", "a"],
|
|
120
|
+
},
|
|
121
|
+
rownames=tr.tip_label,
|
|
122
|
+
)
|
|
123
|
+
df2 = DataFrame({"M1": rnorm(13).tolist(), "M2": rnorm(13).tolist()}, rownames=tr.tip_label)
|
|
124
|
+
|
|
125
|
+
p1 = gheatmap(ggtree(tr, layout="circular"), df1, offset=0.8, width=0.2) + scale_fill_viridis_d(option="D") + new_scale_fill()
|
|
126
|
+
p2 = gheatmap(p1, df2, offset=12.8, width=0.3) + scale_fill_viridis_c(option="A")
|
|
127
|
+
ggsave("circular_stacked_heatmap.svg", plot=p2, width=9, height=8.5)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 4. Composite Plots, MSA, Facets, and Tanglegrams
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from ggtree import *
|
|
134
|
+
|
|
135
|
+
# Patchwork composition with | and /
|
|
136
|
+
p_patch = (
|
|
137
|
+
(ggtree(tr) + geom_tiplab(size=2.5) + ggtitle("Rectangular"))
|
|
138
|
+
| (ggtree(tr, layout="fan", open_angle=120) + ggtitle("Fan"))
|
|
139
|
+
) / (ggtree(tr, layout="tidy") + geom_tiplab(size=2.5) + ggtitle("Tidy"))
|
|
140
|
+
|
|
141
|
+
# Multiple sequence alignment
|
|
142
|
+
pa_tree = read_tree(system_file("extdata/pa.nwk", package="ggtree"))
|
|
143
|
+
p_msa = msaplot(ggtree(pa_tree) + geom_tiplab(size=2.5), fasta=system_file("extdata/pa.fas", package="ggtree"), window=(1, 60))
|
|
144
|
+
|
|
145
|
+
# Phylogeny + trait panel facet_plot / geom_facet
|
|
146
|
+
trait_df = DataFrame({"id": tr.tip_label, "value": rnorm(len(tr.tip_label)).tolist()})
|
|
147
|
+
p_facet = ggtree(tr) + geom_tiplab() + geom_facet(panel="Trait", data=trait_df, geom=geom_point, mapping=aes(x="value"))
|
|
148
|
+
|
|
149
|
+
# Crossing-minimized tanglegram
|
|
150
|
+
t1 = read_tree(text="(((a:1,b:1):1,c:2):1,(d:1.5,e:1.5):1.5);")
|
|
151
|
+
t2 = read_tree(text="((e:1.5,d:1.5):1.5,(c:2,(b:1,a:1):1):1);")
|
|
152
|
+
p_tangle = ggdoubletree(ggtree(t1) + geom_tiplab(), ggtree(t2) + geom_tiplab(), optimize=True, optimize_side="both")
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## R ↔ Python Operator & Syntax Cheatsheet
|
|
158
|
+
|
|
159
|
+
| R `ggtree` | Python `ggtree` | Notes |
|
|
160
|
+
| --- | --- | --- |
|
|
161
|
+
| `p + layer` | `p + layer` | Immutable copy returning updated `GGTree` |
|
|
162
|
+
| `p %<+% df` | `p % df` (also `<<`, `@`) | Left-joins external `DataFrame` onto tree node data |
|
|
163
|
+
| `p %<% new_tree` | `p % new_tree` (also `<<`, `@`) | Replaces the underlying tree while preserving layers/themes |
|
|
164
|
+
| `aes(subset = !isTip)` | `aes(subset=~isTip)` | Bitwise `~` negates symbolic expressions (`~isTip`, `"!isTip"`) |
|
|
165
|
+
| `read.tree(...)`, `read.beast(...)` | `read_tree(...)`, `read_beast(...)`, or `read.tree(...)`, `read.beast(...)` | Dot-call proxies (`read`, `write`, `system`, `data`, `set_`, `as_`, `is_`) supported |
|
|
166
|
+
| `p1 \| p2`, `p1 / p2` | `p1 \| p2`, `p1 / p2` | Nested patchwork composition returning `PlotList` |
|
|
167
|
+
| `TRUE`, `FALSE`, `NA`, `NULL` | `True`/`TRUE`, `False`/`FALSE`, `None`/`NA`/`NULL` | R constants exported from `ggtree` |
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Documentation
|
|
172
|
+
|
|
173
|
+
Full guides and reference pages live in [`docs/`](docs/index.md):
|
|
174
|
+
|
|
175
|
+
- [Documentation Index](docs/index.md)
|
|
176
|
+
- [Getting Started & Tree Data I/O](docs/getting_started.md)
|
|
177
|
+
- [Layouts & Tree Manipulation](docs/layouts_and_manipulation.md)
|
|
178
|
+
- [Annotations, Heatmaps, Facets & Multi-Tree Compositions](docs/annotations_and_compositions.md)
|
|
179
|
+
- [API Reference](docs/api_reference.md)
|
|
180
|
+
- [R-to-Python Migration Guide](docs/r_to_python_migration.md)
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Citation & Upstream Credit
|
|
185
|
+
|
|
186
|
+
This library is a Python port of **Bioconductor `ggtree`** (`v4.3.2`) originally created by **Guangchuang Yu**, **Tommy Tsan-Yuk Lam**, **Shuangbin Xu**, and contributors ([YuLab-SMU/ggtree](https://github.com/YuLab-SMU/ggtree)).
|
|
187
|
+
|
|
188
|
+
If you use `ggtree` in published research, please cite the original papers:
|
|
189
|
+
|
|
190
|
+
1. S Xu, L Li, X Luo, M Chen, W Tang, L Zhan, Z Dai, TT Lam, Y Guan, **G Yu**. Ggtree: A serialized data object for visualization of a phylogenetic tree and annotation data. ***iMeta***, 2022, 1(4):e56. doi: [10.1002/imt2.56](https://doi.org/10.1002/imt2.56)
|
|
191
|
+
2. **G Yu**. *Data Integration, Manipulation and Visualization of Phylogenetic Trees* (1st edition). ***Chapman and Hall/CRC***, 2022. doi: [10.1201/9781003279242](https://doi.org/10.1201/9781003279242)
|
|
192
|
+
3. **G Yu**. Using ggtree to visualize data on tree-like structures. ***Current Protocols in Bioinformatics***, 2020, 69:e96. doi: [10.1002/cpbi.96](https://doi.org/10.1002/cpbi.96)
|
|
193
|
+
4. **G Yu**, TTY Lam, H Zhu, Y Guan. Two methods for mapping and visualizing associated data on phylogeny using ggtree. ***Molecular Biology and Evolution***, 2018, 35(12):3041–3043. doi: [10.1093/molbev/msy194](https://doi.org/10.1093/molbev/msy194)
|
|
194
|
+
5. **G Yu**, DK Smith, H Zhu, Y Guan, TTY Lam. ggtree: an R package for visualization and annotation of phylogenetic trees with their covariates and other associated data. ***Methods in Ecology and Evolution***, 2017, 8(1):28–36. doi: [10.1111/2041-210X.12628](https://doi.org/10.1111/2041-210X.12628)
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
Artistic License 2.0 — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# ggtree-py
|
|
2
|
+
|
|
3
|
+
**Grammar of Graphics for Phylogenetic Trees in Python** — an R-compatible Python port of the Bioconductor [`ggtree`](https://github.com/YuLab-SMU/ggtree) ecosystem (`treeio`, `tidytree`, `treelayout`, `ggtree`).
|
|
4
|
+
|
|
5
|
+
`ggtree-py` provides the `ggtree` Python package (`import ggtree`) with layered `+` plot composition, symbolic `aes(...)` expressions, 12+ phylogenetic tree layouts, evolutionary time-scale mapping (`mrsd`, `revts()`), clade manipulation, heatmaps (`gheatmap`), multiple sequence alignments (`msaplot`), multi-panel facets (`geom_facet`), node pie/bar insets (`nodepie`, `nodebar`, `inset`), tanglegrams (`ggdoubletree`), and built-in vector SVG and PNG rendering with **only `numpy` required at runtime**.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install ggtree-py
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Requirements
|
|
16
|
+
|
|
17
|
+
- **Python** `>= 3.10`
|
|
18
|
+
- **Runtime dependency**: `numpy >= 1.22`
|
|
19
|
+
- **Optional external rasterizer**: [`rsvg-convert`](https://wiki.gnome.org/Projects/LibRsvg) (`librsvg`) on `PATH` for high-resolution rasterization when calling `ggsave("plot.png")` or `plot.to_png()` (falls back automatically to a built-in pure-NumPy RGBA PNG rasterizer when `rsvg-convert` is not installed).
|
|
20
|
+
|
|
21
|
+
### Local Development Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
git clone <repo-url>
|
|
25
|
+
cd ggtree.py
|
|
26
|
+
pip install -e ".[dev]"
|
|
27
|
+
python3 -s -m unittest discover -s tests -v
|
|
28
|
+
python3 -s examples/run_all_examples.py
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Quickstart
|
|
34
|
+
|
|
35
|
+
### 1. Annotated Tree (`+` Layers & Clade Highlights)
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
import ggtree as gg
|
|
39
|
+
|
|
40
|
+
tree = gg.read_tree(gg.system_file("extdata", "sample.nwk", package="treeio"))
|
|
41
|
+
mrca_il = gg.MRCA(tree, "I", "L")
|
|
42
|
+
|
|
43
|
+
p = (
|
|
44
|
+
gg.ggtree(tree)
|
|
45
|
+
+ gg.geom_tippoint(color="steelblue", size=2)
|
|
46
|
+
+ gg.geom_nodepoint(gg.aes(subset=~gg.isTip), color="orange", size=2)
|
|
47
|
+
+ gg.geom_text2(gg.aes(subset=~gg.isTip, label=gg.node), hjust=-0.3, size=2.5)
|
|
48
|
+
+ gg.geom_tiplab(align=True, linetype="dotted", offset=0.2)
|
|
49
|
+
+ gg.geom_hilight(node=mrca_il, fill="steelblue", alpha=0.35, to_bottom=True)
|
|
50
|
+
+ gg.geom_cladelab(node=mrca_il, label="italic('Clade IL')", parse=True, offset=2.0)
|
|
51
|
+
+ gg.geom_strip("A", "C", label="Strip AC", offset=2.0, angle="auto")
|
|
52
|
+
+ gg.geom_taxalink("A", "E", color="firebrick")
|
|
53
|
+
+ gg.geom_treescale()
|
|
54
|
+
+ gg.theme_tree2()
|
|
55
|
+
+ gg.ggtitle("Annotated Sample Tree")
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
gg.ggsave("annotated_tree.svg", plot=p, width=9.5, height=6.5)
|
|
59
|
+
gg.ggsave("annotated_tree.png", plot=p, width=9.5, height=6.5, dpi=100)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 2. Time-Scaled BEAST Tree + HPD Intervals + Genotype Heatmap
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from ggtree import *
|
|
66
|
+
|
|
67
|
+
beast_tree = read_beast(system_file("extdata/MCC_FluA_H3.tree", package="ggtree"))
|
|
68
|
+
genotype = read_table(system_file("extdata/Genotype.txt", package="ggtree"), sep="\t")
|
|
69
|
+
|
|
70
|
+
p_beast = (
|
|
71
|
+
ggtree(beast_tree, mrsd="2013-01-01")
|
|
72
|
+
+ geom_range("height_0.95_HPD", color="red", size=1.5, alpha=0.5)
|
|
73
|
+
+ geom_tiplab(size=2)
|
|
74
|
+
+ theme_tree2()
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
p_hm = (
|
|
78
|
+
gheatmap(p_beast, genotype, offset=5, width=0.5, font_size=3, colnames_angle=-45, hjust=0)
|
|
79
|
+
+ ggtitle("FluA H3 BEAST Tree + Genotype Heatmap")
|
|
80
|
+
)
|
|
81
|
+
ggsave("beast_gheatmap.png", plot=p_hm, width=11, height=11, dpi=100)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 3. Multi-Scale Circular Heatmaps (`new_scale_fill`)
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from ggtree import *
|
|
88
|
+
|
|
89
|
+
set_seed(2026)
|
|
90
|
+
tr = read_tree(system_file("extdata", "sample.nwk", package="treeio"))
|
|
91
|
+
df1 = DataFrame(
|
|
92
|
+
{
|
|
93
|
+
"first": ["a", "b", "a", "c", "d", "d", "a", "b", "e", "e", "f", "c", "f"],
|
|
94
|
+
"second": ["z", "z", "z", "z", "y", "y", "y", "y", "x", "x", "x", "a", "a"],
|
|
95
|
+
},
|
|
96
|
+
rownames=tr.tip_label,
|
|
97
|
+
)
|
|
98
|
+
df2 = DataFrame({"M1": rnorm(13).tolist(), "M2": rnorm(13).tolist()}, rownames=tr.tip_label)
|
|
99
|
+
|
|
100
|
+
p1 = gheatmap(ggtree(tr, layout="circular"), df1, offset=0.8, width=0.2) + scale_fill_viridis_d(option="D") + new_scale_fill()
|
|
101
|
+
p2 = gheatmap(p1, df2, offset=12.8, width=0.3) + scale_fill_viridis_c(option="A")
|
|
102
|
+
ggsave("circular_stacked_heatmap.svg", plot=p2, width=9, height=8.5)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 4. Composite Plots, MSA, Facets, and Tanglegrams
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from ggtree import *
|
|
109
|
+
|
|
110
|
+
# Patchwork composition with | and /
|
|
111
|
+
p_patch = (
|
|
112
|
+
(ggtree(tr) + geom_tiplab(size=2.5) + ggtitle("Rectangular"))
|
|
113
|
+
| (ggtree(tr, layout="fan", open_angle=120) + ggtitle("Fan"))
|
|
114
|
+
) / (ggtree(tr, layout="tidy") + geom_tiplab(size=2.5) + ggtitle("Tidy"))
|
|
115
|
+
|
|
116
|
+
# Multiple sequence alignment
|
|
117
|
+
pa_tree = read_tree(system_file("extdata/pa.nwk", package="ggtree"))
|
|
118
|
+
p_msa = msaplot(ggtree(pa_tree) + geom_tiplab(size=2.5), fasta=system_file("extdata/pa.fas", package="ggtree"), window=(1, 60))
|
|
119
|
+
|
|
120
|
+
# Phylogeny + trait panel facet_plot / geom_facet
|
|
121
|
+
trait_df = DataFrame({"id": tr.tip_label, "value": rnorm(len(tr.tip_label)).tolist()})
|
|
122
|
+
p_facet = ggtree(tr) + geom_tiplab() + geom_facet(panel="Trait", data=trait_df, geom=geom_point, mapping=aes(x="value"))
|
|
123
|
+
|
|
124
|
+
# Crossing-minimized tanglegram
|
|
125
|
+
t1 = read_tree(text="(((a:1,b:1):1,c:2):1,(d:1.5,e:1.5):1.5);")
|
|
126
|
+
t2 = read_tree(text="((e:1.5,d:1.5):1.5,(c:2,(b:1,a:1):1):1);")
|
|
127
|
+
p_tangle = ggdoubletree(ggtree(t1) + geom_tiplab(), ggtree(t2) + geom_tiplab(), optimize=True, optimize_side="both")
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## R ↔ Python Operator & Syntax Cheatsheet
|
|
133
|
+
|
|
134
|
+
| R `ggtree` | Python `ggtree` | Notes |
|
|
135
|
+
| --- | --- | --- |
|
|
136
|
+
| `p + layer` | `p + layer` | Immutable copy returning updated `GGTree` |
|
|
137
|
+
| `p %<+% df` | `p % df` (also `<<`, `@`) | Left-joins external `DataFrame` onto tree node data |
|
|
138
|
+
| `p %<% new_tree` | `p % new_tree` (also `<<`, `@`) | Replaces the underlying tree while preserving layers/themes |
|
|
139
|
+
| `aes(subset = !isTip)` | `aes(subset=~isTip)` | Bitwise `~` negates symbolic expressions (`~isTip`, `"!isTip"`) |
|
|
140
|
+
| `read.tree(...)`, `read.beast(...)` | `read_tree(...)`, `read_beast(...)`, or `read.tree(...)`, `read.beast(...)` | Dot-call proxies (`read`, `write`, `system`, `data`, `set_`, `as_`, `is_`) supported |
|
|
141
|
+
| `p1 \| p2`, `p1 / p2` | `p1 \| p2`, `p1 / p2` | Nested patchwork composition returning `PlotList` |
|
|
142
|
+
| `TRUE`, `FALSE`, `NA`, `NULL` | `True`/`TRUE`, `False`/`FALSE`, `None`/`NA`/`NULL` | R constants exported from `ggtree` |
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Documentation
|
|
147
|
+
|
|
148
|
+
Full guides and reference pages live in [`docs/`](docs/index.md):
|
|
149
|
+
|
|
150
|
+
- [Documentation Index](docs/index.md)
|
|
151
|
+
- [Getting Started & Tree Data I/O](docs/getting_started.md)
|
|
152
|
+
- [Layouts & Tree Manipulation](docs/layouts_and_manipulation.md)
|
|
153
|
+
- [Annotations, Heatmaps, Facets & Multi-Tree Compositions](docs/annotations_and_compositions.md)
|
|
154
|
+
- [API Reference](docs/api_reference.md)
|
|
155
|
+
- [R-to-Python Migration Guide](docs/r_to_python_migration.md)
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Citation & Upstream Credit
|
|
160
|
+
|
|
161
|
+
This library is a Python port of **Bioconductor `ggtree`** (`v4.3.2`) originally created by **Guangchuang Yu**, **Tommy Tsan-Yuk Lam**, **Shuangbin Xu**, and contributors ([YuLab-SMU/ggtree](https://github.com/YuLab-SMU/ggtree)).
|
|
162
|
+
|
|
163
|
+
If you use `ggtree` in published research, please cite the original papers:
|
|
164
|
+
|
|
165
|
+
1. S Xu, L Li, X Luo, M Chen, W Tang, L Zhan, Z Dai, TT Lam, Y Guan, **G Yu**. Ggtree: A serialized data object for visualization of a phylogenetic tree and annotation data. ***iMeta***, 2022, 1(4):e56. doi: [10.1002/imt2.56](https://doi.org/10.1002/imt2.56)
|
|
166
|
+
2. **G Yu**. *Data Integration, Manipulation and Visualization of Phylogenetic Trees* (1st edition). ***Chapman and Hall/CRC***, 2022. doi: [10.1201/9781003279242](https://doi.org/10.1201/9781003279242)
|
|
167
|
+
3. **G Yu**. Using ggtree to visualize data on tree-like structures. ***Current Protocols in Bioinformatics***, 2020, 69:e96. doi: [10.1002/cpbi.96](https://doi.org/10.1002/cpbi.96)
|
|
168
|
+
4. **G Yu**, TTY Lam, H Zhu, Y Guan. Two methods for mapping and visualizing associated data on phylogeny using ggtree. ***Molecular Biology and Evolution***, 2018, 35(12):3041–3043. doi: [10.1093/molbev/msy194](https://doi.org/10.1093/molbev/msy194)
|
|
169
|
+
5. **G Yu**, DK Smith, H Zhu, Y Guan, TTY Lam. ggtree: an R package for visualization and annotation of phylogenetic trees with their covariates and other associated data. ***Methods in Ecology and Evolution***, 2017, 8(1):28–36. doi: [10.1111/2041-210X.12628](https://doi.org/10.1111/2041-210X.12628)
|
|
170
|
+
|
|
171
|
+
## License
|
|
172
|
+
|
|
173
|
+
Artistic License 2.0 — see [LICENSE](LICENSE).
|