Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
68625a0
added option to collapse nodes
KatyBrown Aug 30, 2024
65c9a25
working on auto axis
KatyBrown Sep 21, 2024
7d40980
moving functions after drawing the tree to the post_draw module file
KatyBrown Sep 30, 2024
21ad6d4
documenting auto ax function
KatyBrown Sep 30, 2024
4d59b85
troubleshooting reverse branch tip alignment
KatyBrown Sep 30, 2024
86ff600
pep8
KatyBrown Nov 27, 2024
a47c8a6
split functions into draw_tree module file
KatyBrown Nov 27, 2024
6f57d0f
rearranged files and adjusted unit tests to match
KatyBrown Nov 28, 2024
33f8bb1
tried to modularise the code and caused 1000 test errors
KatyBrown Nov 28, 2024
12eda6f
git ignore
KatyBrown Mar 10, 2025
6272915
merged master
KatyBrown Mar 10, 2025
80e00ff
tidying code in draw_tree.py
KatyBrown Apr 9, 2025
aec1e56
debugging kwargs for plot appearance
KatyBrown Apr 10, 2025
18ffb75
working on passing tests for modularised code
KatyBrown May 6, 2025
24bd881
almost managed to fix the axis alignment for reversed trees
KatyBrown May 7, 2025
8c0194c
debugging collapse function
KatyBrown May 7, 2025
59d4d5f
added dots options
KatyBrown May 7, 2025
139d0d2
updated axis limits
KatyBrown Jul 8, 2025
eaf26bf
Merge branch 'main' of github.com:KatyBrown/plot_tree
KatyBrown Jul 11, 2025
6f883a4
Merge branch 'main' into new_release
KatyBrown Jul 11, 2025
2e14f7b
updated test images for refactored code
KatyBrown Jul 11, 2025
f77f933
updated tests, pep8
KatyBrown Jul 11, 2025
43b0e76
pep8
KatyBrown Jul 11, 2025
89b8eee
updated test images for refactored code
KatyBrown Jul 11, 2025
6329144
removed image fragments from tests
KatyBrown Jul 11, 2025
58c16b7
updated tests
KatyBrown Jul 11, 2025
026c495
updated tests
KatyBrown Jul 11, 2025
6e49a9f
Merge branch 'main' of github.com:KatyBrown/plot_tree
KatyBrown Jul 11, 2025
3789875
Merge branch 'main' into new_release
KatyBrown Jul 11, 2025
eac4d7c
debugging github workflow
KatyBrown Jul 11, 2025
ac8477d
updated tests and imports
KatyBrown Jul 11, 2025
8af8261
Merge branch 'new_release' of github.com:KatyBrown/plot_tree into new…
KatyBrown Jul 11, 2025
b984865
debugging tip alignment
KatyBrown Jul 11, 2025
70c8d9a
updated test images
KatyBrown Jul 11, 2025
a3d7f34
removed old compare images function
KatyBrown Jul 11, 2025
08df6c0
added numpy to requirements.txt
KatyBrown Jul 11, 2025
1605a91
Merge branch 'new_release' of github.com:KatyBrown/plot_tree into new…
KatyBrown Jul 11, 2025
d646459
removed python 3.6 as wasn't working with numpy
KatyBrown Jul 11, 2025
1374fe1
Merge branch 'main' of github.com:KatyBrown/plot_tree
KatyBrown Jul 11, 2025
cad73f9
Merge branch 'main' into new_release
KatyBrown Jul 11, 2025
3f803d9
tried to remove need for numpy
KatyBrown Jul 11, 2025
14c4b4d
updated tests to not use numpy
KatyBrown Jul 11, 2025
7404325
updated tests to not use numpy
KatyBrown Jul 11, 2025
66320ff
fixed dpi in tests
KatyBrown Jul 11, 2025
371cc0e
standardised mpl settings in tests
KatyBrown Jul 11, 2025
ececc2f
updated test images again
KatyBrown Jul 11, 2025
5724234
tried to standardise test data
KatyBrown Jul 17, 2025
c1fa220
updated test pickled objects to not use numpy
KatyBrown Jul 17, 2025
e9951ed
still trying to fix numpy error
KatyBrown Jul 17, 2025
317b092
Merge branch 'main' of github.com:KatyBrown/plot_tree
KatyBrown Jul 17, 2025
34e233f
trying to update github workflow
KatyBrown Jul 17, 2025
0bf2ecb
Merge branch 'new_release' of github.com:KatyBrown/plot_tree into new…
KatyBrown Jul 17, 2025
84044cd
updated collapse function
KatyBrown Jul 21, 2025
e447af3
updated test images
KatyBrown Jul 21, 2025
6e2b0e8
added documentation for collapse nodes and auto ax
KatyBrown Jul 21, 2025
08a0101
updated documentation for collase and auto_ax functions
KatyBrown Jul 21, 2025
d5967b5
added new example tree to demonstrate collapse function
KatyBrown Jul 21, 2025
4c68666
updated documentation images
KatyBrown Jul 21, 2025
93c081b
updated docstrings for sphinx
KatyBrown Jul 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ jobs:
run: |
python3 -m pip install --upgrade pip
pip uninstall -y numpy || true
pip install numpy
pip install --no-cache-dir -r requirements.txt
pip install pytest flake8
pip3 install -e .
Expand All @@ -40,8 +41,6 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y xvfb
- name: Install fonts
run: sudo apt-get install -y fonts-dejavu
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
dist/
man/_build/
plot_phylo.egg-info/
plot_phylo/__pycache__/
tests/__pycache__/
.coverage
Binary file added _basic_tree.pickle
Binary file not shown.
Binary file added _big_tree.pickle
Binary file not shown.
File renamed without changes.
1 change: 1 addition & 0 deletions examples/big_tree_collapse.nw
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(Eulemur_coronatus:0.022785,((((((Eulemur_fulvus_albifrons:0.001415,Eulemur_fulvus_sanfordi:0.001102):0.002176,(Eulemur_fulvus_fulvus:0.000606,Eulemur_fulvus_mayottensis:0.001497):0.003124):0.005568,(Eulemur_fulvus_albocollaris:0.003826,Eulemur_fulvus_collaris:0.00483):0.00545):0.008709,Eulemur_rubriventer:0.019121)0.71:0.001645,(Eulemur_fulvus_rufus:0.022882,Eulemur_mongoz:0.010986):0.01093):0.007215,(Eulemur_macaco_flavifrons:0.010375,Eulemur_macaco_macaco:0.007914):0.012571)0.74:0.003619,(((Hapalemur_aureus:0.02834,((((Hapalemur_griseus:0.000184,Hapalemur_griseus_meridionalis:0.002911):0.005525,Hapalemur_griseus_occidentalis:0.001983):0.003136,Hapalemur_griseus_alaotrensis:0.008788)0.9:0.001673,Hapalemur_griseus_griseus:0.002835):0.025442):0.014357,Hapalemur_simus:0.043406)0.98:0.006411,Lemur_catta:0.034858):0.068058):0.1;
Binary file added man/pages/examples/collapse_after.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added man/pages/examples/collapse_before.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions man/pages/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,18 @@ Functions
:members:
:undoc-members:
:show-inheritance:

