diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..700707ce --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/src/LinkTree.jl b/src/LinkTree.jl index f2f7a2aa..3a30cdf7 100644 --- a/src/LinkTree.jl +++ b/src/LinkTree.jl @@ -9,9 +9,9 @@ newempty(::Type{Data}) where {Data} = Data() A branch type that connects LinkNodes in a LinkTree """ -struct LinkBranch{RT, NL, Data, LenUnits} <: AbstractBranch{RT, NL} +mutable struct LinkBranch{RT, NL, Data, LenUnits} <: AbstractBranch{RT, NL} name::Int - inout::Tuple{AbstractNode{RT, NL}, AbstractNode{RT, NL}} + inout::Tuple{<:AbstractNode{RT, NL}, <:AbstractNode{RT, NL}} length::Union{Missing, LenUnits} data::Data end diff --git a/src/Phylo.jl b/src/Phylo.jl index d0a3efb0..74cc6020 100644 --- a/src/Phylo.jl +++ b/src/Phylo.jl @@ -213,6 +213,63 @@ include("metrics.jl") export mrca, nodeheights export distance, distances, heighttoroot, heightstoroot +""" + sort!(::AbstractTree; uselength = [if available], rev = false) + +Sorts the branches descending from each node by total number of descendants +(or branch length is `uselength = true`). This creates a clearer tree for +plotting. The process is also called "ladderizing" the tree. Use `rev = true` +to reverse the sorting order. +""" +function Base.sort!(tree::T; + uselength = all(haslength.(Ref(tree), getbranches(tree))), + rev = false) where + {T <: AbstractTree{OneTree, <:Rooted}} + function loc!(clade::String) + if isleaf(tree, clade) + return uselength ? + zero(nonmissingtype(typeof(getlength(tree, + getinbound(tree, + clade))))) : + 1.0 + end + + sizes = uselength ? + [getlength(tree, child) + + loc!(getnodename(tree, dst(tree, child))) + for child in getoutbounds(tree, clade)] : + map(loc!, getchildren(tree, clade)) + node = getnode(tree, clade) + if T <: LinkTree + node.other .= node.other[sortperm(sizes, rev = rev)] + elseif T <: RecursiveTree + node.conns .= node.conns[sortperm(sizes, rev = rev)] + end + if uselength + return maximum(sizes) + else + return sum(sizes) + 1.0 + end + end + + for root in getroots(tree) + loc!(getnodename(tree, root)) + end + + return tree +end + +""" + sort(::AbstractTree; uselength = [if available], rev = false) + +Copies a tree and sorts its branches. See `sort!` for further details. +""" +function Base.sort(tree::AbstractTree; + uselength = all(haslength.(Ref(tree), getbranches(tree))), + rev = false) + return sort!(deepcopy(tree), uselength = uselength, rev = rev) +end + # Path into package path(path...; dir::String = "test") = joinpath(@__DIR__, "..", dir, path...) diff --git a/src/RecursiveTree.jl b/src/RecursiveTree.jl index d341ea98..1dbc99da 100644 --- a/src/RecursiveTree.jl +++ b/src/RecursiveTree.jl @@ -121,11 +121,7 @@ function RecursiveTree{RT, NL, NodeData, BranchData, BT, LenUnits, TD}(leafinfos end import Phylo.API: _prefernodeobjects, _preferbranchobjects -_prefernodeobjects(::Type{<:RecursiveTree}) = true -_prefernodeobjects(::Type{<:RecursiveNode}) = true _prefernodeobjects(::Type{<:RecursiveElt}) = true -_preferbranchobjects(::Type{<:RecursiveTree}) = true -_preferbranchobjects(::Type{<:RecursiveBranch}) = true _preferbranchobjects(::Type{<:RecursiveElt}) = true import Phylo.API: _validate! diff --git a/src/plot.jl b/src/plot.jl index 44617813..23852417 100644 --- a/src/plot.jl +++ b/src/plot.jl @@ -220,41 +220,6 @@ function _extend(tmp, x) return ret end -""" - sort!(::AbstractTree; rev = false) - -Sorts the branches descending from each node by total number of -descendants. This creates a clearer tree for plotting. The -process is also called "ladderizing" the tree. Use `rev=true` to -reverse the sorting order. -""" -function Base.sort!(tree::T; rev = false) where {T <: AbstractTree} - function loc!(clade::String) - if isleaf(tree, clade) - return 1 - end - - sizes = map(loc!, getchildren(tree, clade)) - node = getnode(tree, clade) - if T <: LinkTree - node.other .= node.other[sortperm(sizes, rev = rev)] - elseif T <: RecursiveTree - node.conns .= node.conns[sortperm(sizes, rev = rev)] - end - return sum(sizes) + 1 - end - - loc!(first(nodenamefilter(isroot, tree))) - return tree -end - -""" - sort(::AbstractTree; rev = false) - -Copies a tree and sorts its branches. See `sort!` for further details. -""" -Base.sort(tree::AbstractTree; rev = false) = sort!(copy(tree)) - function _nodedepths(tree::Phylo.AbstractTree) function finddepths!(clade::String) if !in(clade, keys(depth)) diff --git a/test/test_Phylo.jl b/test/test_Phylo.jl index 9c665821..279b98f3 100644 --- a/test/test_Phylo.jl +++ b/test/test_Phylo.jl @@ -2,6 +2,7 @@ module TestPhylo using Test using Phylo +using Random @testset "Deprecations" begin t = NamedTree() @@ -16,4 +17,18 @@ using Phylo @test_deprecated treenameiter(t) end +@testset "Sort $TreeType" for TreeType in [RootedTree, Phylo.LTD{OneRoot, Float64}] + tree = rand(Nonultrametric{TreeType}(100)) + @test validate!(deepcopy(tree)) + t2 = sort(tree) + leaves = getleaves(t2, inorder) + @test maximum(getheight.(Ref(t2), leaves)) == getheight(t2, leaves[end]) + t3 = sort(tree, uselength = false, rev = true) + leaves = getleaves(t3, inorder) + for b in getbranches(t3) + b.length = one(b.length) + end + @test maximum(getheight.(Ref(t3), leaves)) == getheight(t3, first(leaves)) +end + end