Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add plotting docs #64

Merged
merged 11 commits into from
Oct 14, 2021
4 changes: 3 additions & 1 deletion docs/Project.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
[deps]
AxisArrays = "39de3d68-74b9-583c-8d2d-e117c070f3a9"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
Phylo = "aea672f4-3940-5932-aa44-993d1c3ff149"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
9 changes: 8 additions & 1 deletion docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ using Documenter
using Phylo

makedocs(modules = [Phylo, Phylo.API],
sitename = "Phylo.jl")
sitename = "Phylo.jl",
pages = [
"Home" => "index.md",
"Manual" => Any[
"Plotting" => "man/plotting.md"
],
"API" => "api.md"
])

deploydocs(repo = "github.com/EcoJulia/Phylo.jl.git",
devbranch = "dev",
Expand Down
122 changes: 122 additions & 0 deletions docs/src/man/plotting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Plotting phylogenetic trees

Phylo defines recipes for all `AbstractTree`s, allowing them to be plotted with
[Plots.jl](https://docs.juliaplots.org/latest).

### Keywords

It adds these keywords to the ones initially supported by Plots.jl:
- treetype: choosing `:fan` or `:dendrogram` determines the shape of the tree
- marker_group: applies the `group` keyword to node markers
- line_group: applies the `group` keyword to branch lines
- showtips: `true` (the default) shows the leaf names
- tipfont: a tuple defining the font to use for leaf names (default is `(7,)`),
which sets the font size to 7 - for font definitions see
[Plots annotation fonts](https://docs.juliaplots.org/latest/generated/gr/#gr-ref20)

### Example plots
For this example, we will use the phylogeny of all extant hummingbird species.

Read the tree and plot it using two different `treetype`s. The
default is `:dendrogram`
```@example plotting
using Phylo, Plots
ENV["GKSwstype"]="nul" # hide
mkborregaard marked this conversation as resolved.
Show resolved Hide resolved
default(linecolor = :black, size = (600, 600)) # looks nicer with black lines
hummers = open(parsenewick, Phylo.path("hummingbirds.tree")
plot(hummers, size = (400, 600), showtips = false)
```

For larger trees, the `:fan` treetype may work better
```@example plotting
plot(hummers, treetype = :fan)
```

### Sorting trees for plotting
Many phylogenies look more aesthetically pleasing if the descendants from each
node are sorted in order of their size. This is called `ladderize` in some other
packages.
```@example plotting
sort!(hummmers, rev = true)
plot(hummers, treetype = :fan)
```

### Coloring branches or nodes by a variable
It is common in evolutionary studies to color the branches or node markers with
the value of some variable. Plots already offers the keyword attributes
`marker_z` and `line_z` for these uses, and they also work on Phylo objects.

We can pass either
- a `Vector` with the same number of elements as there are
branches / internal nodes, where the values follow a depthfirst order
(because the tree is plotted in depthfirst order);
- a `Dict` of `node => value`, with the value to be plotted for each node
(skipping nodes not in the Dict).

To demonstrate, let's start by defining a custom function for evolving a trait
on the phylogeny according to Brownian motion, using the utility function
`map_depthfirst`
```@example plotting
evolve(tree) = map_depthfirst((val, node) -> val + randn(), 0., tree, Float64)
trait = evolve(hummers)
plot(hummers, treetype = :fan, line_z = trait, linecolor = :RdYlBu, linewidth = 5, showtips = false)
```

The inbuilt facilities for sampling traits on trees on Phylo returns a
`Node => value` Dict, which can also be passed to `marker_z`
```@example plotting
brownsampler = BrownianTrait(hummers, "Trait")
plot(hummers,
showtips = false, marker_z = rand(brownsampler),
linewidth = 2, markercolor = :RdYlBu, size = (400, 600))
```


We can also use the `map_depthfirst` utility function to highlight the clade
descending from, e.g., Node 248. Here, the recursive function creates a vector
of colors which will all be orange after encountering Node 248 but black before
```@example plotting
clade_color = map_depthfirst((val, node) -> node == "Node 248" ? :orange : val, :black, hummers)
plot(hummers, linecolor = clade_color, showtips = false, linewidth = 2, size = (400, 600))
```

The Plots attributes related to markers (`markersize`, `markershape` etc.) will
put markers on the internal nodes (or on both internal and tip nodes if a longer)
vector is passed). The `series_attributes` keyword is also supported and behaves
the same way
```@example plotting
plot(hummers,
size = (400, 800),
linecolor = :orange, linewidth = 5,
markersize = 10, markercolor = :steelblue, markerstrokecolor = :white,
series_annotations = text.(1:nnodes(hummers), 6, :center, :center, :white)
)
```

The `marker_group` and `line_group` keywords allow plotting discrete values onto
nodes or branches within the phylogeny.

Let's randomly evolve a discrete trait for temperature preference on the tree,
using `rand!` to add the modelled values to the tree's list of node data.
In addition to taking a Vector or a Dict, `marker_group` also accepts the name
of internal node data.


``` @example plotting
# evolve the trait and add it to the tree
@enum TemperatureTrait lowTempPref midTempPref highTempPref
tempsampler = SymmetricDiscreteTrait(hummers, TemperatureTrait, 0.4)
rand!(tempsampler, hummers)

# and plot it
plot(hummers, showtips = false,
marker_group = "TemperatureTrait",
legend = :topleft, msc = :white, treetype = :fan,
c = [:red :blue :green], size = (600, 600))

```@docs
map_depthfirst
sort
sort!
```

1 change: 1 addition & 0 deletions src/Phylo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ export droptips!, keeptips!

# Plot recipes
include("plot.jl")
export map_depthfirst

# Metrics from the tree
include("metrics.jl")
Expand Down
15 changes: 14 additions & 1 deletion src/plot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -290,8 +290,21 @@ function _circle_transform_segments(xs, ys)
retx, rety
end

"""
map_depthfirst(FUN, start, tree, eltype = nothing)

Apply `FUN` to each node in `tree` in depth-first order, and return the result.
`FUN` must take two arguments, `val` and `node`, where `val` is the result of
applying `FUN` to the previous node, and `node` is the current node. `start`
specifies the initial value of `val`, and `eltype` specifies the type of the
return value of `FUN`.

# a function to update a value successively from the root to the tips
### Examples
≡≡≡≡≡≡≡≡≡≡≡
Define a function to evolve a trait on the tree according to Brownian motion

julia> evolve(tree) = map_depthfirst((val, node) -> val + randn(), 0., tree, Float64)
"""
function map_depthfirst(FUN, start, tree, eltype = nothing)
root = first(nodenamefilter(isroot, tree))
eltype === nothing && (eltype = typeof(FUN(start, root)))
Expand Down