https://github.com/cran/ape
Raw File
Tip revision: bdccfb1717ec562ce2eb0c17f206cf8dd8b34c8c authored by Emmanuel Paradis on 12 October 2004, 00:00:00 UTC
version 1.3
Tip revision: bdccfb1
plot.phylo.R
### plot.phylo.R  (2004-10-19)
###
###                      Plot Phylogenies
###
### Copyright 2004 Emmanuel Paradis <paradis@isem.univ-montp2.fr>
###
### This file is part of the `ape' library for R and related languages.
### It is made available under the terms of the GNU General Public
### License, version 2, or at your option, any later version,
### incorporated herein by reference.
### 
### This program is distributed in the hope that it will be
### useful, but WITHOUT ANY WARRANTY; without even the implied
### warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
### PURPOSE.  See the GNU General Public License for more
### details.
### 
### You should have received a copy of the GNU General Public
### License along with this program; if not, write to the Free
### Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
### MA 02111-1307, USA

plot.phylo <- function(x, type = "phylogram", use.edge.length = TRUE,
                       node.pos = NULL, show.node.label = FALSE,
                       edge.color = NULL, edge.width = NULL, font = 3,
                       cex = par("cex"), adj = NULL, srt = 0, no.margin = FALSE,
                       root.edge = FALSE, label.offset = 0, underscore = FALSE,
                       x.lim = NULL, y.lim = NULL, direction = "rightwards",
                       lab4ut = "horizontal", ...)
{
    type <- match.arg(type, c("phylogram", "cladogram", "unrooted"))
    direction <- match.arg(direction, c("rightwards", "leftwards",
                                        "upwards", "downwards"))
    if (is.null(x$edge.length)) use.edge.length <- FALSE
    tmp <- as.numeric(x$edge)
    nb.tip <- max(tmp)
    nb.node <- -min(tmp)
    if (type == "unrooted" | !use.edge.length) root.edge <- FALSE
    if (type %in% c("phylogram", "cladogram")) {
        if (is.null(node.pos)) {
            if (type == "phylogram") node.pos <- 1
            if (type == "cladogram") node.pos <- if (!use.edge.length) 2 else 1
        }
        yy <- if (node.pos == 1) node.height(x$edge) else node.height.clado(x$edge)
        if (!use.edge.length) {
            xx <- node.depth(x$edge) - 1
            xx <- max(xx) - xx
        } else  xx <- node.depth.edgelength(x$edge, x$edge.length)
        if (root.edge) {
            if (direction == "rightwards") xx <- xx + x$root.edge
            if (direction == "leftwards") xx <- xx <- x$root.edge
            if (direction == "upwards") yy <- yy + x$root.edge
            if (direction == "downwards") yy <- yy - x$root.edge
        }
    } else { # if type == "unrooted"
        XY <- if (use.edge.length) unrooted.xy(nb.tip, nb.node, x$edge, x$edge.length) else unrooted.xy(nb.tip, nb.node, x$edge, rep(1, dim(x$edge)[1]))
        ## rescale so that we have only positive values
        xx <- XY$M[, 1] - min(XY$M[, 1])
        yy <- XY$M[, 2] - min(XY$M[, 2])
    }

    if (type %in% c("phylogram", "cladogram") & direction != "rightwards") {
        if (direction == "leftwards") {
            xx <- -xx
            xx <- xx - min(xx)
        }
        if (direction %in% c("upwards", "downwards")) {
            tmp <- yy
            yy <- xx
            xx <- tmp - min(tmp) + 1
            if (direction == "downwards") {
                yy <- -yy
                yy <- yy - min(yy)
            }
        }
    }
    if (no.margin) par(mai = rep(0, 4))
    if (is.null(x.lim)) {
        if (type %in% c("phylogram", "cladogram")) {
            if (direction %in% c("rightwards", "leftwards")) {
                xs <- 0
                if (direction == "leftwards")
                  x.lim <- max(xx["-1"] + nchar(x$tip.label) * 0.018 * max(xx) * cex)
                else
                  x.lim <- max(xx[as.character(1:nb.tip)] +
                                  nchar(x$tip.label) * 0.018 * max(xx) * cex)
            } else {
                xs <- 1
                x.lim <- nb.tip
            }
        } else x.lim <- max(xx) # if type == "unrooted"
    } else {
        if (type %in% c("phylogram", "cladogram"))
          xs <- if (direction %in% c("rightwards", "leftwards")) 0 else 1
    }
    if (is.null(y.lim)) {
        if (type %in% c("phylogram", "cladogram")) {
            if (direction %in% c("upwards", "downwards")) {
                ys <- 0
                if (direction == "downwards")
                  y.lim <- max(yy["-1"] + nchar(x$tip.label) * 0.018 * max(yy) * cex)
                else y.lim <- max(yy[as.character(1:nb.tip)] +
                                  nchar(x$tip.label) * 0.018 * max(yy) * cex)
            } else {
                ys <- 1
                y.lim <- nb.tip
            }
        } else { # if type == "unrooted"
            y.lim <- max(yy)
            offset <- max(nchar(x$tip.label) * 0.018 * max(yy) * cex)
        }
    } else {
        if (type %in% c("phylogram", "cladogram"))
          ys <- if (direction %in% c("rightwards", "leftwards")) 1 else 0
    }
    if (is.null(edge.color)) edge.color <- rep("black", dim(x$edge)[1]) else {
        names(edge.color) <- x$edge[, 2]
        edge.color <- edge.color[as.character(c(1:nb.tip, -(nb.node:2)))]
    }
    if (is.null(edge.width)) edge.width <- rep(1, dim(x$edge)[1]) else {
        names(edge.width) <- x$edge[, 2]
        edge.width <- edge.width[as.character(c(1:nb.tip, -(nb.node:2)))]
    }
    if (type %in% c("phylogram", "cladogram")) {
        plot(0, type = "n", xlim = c(xs, x.lim), ylim = c(ys, y.lim),
             xlab = "", ylab = "", xaxt = "n", yaxt = "n", bty = "n", ...)
    } else { # if type == "unrooted"
        plot(0, type = "n", xlim = c(0 - offset, x.lim + offset),
             ylim = c(0 - offset, y.lim + offset), xlab = "",
             ylab = "", xaxt = "n", yaxt = "n", bty = "n", ...)
    }
    if (is.null(adj))
      adj <- if (type %in% c("phylogram", "cladogram") & direction == "leftwards") 1 else 0
    if (type %in% c("phylogram", "cladogram")) {
        MAXSTRING <- max(strwidth(x$tip.label, cex = cex))
        if (direction == "rightwards") {
            if (adj == 1) lox <- label.offset + MAXSTRING * 1.05
            if (adj == 0.5) lox <- label.offset + MAXSTRING * .525
            if (adj == 0) lox <- label.offset
            loy <- 0
        }
        if (direction == "leftwards") {
            if (adj == 1) lox <- -label.offset
            if (adj == 0.5) lox <- -label.offset - MAXSTRING * .525
            if (adj == 0) lox <- -label.offset - MAXSTRING * 1.05
            loy <- 0
            xx <- xx + MAXSTRING
        }
        if (direction == "upwards") {
            psr <- par("usr")
            MAXSTRING <- MAXSTRING * 1.09 * (psr[4] - psr[3]) / (psr[2] - psr[1])
            if (adj == 1) loy <- label.offset + MAXSTRING * 1.05
            if (adj == 0.5) loy <- label.offset + MAXSTRING * .525
            if (adj == 0) loy <- label.offset
            lox <- 0
            srt <- 90 + srt
        }
        if (direction == "downwards") {
            psr <- par("usr")
            MAXSTRING <- MAXSTRING * 1.09 * (psr[4] - psr[3]) / (psr[2] - psr[1])
            if (adj == 1) loy <- -label.offset - MAXSTRING * 1.05
            if (adj == 0.5) loy <- -label.offset - MAXSTRING * .525
            if (adj == 0) loy <- -label.offset
            lox <- 0
            yy <- yy + MAXSTRING
            srt <- 270 + srt
        }
    }

    switch(type, "phylogram" = phylogram.plot(x$edge, nb.tip, nb.node, xx, yy,
                                              direction, edge.color, edge.width),
                 "cladogram" = cladogram.plot(x$edge, xx, yy, edge.color, edge.width),
                 "unrooted" = unrooted.plot(x$edge, xx, yy, edge.color, edge.width))
    if (root.edge) segments(0, yy["-1"], x$root.edge, yy["-1"])
    if (!underscore) x$tip.label <- gsub("_", " ", x$tip.label)
    if (type %in% c("phylogram", "cladogram")) {
        text(xx[as.character(1:nb.tip)] + lox, yy[as.character(1:nb.tip)] + loy,
             x$tip.label, adj = adj, font = font, srt = srt, cex = cex)
    } else { # if type == "unrooted"
        if (lab4ut == "horizontal") {
            y.adj <- x.adj <- numeric(nb.tip)
            sel <- abs(XY$axe) > 0.75 * pi
            x.adj[sel] <- -strwidth(x$tip.label)[sel] * 1.05
            sel <- abs(XY$axe) > pi/4 & abs(XY$axe) < 0.75 * pi
            x.adj[sel] <- -strwidth(x$tip.label)[sel] * (2 * abs(XY$axe)[sel] / pi - 0.5)
            sel <- XY$axe > pi / 4 & XY$axe < 0.75 * pi
            y.adj[sel] <- strheight(x$tip.label)[sel] / 2
            sel <- XY$axe < -pi / 4 & XY$axe > -0.75 * pi
            y.adj[sel] <- -strheight(x$tip.label)[sel] * 0.75
            text(xx[as.character(1:nb.tip)] + x.adj, yy[as.character(1:nb.tip)] + y.adj,
                 x$tip.label, adj = c(adj, 0), font = font, srt = srt, cex = cex)
        } else { # if lab4ut == "axial"
            adj <- as.numeric(abs(XY$axe) > pi / 2)
            srt <- 180 * XY$axe / pi
            srt[as.logical(adj)] <- srt[as.logical(adj)] - 180
            for (i in 1:nb.tip) {
                text(xx[as.character(i)], yy[as.character(i)], cex = cex,
                     x$tip.label[i], adj = adj[i], font = font, srt = srt[i])
            }
        }
    }
    if (show.node.label) text(xx[as.character(-(1:nb.node))] + label.offset,
                              yy[as.character(-(1:nb.node))], x$node.label,
                              adj = adj, font = font, srt = srt, cex = cex)
    invisible(list(type = type, use.edge.length = use.edge.length,
                   node.pos = node.pos, show.node.label = show.node.label,
                   edge.color = edge.color, edge.width = edge.width,
                   font = font, cex = cex, adj = adj, srt = srt,
                   no.margin = no.margin, label.offset = label.offset,
                   x.lim = x.lim, y.lim = y.lim, direction = direction))
}

