[Rd] Coloring leaves, twigs and labels in plot.dendrogram()

Walton A. Green walton.green at yale.edu
Sun Oct 23 02:12:52 CEST 2005


Core developers,

I couldn't find any simple way to send a vector of colors to apply to each
terminal in plot.dendrogram() or plot.hclust()---I asked R-help about it a
few weeks ago and didn't get any response---so I hacked that functionality
into the plot.dendrgram code (see below for hacked function plus 
examples)....
    
Is there any chance this functionality could be added to the base code? 
Unquestionably it can be done more elegantly than below, but surely it 
would be a useful feature to have....

Or is there an easier way of doing this that I have missed?

Walton Green

*	*	*

my.dend.plot <- function (x, type = c("rectangle", "triangle"),
    center = FALSE, edge.root = is.leaf(x) || !is.null(attr(x,
    "edgetext")), nodePar = NULL, edgePar = list(),
    leaflab = c("perpendicular", "textlike", "none"), dLeaf = NULL,
    xlab = "", ylab = "", xaxt = "n", yaxt = "s", horiz = FALSE,
    frame.plot = FALSE, twig.col = NULL, lab.col = NULL, leaf.col = NULL, 
...){

############################## 80 Characters Wide 
##############################

plotNode <- function (x1, x2, subtree, type, center, leaflab, dLeaf, 
nodePar, 
    edgePar, horiz = FALSE, leafcounter = leafcounter, ...) 
{
	#cat('.') uncomment this for debugging...
    inner <- !is.leaf(subtree) && x1 != x2
    yTop <- attr(subtree, "height")
    bx <- plotNodeLimit(x1, x2, subtree, center)
    xTop <- bx$x
    hasP <- !is.null(nPar <- attr(subtree, "nodePar"))
    if (!hasP) 
        nPar <- nodePar
    if (getOption("verbose")) {
        cat(if (inner) 
            "inner node"
        else "leaf", ":")
        if (!is.null(nPar)) {
            cat(" with node pars\n")
            str(nPar)
        }
        cat(if (inner) 
            paste(" height", formatC(yTop), "; "), "(x1,x2)= (", 
            formatC(x1, wid = 4), ",", formatC(x2, wid = 4), 
            ")", "--> xTop=", formatC(xTop, wid = 8), "\n", sep = "")
    }
    Xtract <- function(nam, L, default, indx) rep(if (nam %in% 
        names(L)) 
        L[[nam]]
    else default, length.out = indx)[indx]
    asTxt <- function(x) if (is.character(x) || is.expression(x) || 
        is.null(x)) 
        x
    else as.character(x)
    i <- if (inner || hasP) 
        1
    else 2
    if (!is.null(nPar)) {
        pch <- Xtract("pch", nPar, default = 1:2, i)
        cex <- Xtract("cex", nPar, default = c(1, 1), i)
        col <- Xtract("col", nPar, default = par("col"), i)
        bg <- Xtract("bg", nPar, default = par("bg"), i)
		if(is.null(leaf.col) || inner){
		    leaf.col <- rep(col, x2)
		}
		if(!(is.null(leaf.col)) && (is.null(lab.col)) && 
(is.null(twig.col))){
		    leafcounter <- leafcounter + 1
		}
        points(if (horiz) 
            cbind(yTop, xTop)
        else cbind(xTop, yTop), pch = pch, bg = bg, col = 
leaf.col[leafcounter - 1], 
            cex = cex)
    }
    if (leaflab == "textlike") 
	p.col <- Xtract("p.col", nPar, default = "white", i)
    lab.cex <- Xtract("lab.cex", nPar, default = c(1, 1), i)
    lab.font <- Xtract("lab.font", nPar, default = par("font"), i)
    if (is.leaf(subtree)) {
        if (leaflab == "perpendicular") {
            if (horiz) {
                X <- yTop + dLeaf * lab.cex
                Y <- xTop
                srt <- 0
                adj <- c(0, 0.5)
            }
            else {
                Y <- yTop - dLeaf * lab.cex
                X <- xTop
                srt <- 90
                adj <- 1
            }
            nodeText <- asTxt(attr(subtree, "label"))
			if(!is.null(lab.col)){
			    lab.col <- lab.col[leafcounter - 1]
				if(is.null(twig.col) && 
is.null(leaf.col)){
				    leafcounter <- leafcounter + 1
				}
            }
			text(X, Y, nodeText, xpd = TRUE, srt = srt, adj = 
adj, 
                cex = lab.cex, col = lab.col, font = lab.font)
        }
    }
    else if (inner) {
        segmentsHV <- function(x0, y0, x1, y1, ...) {
            if (horiz) 
                segments(y0, x0, y1, x1, ...)
            else segments(x0, y0, x1, y1, ...)
        }
        for (k in 1:length(subtree)) {
            child <- subtree[[k]]
            yBot <- attr(child, "height")
            if (getOption("verbose")) 
                cat("ch.", k, "@ h=", yBot, "; ")
            if (is.null(yBot)) 
                yBot <- 0
            xBot <- if (center) 
                mean(bx$limit[k:(k + 1)])
            else bx$limit[k] + .midDend(child)
            hasE <- !is.null(ePar <- attr(child, "edgePar"))
            if (!hasE) 
                ePar <- edgePar
            i <- if (!is.leaf(child) || hasE) 
                1
            else 2
            col <- Xtract("col", ePar, default = par("col"), 
                i)
            lty <- Xtract("lty", ePar, default = par("lty"), 
                i)
            lwd <- Xtract("lwd", ePar, default = par("lwd"), 
                i)
            if (type == "triangle") {
                segmentsHV(xTop, yTop, xBot, yBot)
            }
            else {
			    if(is.null(twig.col)){
                    segmentsHV(xTop, yTop, xBot, yTop)
                    segmentsHV(xBot, yTop, xBot, yBot)
				}else{
				    if(is.leaf(child)){
                        # cat(leafcounter) uncomment this for debugging...
					    segmentsHV(xTop, yTop, xBot, 
yTop)
						segmentsHV(xBot, yTop, 
xBot, yBot, col = twig.col[leafcounter])
						leafcounter <- leafcounter 
+ 1
					}else{
					    segmentsHV(xTop, yTop, xBot, 
yTop)
						segmentsHV(xBot, yTop, 
xBot, yBot)
					}
				}
            }
            vln <- NULL
            if (is.leaf(child) && leaflab == "textlike") {
                nodeText <- asTxt(attr(child, "label"))
                if (getOption("verbose")) 
                  cat("-- with \"label\"", format(nodeText))
                hln <- 0.6 * strwidth(nodeText, cex = lab.cex)/2
                vln <- 1.5 * strheight(nodeText, cex = lab.cex)/2
                rect(xBot - hln, yBot, xBot + hln, yBot + 2 * 
                  vln, col = p.col)
                text(xBot, yBot + vln, nodeText, xpd = TRUE, 
                  cex = lab.cex, col = lab.col, font = lab.font)
            }
            if (!is.null(attr(child, "edgetext"))) {
                edgeText <- asTxt(attr(child, "edgetext"))
                if (getOption("verbose")) 
                  cat("-- with \"edgetext\"", format(edgeText))
                if (!is.null(vln)) {
                  mx <- if (type == "triangle") 
                    (xTop + xBot + ((xTop - xBot)/(yTop - yBot)) * 
                      vln)/2
                  else xBot
                  my <- (yTop + yBot + 2 * vln)/2
                }
                else {
                  mx <- if (type == "triangle") 
                    (xTop + xBot)/2
                  else xBot
                  my <- (yTop + yBot)/2
                }
                p.col <- Xtract("p.col", ePar, default = "white", 
                  i)
                p.border <- Xtract("p.border", ePar, default = par("fg"), 
                  i)
                p.lwd <- Xtract("p.lwd", ePar, default = lwd, 
                  i)
                p.lty <- Xtract("p.lty", ePar, default = lty, 
                  i)
                t.col <- Xtract("t.col", ePar, default = col, 
                  i)
                t.cex <- Xtract("t.cex", ePar, default = 1, i)
                t.font <- Xtract("t.font", ePar, default = par("font"), 
                  i)
                vlm <- strheight(c(edgeText, "h"), cex = t.cex)/2
                hlm <- strwidth(c(edgeText, "m"), cex = t.cex)/2
                hl3 <- c(hlm[1], hlm[1] + hlm[2], hlm[1])
                polygon(mx + c(-hl3, hl3), my + sum(vlm) * c(-1:1, 
                  1:-1), col = p.col, border = p.border, lty = p.lty, 
                  lwd = p.lwd)
                text(mx, my, edgeText, cex = t.cex, col = t.col, 
                  font = t.font)
			}
	            leafcounter <- 1 + plotNode(bx$limit[k], bx$limit[k + 
1], subtree = child, 
                type, center, leaflab, dLeaf, nodePar, edgePar, 
                horiz, leafcounter = leafcounter, ...)
        } # End of loop through k
	}
return(leafcounter - 1)
}

############################## 80 Characters Wide 
##############################

plotNodeLimit <- function (x1, x2, subtree, center) 
{
    inner <- !is.leaf(subtree) && x1 != x2
    if (inner) {
        K <- length(subtree)
        mTop <- .memberDend(subtree)
        limit <- integer(K)
        xx1 <- x1
        for (k in 1:K) {
            m <- .memberDend(subtree[[k]])
            xx1 <- xx1 + (if (center) 
                (x2 - x1) * m/mTop
            else m)
            limit[k] <- xx1
        }
        limit <- c(x1, limit)
    }
    else {
        limit <- c(x1, x2)
    }
    mid <- attr(subtree, "midpoint")
    center <- center || (inner && !is.numeric(mid))
    x <- if (center) 
        mean(c(x1, x2))
    else x1 + (if (inner) 
        mid
    else 0)
    list(x = x, limit = limit)
}

############################## 80 Characters Wide 
##############################

.memberDend <- function (x) 
{
    r <- attr(x, "x.member")
    if (is.null(r)) {
        r <- attr(x, "members")
        if (is.null(r)) 
            r <- 1:1
    }
    r
}

.midDend <- function (x) 
if (is.null(mp <- attr(x, "midpoint"))) 0 else mp

############################## 80 Characters Wide 
##############################
########## Body follows

    type <- match.arg(type)
    leaflab <- match.arg(leaflab)
    hgt <- attr(x, "height")
    if (edge.root && is.logical(edge.root)) 
        edge.root <- 0.0625 * if (is.leaf(x)) 
            1
        else hgt
    mem.x <- .memberDend(x)
    yTop <- hgt + edge.root
    if (center) {
        x1 <- 0.5
        x2 <- mem.x + 0.5
    }
    else {
        x1 <- 1
        x2 <- mem.x
    }
    xlim <- c(x1 - 1/2, x2 + 1/2)
    ylim <- c(0, yTop)
    if (horiz) {
        xl <- xlim
        xlim <- rev(ylim)
        ylim <- xl
        tmp <- xaxt
        xaxt <- yaxt
        yaxt <- tmp
    }
    plot(0, xlim = xlim, ylim = ylim, type = "n", xlab = xlab, 
        ylab = ylab, xaxt = xaxt, yaxt = yaxt, frame.plot = frame.plot, 
        ...)
    if (is.null(dLeaf)) 
        dLeaf <- 0.75 * (if (horiz) 
            strwidth("w")
        else strheight("x"))
    if (edge.root) {
        x0 <- plotNodeLimit(x1, x2, x, center)$x
        if (horiz){
            segments(hgt, x0, yTop, x0)
        }else{
            segments(x0, hgt, x0, yTop)
        }
        if (!is.null(et <- attr(x, "edgetext"))) {
            my <- mean(hgt, yTop)
            if (horiz) 
                text(my, x0, et)
            else text(x0, my, et)
        }
    }
    plotNode(x1, x2, x, type = type, center = center, leaflab = leaflab, 
        dLeaf = dLeaf, nodePar = nodePar, edgePar = edgePar, 
        horiz = horiz, leafcounter = 1, ...)
} # End of function

############################## 80 Characters Wide 
##############################

## Examples
hc <- hclust(dist(USArrests), "ave")
NewEnglandStates <- rep(0, 50)
NewEnglandStates[c(7, 19, 21, 29, 39, 45)] <- 1
my.dend.plot(as.dendrogram(hc, hang = 0.02),
             twig.col = (NewEnglandStates + 1)[hc$order],
			 lab.col = (NewEnglandStates + 1)[hc$order])
my.dend.plot(as.dendrogram(hc), twig.col = (NewEnglandStates + 
1)[hc$order],
             leaf.col = (NewEnglandStates + 1)[hc$order],
			 nodePar = list(pch = c(NA,1)))



More information about the R-devel mailing list