.. automodule:: plot_phylo.draw_tree
:members:
:undoc-members:
:show-inheritance:

.. automodule:: plot_phylo.amend_tree
:members:
:undoc-members:
:show-inheritance:

.. automodule:: plot_phylo.get_boxes
:members:
:undoc-members:
:show-inheritance:
48 changes: 46 additions & 2 deletions man/pages/parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Detailed descriptions of all parameters are provided below.
* [ypos](#ypos) - y-axis position
* [height](#height) - tree height
* [width](#width) - tree width
* [auto_ax](#auto-ax) - determine axis limits automatically?

*Visualisation options*

Expand All @@ -31,6 +32,7 @@ Detailed descriptions of all parameters are provided below.
* [line_col](#line-col) - set line colour
* [line_width](#line-width) - set line width
* [bold](#bold) - highlight tip labels in bold
* [collapse](#collapse) - collapse clades based on a string

The primate tree used in these examples is from the [10K trees](https://10ktrees.nunn-lab.org/) project and is illustrative only.

Expand Down Expand Up @@ -139,7 +141,7 @@ height_val = 5
width_val = 20

# Run the plot_phylo function with these values
results = plot_phylo.plot_phylo("examples/primates.nw", ax, xpos=xpos_val, ypos=ypos_val, height=height_val, width=width_val, show_axis=True, branch_lengths=False)
results = plot_phylo.plot_phylo("examples/primates.nw", ax, xpos=xpos_val, ypos=ypos_val, height=height_val, width=width_val, show_axis=True, branch_lengths=False, auto_ax=False)

# Annotate these points on the tree using matplotlib functions
# Mark the bottom left corner
Expand Down Expand Up @@ -187,9 +189,14 @@ Desired height of the tree, in axis units. Regardless of the height of the axis,

### `width`
(`float`, Default 10)

Desired width of the tree, in axis units. Default 10.

### `auto-ax`
(`bool`, Default True)

If True, axis limits are determined automatically to best visualise the tree. If False, the user should determine the axis limits directly using matplotlib functionality. Axis limits can still be adjusted later if auto_ax is True.


## Visualisation Options
### `show_axis`
(`bool`, Default False)
Expand Down Expand Up @@ -370,3 +377,40 @@ plt.savefig("examples/bold.png", bbox_inches='tight')
```

![Bold](./examples/bold.png "Bold")

### `collapse`
(`list`, Default `[]`)
### `collapse_names`
(`list`, Default `[]`)

Two parameters - collapse and collapse_names are used to allow monophyletic groups to be collapsed, if all tip labels within that clade contain a specific string.
The parameter `collapse` should be a list of strings to look for e.g.
`['Saimiri', 'Callithrix']`

The parameter `collapse_names` is a list, in the same order, of the new names to give to the
collapsed groups, e.g. `['Saimiri species', 'Callithrix species']`.

With the default settings plus `outgroup='Lemur_catta'`

```
f = plt.figure(figsize=(10, 8))
ax = f.add_subplot()
plot_phylo.plot_phylo("%s/examples/big_tree_collapse.nw" % path, ax, outgroup='Lemur_catta')
plt.savefig("examples/collapse_before.png", dpi=300)
```
![Collapse before](./examples/collapse_before.png "Collapse before")


With `collapse=['Eulemur', 'Hapalemur'], collapse_names=['Hapalemur species', 'Eulemur species'], outgroup='Lemur_catta'`
```
f = plt.figure(figsize=(10, 8))
ax = f.add_subplot()
plot_phylo.plot_phylo("%s/examples/big_tree_collapse.nw" % path, ax,
outgroup='Lemur_catta',
collapse=['Eulemur', 'Hapalemur'],
collapse_names=['Eulemur species', 'Hapalemur species'])
plt.savefig("examples/collapse_after.png", dpi=300)
```

![Collapse after](./examples/collapse_after.png "Collapse after")

228 changes: 228 additions & 0 deletions plot_phylo/amend_tree.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
#!/usr/bin/env python3
try:
from plot_phylo import get_boxes
except ImportError:
import get_boxes


def auto_axis(ax, textobj, xpos, ypos, width, height, depth, scale_bar,
branch_lengths, reverse):
"""
Adjust the axis limits around the tree automatically (rather than
using user definted values).

Parameters
----------
ax : matplotlib.axes._axes.Axes
An open matplotlib ax object
textobj: list of matplotlib.text.Text
List of text objects containing tip labels, used to ensure the
labels are within the limits of the plot
xpos : float
User defined desired position of the root node of the tree on the x
axis of ax, in axis units.
ypos : float
User defined desired position of the bottom of the tree on the y
axis of ax, in axis units.
height : float
User defined desired height of the tree, in axis units. Default 10.
width : float
User defined desired width of the tree, in axis units. Default 10.
depth: tuple(float, float, float)
Total height and width of the original tree in terms of number of
nodes, total branch length, number of tips
scale_bar: bool
User defined - True to draw a scale bar, else False
branch_lengths: bool
User defined - True to use true branch lengths, False to use
fixed branch lengths
reverse: bool
If True, the tree is reversed on the y-axis,
showing the root on the right
hand side. Default False.

Returns
-------
cboxes: dict
Dictionary where keys are tip labels and values are the boundary
boxes of the tip labels on the plot
ax: matplotlib.axes._axes.Axes
A modified open matplotlib ax object
"""
xint = width * 0.01
yint = (height / depth[2])

nboxes = 0
cboxes = 1

# mpl adjusts the positions of the plot elments when the axis limits are
# changed - keep updating until the boxes stop moving
while nboxes != cboxes:
nboxes = get_boxes.get_boxes(ax, textobj)
if not reverse:
xmin = xpos - xint
xmaxes = [nboxes[x]['xmax'] for x in nboxes]
xmax = max(xmaxes) + xint
else:
xmins = [nboxes[x]['xmin'] for x in nboxes]
xmin = min(xmins)
xmax = -xpos + width + xint
ymaxes = [nboxes[y]['ymax'] for y in nboxes]
ymax = max(ymaxes)
ymins = [nboxes[y]['ymin'] for y in nboxes]
ymin = min(ymins)

if scale_bar:
ymin -= yint
ax.set_xlim(min(xmin, xmax)-xint, max(xmin, xmax)+xint)
ax.set_ylim(min(ymin, ymax), max(ymin, ymax))
cboxes = get_boxes.get_boxes(ax, textobj)
ax.set_autoscale_on(False)
return (cboxes, ax)


def draw_scale_bar(ax, width, height, depth, left, bottom,
scale_bar_width=None,
appearance={'font_size': 10}):
'''
Adds a scale bar to the tree - only when branch lengths are specified.

The default length is 0.25 * total tree width, or it can be specified
using scale_bar_width.

Parameters
----------
ax : matplotlib.axes._axes.Axes
An open matplotlib ax objectmatplotlib.axes._axes.Axes
An open matplotlib ax object

height : float
Height of the tree, in axis units.
width : float
Width of the tree, in axis units.
depth: tuple(float, float, float)
Total height and width of the original tree in terms of number of
nodes, total branch length, number of tips
bottom: float
The bottom position of the tree on the y axis
scale_bar_width: float
The desired width of the scale bar, if not specified
tree width * 0.25 is used
appearance: dict
Dictionary of parameters specifying the appearance of the tree.
'''
# depth[1] - total width of the tree in tree units
# width - total width of tree in axis units
# xint - width of one tree unit in axis units
xint = width / depth[1]

# Distance from x axis to start the scale bar
# 10% of total tree width
interx = width * 0.1

# Distance on y axis to extend the bracket ends on the scale bar
# 10% of the height of one node
intery = (height / depth[2]) * 0.1
bottom -= (height / depth[2])
# scale_bar_width = total width of scale bar in axis units
if not scale_bar_width:
scale_bar_width = width * 0.25

# Convert the scale bar width to tree units
scale = scale_bar_width / xint

# Draw the horizontal line
ax.plot([left+interx, left+interx+scale_bar_width],
[bottom, bottom], color='black')

# Bracket the ends of the scale bar with small vertical lines
ax.plot([left+interx, left+interx],
[bottom-intery, bottom+intery], color='black')
ax.plot([left+interx+scale_bar_width, left+interx+scale_bar_width],
[bottom-intery, bottom+intery], color='black')

# Add the scale text
ax.text(left+interx+(scale_bar_width/2), bottom+intery, "%.3f" % scale,
va='bottom', ha='center', fontsize=appearance['font_size']-2)


def reverse_align(ax, ps, reverse):
'''
Realigns the text in the tip labels so that for a standard tree, the
text is right aligned, for a mirrored tree (root on the right), the
text is left aligned. Only used when tip labels are set to be aligned.

This has to happen once the whole plot is drawn as the limits
of the text only exist once the text exists.

Parameters
----------
ax : matplotlib.axes._axes.Axes
An open matplotlib ax object
ps : list
List of lists - ordered as tip labels, tip label text objects,
alignment lines (if aligned). All are in the same order.
reverse: bool
If True, reverse the tree on the y-axis, showing the root on the right
hand side. Default False.

Returns
-------
ps_new : list
List of lists - ordered as tip labels, tip label text objects,
alignment lines (if aligned). All are in the same order. Updated
based on new alignment.
'''
x_extremes = []
ys = []
# Used to determine which x co-ordinates to take depending on the
# tree orientation
if reverse:
indi = 0
else:
indi = 1
for pnam, ptext, pline in ps:
# Get the data units of the box which encloses the text
box = ax.transData.inverted().transform(
ptext.get_window_extent(renderer=ax.figure.canvas.get_renderer()))
# Get the rightmost point of the text box (regular tree)
# or the leftmost (reversed tree)
x_extreme = box[indi][0]
x_extremes.append(x_extreme)

# Store the y axis positions
ys.append(box[1][1])

if not reverse:
# For right alignment, take the rightmost x point
maxi = max(x_extremes)
else:
# For left alignment, take the leftmost x point
maxi = min(x_extremes)
alis = ['left', 'right']

ps_new = []
for i, p in enumerate(ps):
# Move the text to the right position
p[1].set_position([maxi, ys[i]])

# Get the current left end of the dotted alignment line
oldline = p[2][0].get_xdata()[0]

# Left or right align the text
p[1].set_horizontalalignment(alis[indi])

# Get the updated text position limits
pbox = ax.transData.inverted().transform(p[1].get_window_extent(
renderer=ax.figure.canvas.get_renderer()))

# Move the dotted line to hit the new text position
# int(not indi) swaps 0 for 1
p[2][0].set_xdata([pbox[int(not indi)][0], oldline])

# Set the y axis position
p[1].set_verticalalignment('center')

# Store the new values
ps_new.append(p)
return (ps_new)
Loading
Loading