phylogram.plot <- function(edge, nb.tip, nb.node, xx, yy,
                           direction, edge.color, edge.width)
{
    if (direction %in% c("upwards", "downwards")) {
        tmp <- yy
        yy <- xx
        xx <- tmp
    }
    ## un trait vertical ŕ chaque noeud...
    x0v <- xx[1:nb.node]
    y0v <- y1v <- numeric(nb.node)
    for (i in names(x0v)) {
        j <- edge[which(edge[, 1] == i), 2]
        y0v[-as.numeric(i)] <- min(yy[j])
        y1v[-as.numeric(i)] <- max(yy[j])
    }
    names(x0v) <- NULL
    ## ... et un trait horizontal partant de chaque tip et chaque noeud
    ##  vers la racine
    sq <- if (nb.node == 1) 1:nb.tip else c(1:nb.tip, -(nb.node:2))
    x0h <- x1h <- y0h <- numeric(nb.tip + nb.node - 1)
    j <- 1
    for (i in as.character(sq)) {
        y0h[j] <- yy[i]
        x0h[j] <- xx[edge[which(edge[, 2] == i), 1]]
        x1h[j] <- xx[i]
        j <- j + 1
        x1v <- x0v
        y1h <- y0h
    }
    if (direction %in% c("rightwards", "leftwards")) {
        segments(x0v, y0v, x1v, y1v) # draws vertical lines
        segments(x0h, y0h, x1h, y1h, col = edge.color, lwd = edge.width) # draws horizontal lines
    } else {
        segments(y0v, x0v, y1v, x1v) # draws horizontal lines
        segments(y0h, x0h, y1h, x1h, col = edge.color, lwd = edge.width) # draws vertical lines
    }
}

cladogram.plot <- function(edge, xx, yy, edge.color, edge.width)
{
    segments(xx[edge[, 1]], yy[edge[, 1]], xx[edge[, 2]], yy[edge[, 2]],
             col = edge.color, lwd = edge.width)
}
### `cladogram.plot' and `unrooted.plot' are currently identical (2003-11-23)
unrooted.plot <- function(edge, xx, yy, edge.color, edge.width)
{
    segments(xx[edge[, 1]], yy[edge[, 1]], xx[edge[, 2]], yy[edge[, 2]],
             col = edge.color, lwd = edge.width)
}

node.depth.edgelength <- function(x, el)
### This function returns the distance from the root to
### each node and tip with respect to the edge lengths.
### The input is the matrix `edge' of an object of class
### "phylo", and the corresponding vector edge.length.
{
    tmp <- as.numeric(x)
    nb.tip <- max(tmp)
    nb.node <- -min(tmp)
    xx <- as.numeric(rep(NA, nb.tip + nb.node))
    names(xx) <- as.character(c(-(1:nb.node), 1:nb.tip))
    ## xx: vecteur donnant la distance d'un noeud ou tip ŕ partir de la racine
    xx["-1"] <- 0
    for (i in 2:length(xx)) {
        nod <- names(xx[i])
        ind <- which(x[, 2] == nod)
        base <- x[ind, 1]
        xx[i] <- xx[base] + el[ind]
    }
    xx
}

node.depth <- function(x)
### This function returns the depth of nodes and tips given by
### the number of descendants (1 is returned for tips).
### The input is the matrix `edge' of an object of class "phylo".
{
    tmp <- as.numeric(x)
    nb.tip <- max(tmp)
    nb.node <- -min(tmp)
    xx <- as.numeric(rep(NA, nb.tip + nb.node))
    names(xx) <- as.character(c(-(1:nb.node), 1:nb.tip))
    xx[(nb.node + 1):(nb.tip + nb.node)] <- 1
    ## `unused' says if the node or tip has NOT been used to compute
    ## the `xx' value of its ancestor
    unused <- rep(TRUE, nb.tip + nb.node)
    names(unused) <- names(xx)

    while(sum(unused) > 1) {
        term <- names(xx[!is.na(xx) & unused])
        ind <- as.logical(match(x[, 2], term))
        ind[is.na(ind)] <- FALSE
        term.br <- matrix(x[ind], length(term), 2)
        ## extract the nodes with at least 2 branches above
        basal <- names(which(table(term.br[, 1]) >= 2))
        for (nod in basal) {
            pair.ind <- which(x[, 1] == nod)
            pairs <- x[pair.ind, 2]
            ## Here we need to check that all the branches found in the next
            ## few lines just above are `available' for `clustering'; this may
            ## not be the case if other sister-branches have daughter-branches
            ## which are not yet defined, for instance if there is a multichotomy.
            if (all(pairs %in% term)) {
                xx[nod] <- sum(xx[pairs])
                unused[pairs] <- FALSE
            }
        }
    }
    xx
}

node.height <- function(x)
### This function returns the `height' of nodes and tips given by
### the average of the heights of the direct descendants. The tips
### have the heights 1 to the number of tips. This is not intended
### to be normally used directly by the user.
### The input is the matrix `edge' of an object of class "phylo".
{
    tmp <- as.numeric(x)
    nb.tip <- max(tmp)
    nb.node <- -min(tmp)
    ## yy: vecteur donnant l'ordonnée des lignes horizontales partant des tips
    ##     et des noeuds et allant vers la racine
    yy <- as.numeric(rep(NA, nb.tip + nb.node))
    names(yy) <- as.character(c(-(1:nb.node), 1:nb.tip))
    yy[(nb.node + 1):(nb.tip + nb.node)] <- 1:nb.tip
    ## `unused' says if the node or tip has NOT been used to compute
    ## the `yy' value of its ancestor
    unused <- rep(TRUE, nb.tip + nb.node)
    names(unused) <- names(yy)
    
    while(sum(unused) > 1) {
        term <- names(yy[!is.na(yy) & unused])
        ind <- as.logical(match(x[, 2], term))
        ind[is.na(ind)] <- FALSE
        term.br <- matrix(x[ind], length(term), 2)
    
        ## extract the nodes with at least 2 branches above
        basal <- names(which(table(term.br[, 1]) >= 2))
        for (nod in basal) {
            pair.ind <- which(x[, 1] == nod)
            pairs <- x[pair.ind, 2]
            ## Here we need to check that all the branches found in the next
            ## few lines just above are `available' for `clustering'; this may
            ## not be the case if other sister-branches have daughter-branches
            ## which are not yet defined, for instance if there is a multichotomy.
            if (all(pairs %in% term)) {
                yy[nod] <- sum(yy[pairs])/length(yy[pairs])
                unused[pairs] <- FALSE
            }
        }
    }
    yy
}

node.height.clado <- function(x)
### This function returns the `height' of nodes and tips given by
### the average of the heights of the tips. This is
### most suitable for cladogram with no edge lengths. The tips
### have the heights 1 to the number of tips. This is not intended
### to be normally used directly by the user.
### The input is the matrix `edge' of an object of class "phylo".
{
    tmp <- as.numeric(x)
    nb.tip <- max(tmp)
    nb.node <- -min(tmp)
    ## yy: vecteur donnant l'ordonnée des lignes horizontales partant des tips
    ##     et des noeuds et allant vers la racine
    yy <- as.numeric(rep(NA, nb.tip + nb.node))
    names(yy) <- as.character(c(-(1:nb.node), 1:nb.tip))
    N <- yy ## to weigh with the number of tips
    yy[(nb.node + 1):(nb.tip + nb.node)] <- 1:nb.tip
    N[(nb.node + 1):(nb.tip + nb.node)] <- 1
    ## `unused' says if the node or tip has NOT been used to compute
    ## the `yy' value of its ancestor
    unused <- rep(TRUE, nb.tip + nb.node)
    names(unused) <- names(yy)
    
    while(sum(unused) > 1) {
        term <- names(yy[!is.na(yy) & unused])
        ind <- as.logical(match(x[, 2], term))
        ind[is.na(ind)] <- FALSE
        term.br <- matrix(x[ind], length(term), 2)
    
        ## extract the nodes with at least 2 branches above
        basal <- names(which(table(term.br[, 1]) >= 2))
        for (nod in basal) {
            pair.ind <- which(x[, 1] == nod)
            pairs <- x[pair.ind, 2]
            ## Here we need to check that all the branches found in the next
            ## few lines just above are `available' for `clustering'; this may
            ## not be the case if other sister-branches have daughter-branches
            ## which are not yet defined, for instance if there is a multichotomy.
            if (all(pairs %in% term)) {
                N[nod] <- sum(N[pairs])
                yy[nod] <- sum(yy[pairs] * N[pairs]) / N[nod]
                unused[pairs] <- FALSE
            }
        }
    }
    yy
}

unrooted.xy <- function(nb.tip, nb.node, edge, edge.length)
{
    ## `xx' and `yy' are the coordinates of the nodes and tips
    xx <- as.numeric(rep(NA, (nb.tip + nb.node)))
    names(xx) <- as.character(c(-(1:nb.node), 1:nb.tip))
    yy <- xx
    nb.sp <- node.depth(edge)[1:nb.node]
    angle <- as.numeric(rep(NA, nb.node)) # the angle allocated to each node wrt their nb of tips
    names(angle) <- names(nb.sp)
    axis <- angle # the axis of each branch
    axe <- numeric(nb.tip) # the axis of the terminal branches (for export)
    names(axe) <- as.character(1:nb.tip)
    ## start with the root...
    xx["-1"] <- yy["-1"] <- 0
    ind <- which(edge[, 1] == "-1")
    sons <- edge[ind, 2]
    alpha <- 2 * pi * nb.sp[sons] / nb.tip
    alpha[is.na(alpha)] <- 2 * pi / nb.tip # for tips
    start <- -alpha[1] / 2
    for (i in 1:length(sons)) {
        h <- edge.length[ind[i]]
        beta <- start + alpha[i] / 2
        xx[sons[i]] <- h * cos(beta)
        yy[sons[i]] <- h * sin(beta)
        if (as.numeric(sons[i]) < 0) {
            axis[sons[i]] <- beta
            angle[sons[i]] <- alpha[i]
        } else axe[sons[i]] <- beta
        start <- start + alpha[i]
    }
    next.node <- sons[as.numeric(sons) < 0]
    ## ... and go on with the other nodes and the tips
    while (length(next.node)) {
        future.next.node <- NULL
        for (ancestor in next.node) {
            ind <- which(edge[, 1] == ancestor)
            sons <- edge[ind, 2]
            ## angle where to start to draw the lines:
            start <- axis[ancestor] - angle[ancestor] / 2
            for (i in 1:length(sons)) {
                h <- edge.length[ind[i]]
                if (as.numeric(sons[i]) < 0) {
                    angle[sons[i]] <- angle[ancestor] * nb.sp[sons[i]] / nb.sp[ancestor]
                    beta <- axis[sons[i]] <- start + angle[sons[i]] / 2
                    start <- start + angle[sons[i]]
                } else {
                    beta <- start + angle[ancestor] / nb.sp[ancestor] / 2
                    start <- start + angle[ancestor] / nb.sp[ancestor]
                    axe[sons[i]] <- beta
                }
                xx[sons[i]] <- h * cos(beta) + xx[ancestor]
                yy[sons[i]] <- h * sin(beta) + yy[ancestor]
            }
            ## In the following line we avoid tips but a vector of mode character
            ## is returned, hence the evaluation in `while(...)'.
            future.next.node <- c(future.next.node, sons[as.numeric(sons) < 0])
        }
        next.node <- future.next.node
    }
    M <- matrix(c(xx, yy), ncol = 2)
    rownames(M) <- names(xx)
    axe[axe > pi] <- axe[axe > pi] - 2 * pi # insures that returned angles are in [-Pi, +PI]
    list(M = M, axe = axe)
}
back to top