diff --git a/R/SRCGrob.R b/R/SRCGrob.R index 820a5d4..63c5877 100644 --- a/R/SRCGrob.R +++ b/R/SRCGrob.R @@ -34,16 +34,6 @@ SRCGrob <- function( scale.size.2 = NA, scale.padding = 1 ) { - if ('CP' %in% colnames(tree) && !(plotting.direction %in% c('down', 0))) { - warning(paste( - '"plotting.direction" is not yet supported with "CP" polygon column.', - '"plotting.direction" will be ignored.' - )); - } - - if ('CCF' %in% colnames(tree) && !('CP' %in% colnames(tree))) { - tree$CP <- tree$CCF; - } add.node.text <- !is.null(node.text); add.polygons <- !is.null(tree$CP) && !disable.polygons; @@ -137,7 +127,8 @@ SRCGrob <- function( scale.bar.coords = scale.bar.coords, scale.size.1 = scale.size.1, scale.size.2 = scale.size.2, - scale.padding = scale.padding + scale.padding = scale.padding, + plotting.direction = plotting.direction ); out.tree <- gTree( diff --git a/R/add.nodes.R b/R/add.nodes.R index c612de4..6944b1a 100644 --- a/R/add.nodes.R +++ b/R/add.nodes.R @@ -3,7 +3,6 @@ add.node.ellipse <- function( node.radius, label.nodes = NULL, label.cex = NA, - add.normal = FALSE, scale1, ... ) { @@ -101,11 +100,11 @@ add.normal <- function(clone.out, node.radius, label.cex, normal.cex = 1) { vp = vpStack( make.plot.viewport(clone.out, clip = 'off'), viewport( - y = unit(1, 'npc') - unit(node.radius * normal.cex, 'inches'), + y = unit(0, 'native'), x = unit(0, 'native'), height = grobHeight(normal.box), width = grobWidth(normal.box), - just = c('centre', 'bottom') + just = c('centre', 'centre') ) ) ); diff --git a/R/add.text.R b/R/add.text.R index 0ab1d86..df05d38 100644 --- a/R/add.text.R +++ b/R/add.text.R @@ -28,7 +28,7 @@ axis.overlap <- function( if (return.cex & !is.null(overlaps)) { new.cex <- cex; - while (!is.null(overlaps)) { + while (!is.null(overlaps) && new.cex > 0.05) { new.cex <- new.cex - 0.05; overlaps <- axis.overlap(xpos, node.text, line.dist, axis.type, new.cex, panel.width); } @@ -119,9 +119,12 @@ position.node.text <- function( split = FALSE, label.nodes = FALSE, adjust.axis.overlap = TRUE, - cex = cex + cex = cex, + plotting.direction = 'down' ) { + is.horizontal <- !is.numeric(plotting.direction) && plotting.direction %in% c('left', 'right'); + text.grob.list <- vector('list', length(unlist(node.list))); orig.cex <- cex; idx <- 1; @@ -191,6 +194,10 @@ position.node.text <- function( (!label.nodes && (label.bottom + str.heightsum) > (tree.max.adjusted$y0[s] + node.radius * 0.5)) ) { cex <- cex - 0.05; + if (cex < 0.01) { + cex <- 0.01; + break; + } } } @@ -209,10 +216,9 @@ position.node.text <- function( vjust <- 'center'; } else { ypos <- label.bottom + (g - 1) * spacing + heights - spacing; - #back computing the x position based on the intercept and the slope xpos <- ifelse( - is.infinite(slope), + is.infinite(slope) || is.horizontal, yes = tree.max.adjusted$x1[s], no = (ypos - intercept) / slope ); @@ -235,14 +241,24 @@ position.node.text <- function( no = sum(str.heights.left[c(1:(g - 1))]) ); - ypos <- label.bottom + (g - 1) * spacing + heights - spacing; - text.grob.list[[idx]] <- textGrob( - node.list[[s]][g], - x = unit(xpos - xline.dist, 'inches'), - y = unit(ypos,'inches'), - just = c('right', 'bottom'), - gp = gpar(col = node.text.col[[s]][g], cex = cex) - ); + if (is.horizontal) { + text.grob.list[[idx]] <- textGrob( + node.list[[s]][g], + x = unit(ypos, 'inches'), + y = unit(xpos - xline.dist, 'inches'), + just = c('right', 'top'), + gp = gpar(col = node.text.col[[s]][g], cex = cex) + ); + } else { + ypos <- label.bottom + (g - 1) * spacing + heights - spacing; + text.grob.list[[idx]] <- textGrob( + node.list[[s]][g], + x = unit(xpos - xline.dist, 'inches'), + y = unit(ypos,'inches'), + just = c('right', 'bottom'), + gp = gpar(col = node.text.col[[s]][g], cex = cex) + ); + } } else { offset.left <- ceiling(length(node.text.col[[s]]) / 2); heights <- ifelse(( @@ -251,15 +267,24 @@ position.node.text <- function( no = sum(str.heights.right[c(1:(g - offset.left - 1))]) ); - ypos <- label.bottom + (g - offset.left - 1) * spacing + heights - spacing; - - text.grob.list[[idx]] <- textGrob( - node.list[[s]][g], - x = unit(xpos + xline.dist, 'inches'), - y = unit(ypos, 'inches'), - just = c('left', 'bottom'), - gp = gpar(col = node.text.col[[s]][g], cex = cex) - ); + if (is.horizontal) { + text.grob.list[[idx]] <- textGrob( + node.list[[s]][g], + x = unit(xpos, 'inches'), + y = unit(ypos + xline.dist, 'inches'), + just = c('right', 'bottom'), + gp = gpar(col = node.text.col[[s]][g], cex = cex) + ); + } else { + ypos <- label.bottom + (g - offset.left - 1) * spacing + heights - spacing; + text.grob.list[[idx]] <- textGrob( + node.list[[s]][g], + x = unit(xpos + xline.dist, 'inches'), + y = unit(ypos, 'inches'), + just = c('left', 'bottom'), + gp = gpar(col = node.text.col[[s]][g], cex = cex) + ); + } } } else if (alternating) { # Alternate between placing the text to the left and to the right of the node @@ -271,15 +296,25 @@ position.node.text <- function( xline.dist.adj <- xline.dist; } - text.grob.list[[idx]] <- textGrob( - node.list[[s]][g], - x = unit(xpos + xline.dist.adj, 'inches'), - y = unit(ypos, 'inches'), - just = just, - gp = gpar(col = node.text.col[[s]][g], cex = cex) - ); + if (is.horizontal) { + text.grob.list[[idx]] <- textGrob( + node.list[[s]][g], + x = unit(xpos, 'inches'), + y = unit(ypos + xline.dist.adj, 'inches'), + just = c('center', ifelse(xline.dist.adj > 0, 'bottom', 'top')), + gp = gpar(col = node.text.col[[s]][g], cex = cex) + ); + } else { + text.grob.list[[idx]] <- textGrob( + node.list[[s]][g], + x = unit(xpos + xline.dist.adj, 'inches'), + y = unit(ypos, 'inches'), + just = just, + gp = gpar(col = node.text.col[[s]][g], cex = cex) + ); + } - if (adjust.axis.overlap) { + if (adjust.axis.overlap && !is.horizontal) { overlaps.axis <- axis.overlap( xpos, node.list[[s]][g], xline.dist.adj, @@ -304,7 +339,8 @@ position.node.text <- function( node.radius = node.radius, alternating = alternating, split = split, - label.nodes = label.nodes + label.nodes = label.nodes, + plotting.direction = plotting.direction ); return(text.grob.list); @@ -418,7 +454,7 @@ position.node.text <- function( } } - if (adjust.axis.overlap) { + if (adjust.axis.overlap && !is.horizontal) { overlaps.axis <- axis.overlap( xpos, node.list[[s]][g], @@ -428,8 +464,10 @@ position.node.text <- function( return.cex = TRUE ); - # Shrink the text if they overlap - if (!is.null(overlaps.axis)) { + # Shrink the text if they overlap; only recurse when + # axis.overlap found a strictly smaller cex, otherwise + # we would loop infinitely at the minimum cex value. + if (!is.null(overlaps.axis) && overlaps.axis < cex) { text.grob.list <- position.node.text( tree.max.adjusted = tree.max.adjusted, node.list = node.list, @@ -444,7 +482,8 @@ position.node.text <- function( node.radius = node.radius, alternating = alternating, split = split, - label.nodes = label.nodes + label.nodes = label.nodes, + plotting.direction = plotting.direction ); return(text.grob.list); @@ -452,17 +491,33 @@ position.node.text <- function( } } - text.grob.list[[idx]] <- textGrob( - node.list[[s]][g], - x = unit(xpos + xline.dist, 'inches'), - y = unit(ypos, 'inches'), - just = c(hjust, vjust), - gp = gpar( - col = node.text.col[[s]][g], - fontface = node.text.fontface[[s]][g], - cex = cex - ) - ); + if (is.horizontal) { + offset.x <- ceiling(length(node.text.col[[s]]) / 2); + xpos <- tree.max.adjusted$x1[s] - ((tree.max.adjusted$x1[s] - tree.max.adjusted$x0[s]) * 0.5); + text.grob.list[[idx]] <- textGrob( + node.list[[s]][g], + x = unit(xpos, 'inches'), + y = unit(ypos + xline.dist, 'inches'), + just = c('center', ifelse(xline.dist > 0, 'bottom', 'top')), + gp = gpar( + col = node.text.col[[s]][g], + fontface = node.text.fontface[[s]][g], + cex = cex + ) + ); + } else { + text.grob.list[[idx]] <- textGrob( + node.list[[s]][g], + x = unit(xpos + xline.dist, 'inches'), + y = unit(ypos, 'inches'), + just = c(hjust, vjust), + gp = gpar( + col = node.text.col[[s]][g], + fontface = node.text.fontface[[s]][g], + cex = cex + ) + ); + } } idx <- idx + 1; @@ -490,13 +545,13 @@ add.text2 <- function( node.radius = NULL, alternating = TRUE, split = TRUE, - clone.out = NULL + clone.out = NULL, + plotting.direction = 'down' ) { # Radius in native units node.radius <- node.radius / scale; node.text <- node.text[node.text$node %in% tree$tip, ]; - node.list <- vector('list', nrow(tree)); node.text.col <- vector('list', nrow(tree)); node.text.fontface <- vector('list', nrow(tree)); @@ -591,7 +646,6 @@ add.text2 <- function( tree.max.adjusted$slope <- (tree.max.adjusted$y1 - tree.max.adjusted$y0) / (tree.max.adjusted$x1 - tree.max.adjusted$x0); tree.max.adjusted$intercept <- tree.max.adjusted$y1 - tree.max.adjusted$slope * tree.max.adjusted$x1; - text.grob.list <- position.node.text( tree.max.adjusted = tree.max.adjusted, node.list = node.list, @@ -606,7 +660,8 @@ add.text2 <- function( node.radius = node.radius, alternating = alternating, split = split, - label.nodes = label.nodes + label.nodes = label.nodes, + plotting.direction = plotting.direction ); text.grob.gList <- do.call(gList, text.grob.list); diff --git a/R/calculate.clone.polygons.R b/R/calculate.clone.polygons.R index 79f4d6f..d734109 100644 --- a/R/calculate.clone.polygons.R +++ b/R/calculate.clone.polygons.R @@ -34,7 +34,7 @@ make.polygon <- function( vaf <- wid; beta <- len / beta.in; - y1 <- max(y0 + beta, y0 + 1); + y1 <- y0 + beta #max(y0 + beta, y0 + 1); yy <- seq(y0, y1, length.out = 100); params.d <- c(-0.7310133, sig.shape); diff --git a/R/make.clone.tree.grobs.R b/R/make.clone.tree.grobs.R index c93120c..aca57fa 100644 --- a/R/make.clone.tree.grobs.R +++ b/R/make.clone.tree.grobs.R @@ -41,6 +41,7 @@ make.clone.tree.grobs <- function( scale.size.1, scale.size.2, scale.padding, + plotting.direction = 'down', ... ) { #initializing dataframe for subclones @@ -110,12 +111,42 @@ make.clone.tree.grobs <- function( ); clone.out$no.ccf <- no.ccf; + + # Rotate node positions and branch angles for fish plots (with CP data) when start.angle is non-zero + # This ensures branches and nodes rotate along with the polygons + if (!no.ccf && start.angle != 0) { + rotated.nodes <- rotate.coords( + x = clone.out$v$x, + y = clone.out$v$y, + rotate.by = start.angle, + x.origin = 0, + y.origin = 0 + ); + clone.out$v$x <- rotated.nodes$x; + clone.out$v$y <- rotated.nodes$y; + + clone.out$tree$angle <- clone.out$tree$angle + start.angle; + + for (j in seq_along(clone.out$clones)) { + rot.poly <- rotate.coords( + x = clone.out$clones[[j]]$x, + y = clone.out$clones[[j]]$y, + rotate.by = start.angle, + x.origin = 0, + y.origin = 0 + ); + clone.out$clones[[j]]$x <- rot.poly$x; + clone.out$clones[[j]]$y <- rot.poly$y; + } + } + plot.size <- calculate.main.plot.size( clone.out, scale1, wid, min.width, - node.radius + node.radius, + start.angle = start.angle ); if (!no.ccf) { @@ -159,7 +190,8 @@ make.clone.tree.grobs <- function( no.ccf = no.ccf, xaxis.label = xaxis.label, yaxis1.label = yaxis1.label, - yaxis2.label = yaxis2.label + yaxis2.label = yaxis2.label, + plotting.direction = plotting.direction ); if (scale.bar) { @@ -200,7 +232,8 @@ make.clone.tree.grobs <- function( node.radius = node.radius, scale = scale1, clone.out = clone.out, - alternating = FALSE + alternating = FALSE, + plotting.direction = plotting.direction ); clone.out$grobs <- c(clone.out$grobs, list(node.text.grobs)); diff --git a/R/set.up.plot.area.R b/R/set.up.plot.area.R index 2d59c24..2973a25 100644 --- a/R/set.up.plot.area.R +++ b/R/set.up.plot.area.R @@ -12,49 +12,46 @@ calculate.main.plot.size <- function( scale1, wid, min.width, - node.radius + node.radius, + start.angle = 0 ) { padding <- 2 * node.radius / scale1; - ymax <- clone.out$v$y[which.max(abs(clone.out$v$y))]; - ymax <- ymax + (- sign(ymax) * padding); - height <- ymax * scale1; - - if (is.null(min.width)) { - xmax <- wid; - width <- wid * scale1 + 4 * node.radius; - xlims <- c( - -(xmax / 2) - padding, - xmax / 2 + padding - ); - } else { - xmin <- min(c(clone.out$v$x)); - xmax <- max(c(clone.out$v$x)); - xlims <- c(xmin, xmax); - - width <- (max(xlims) - min(xlims)) * scale1; - diff <- min.width - width; - if (diff > 0) { - xmin <- xmin - 0.5 * diff / scale1 - xmax <- xmax + 0.5 * diff / scale1; - xlims <- c( - xmin - padding, - xmax + padding - ); - width <- (max(xlims) - min(xlims)) * scale1; - } + all.x <- clone.out$v$x; + all.y <- clone.out$v$y; + if (!is.null(clone.out$clones) && length(clone.out$clones) > 0) { + all.x <- c(all.x, unlist(lapply(clone.out$clones, function(cl) cl$x))); + all.y <- c(all.y, unlist(lapply(clone.out$clones, function(cl) cl$y))); } + xmin <- min(all.x); + xmax <- max(all.x); + ymin <- min(all.y); + ymax <- max(all.y); + + # Guard against degenerate scales (e.g. all nodes at x=0 when polygons are disabled) + if (xmax == xmin) { + xmin <- xmin - 0.5; + xmax <- xmax + 0.5; + } + + xlims <- c(xmin, xmax); + ylims <- c(ymax, ymin); + + width <- (xmax - xmin) * scale1; + height <- (ymax - ymin) * scale1; + clone.out$height <- height; clone.out$width <- width; clone.out$xlims <- xlims; - clone.out$ymax <- ymax; + clone.out$ylims <- ylims; + clone.out$ymax <- ymax; - clone.out$vp <- make.plot.viewport( - clone.out, - clip = if (clone.out$no.ccf) 'off' else 'on' - ); + clone.out$vp <- make.plot.viewport( + clone.out, + clip = 'off' #if (clone.out$no.ccf) 'off' else 'on' + ); } make.plot.viewport <- function( @@ -64,96 +61,88 @@ make.plot.viewport <- function( y = 0.5 ) { - vp <- viewport( - y = y, - height = unit(clone.out$height, 'inches'), - width = unit(clone.out$width, 'inches'), - name = 'plot.vp', - xscale = clone.out$xlims, - yscale = c(clone.out$ymax, 0), - just = just, - gp = gpar(fill = 'pink'), - clip = clip - ); - - return(vp); + vp <- viewport( + y = y, + height = unit(clone.out$height, 'inches'), + width = unit(clone.out$width, 'inches'), + name = 'plot.vp', + xscale = clone.out$xlims, + yscale = clone.out$ylims, + just = just, + gp = gpar(fill = 'pink'), + clip = clip + ); + + return(vp); } extend.axis <- function(axisGrob, limits, type) { - arg.list <- list(getGrob(axisGrob, 'major'), limits); - names(arg.list) <- c('grob', type); - axisGrob <- setGrob(axisGrob, 'major', do.call(editGrob, arg.list)); + arg.list <- list(getGrob(axisGrob, 'major'), limits); + names(arg.list) <- c('grob', type); + axisGrob <- setGrob(axisGrob, 'major', do.call(editGrob, arg.list)); - return(axisGrob); + return(axisGrob); } add.axis.label <- function(axisGrob, axis.label, axis.position, axis.label.cex, vp) { - axis.cex <- axisGrob$gp$cex; + pushViewport(vp); + + label.grob <- getGrob(axisGrob, 'labels'); + gap.mm <- convertX(unit(1.5, 'lines'), 'mm', valueOnly = TRUE); + if (axis.position == 'bottom') { - d <- 'y'; - just <- c('centre', 'top'); - rot <- 0; - x <- unit(0.5, 'npc'); - - y <- unit( - convertY(getGrob(axisGrob, 'labels')$y, 'mm', valueOnly = TRUE) - - convertY(unit(1.2, 'lines') * axis.cex, 'mm', valueOnly = TRUE), - 'mm' - ); - } else { - pushViewport(vp); - - tick.length <- unit( - diff(c( - as.numeric(getGrob(axisGrob, 'ticks')$x0), - as.numeric(getGrob(axisGrob, 'ticks')$x1) - )), - 'lines' - ); - - y <- convertY( - unit(max(as.numeric(getGrob(axisGrob, 'major')$y)) * 0.5, 'native'), - 'inches' - ); - - if (axis.position == 'left') { - d <- 'x'; - just <- c('centre', 'centre'); - rot <- 90; - sign <- -1; - } else if (axis.position == 'right') { - d <- 'x'; - just <- c('left', 'centre'); - x <- (getGrob(axisGrob, 'labels')$x + tick.length) * 1.5; - rot <- 270; - sign <- 1; - } - - x <- unit( - sign * ( - convertX(grobWidth(getGrob(axisGrob, 'labels')), 'mm', valueOnly = TRUE) * axis.cex + - convertX(unit(1, 'lines') * axis.cex, 'mm', valueOnly = TRUE) - ) + - convertX(getGrob(axisGrob, 'labels')$x, 'mm', valueOnly = TRUE), 'mm'); - } - - axis.lab <- textGrob( - name = 'axis.label', - axis.label, - gp = gpar(cex = axis.label.cex), - vjust = 1, - x = x, - rot = rot, - y = y - ); - - axis.gTree <- gTree( - name = paste0('axis.', axis.position), - children = gList(axis.lab, axisGrob), - vp = vp - ); - - return(axis.gTree); + just <- c('centre', 'top'); + rot <- 0; + x <- unit(0.5, 'npc'); + label.y.mm <- convertY(label.grob$y, 'mm', valueOnly = TRUE); + label.h.mm <- convertY(grobHeight(label.grob), 'mm', valueOnly = TRUE); + # bottom axis: tick labels extend downward from label.y.mm; + # outer (bottom) edge = label.y.mm - label.h.mm; step down by gap + y <- unit(label.y.mm - label.h.mm - gap.mm, 'mm'); + } else if (axis.position == 'top') { + just <- c('centre', 'bottom'); + rot <- 0; + x <- unit(0.5, 'npc'); + label.y.mm <- convertY(label.grob$y, 'mm', valueOnly = TRUE); + label.h.mm <- convertY(grobHeight(label.grob), 'mm', valueOnly = TRUE); + # top axis: tick labels extend upward from label.y.mm; + # outer (top) edge = label.y.mm + label.h.mm; step up by gap + y <- unit(label.y.mm + label.h.mm + gap.mm, 'mm'); + } else { + label.x.mm <- convertX(label.grob$x, 'mm', valueOnly = TRUE); + label.w.mm <- convertX(grobWidth(label.grob), 'mm', valueOnly = TRUE); + y <- unit(mean(as.numeric(getGrob(axisGrob, 'major')$y)), 'native'); + + if (axis.position == 'left') { + just <- c('right', 'centre'); + rot <- 90; + x <- unit(label.x.mm - label.w.mm - gap.mm, 'mm'); + } else if (axis.position == 'right') { + just <- c('left', 'centre'); + rot <- 270; + x <- unit(label.x.mm + label.w.mm + gap.mm, 'mm'); + } + } + + popViewport(); + + axis.lab <- textGrob( + name = 'axis.label', + axis.label, + gp = gpar(cex = axis.label.cex), + vjust = 0, + x = x, + rot = rot, + y = y + ); + + axis.gTree <- gTree( + name = paste0('axis.', axis.position), + children = gList(axis.lab, axisGrob), + vp = vp + ); + + return(axis.gTree); } add.axes <- function( @@ -168,62 +157,67 @@ add.axes <- function( yaxis2.label = NULL, no.ccf = FALSE, axis.label.cex = list(x = 1.55, y = 1.55), - axis.cex = list(x = 1, y = 1) + axis.cex = list(x = 1, y = 1), + plotting.direction = 'down' ) { - if (!no.ccf && 'ccf' %in% colnames(clone.out$v) && all(!is.na(clone.out$v$ccf))) { - add.xaxis( - clone.out, - scale1 = scale1, - axis.label = xaxis.label, - no.ccf = no.ccf, - axis.label.cex = axis.label.cex[['x']], - axis.cex = axis.cex[['x']] - ); - } + # Skip x-axis if plotting.direction is numeric (custom angle) + draw.xaxis <- !is.numeric(plotting.direction); + + if (!no.ccf && 'ccf' %in% colnames(clone.out$v) && all(!is.na(clone.out$v$ccf)) && draw.xaxis) { + add.xaxis( + clone.out, + scale1 = scale1, + axis.label = xaxis.label, + no.ccf = no.ccf, + axis.label.cex = axis.label.cex[['x']], + axis.cex = axis.cex[['x']], + plotting.direction = plotting.direction + ); + } if (yaxis.position != 'none' & scale.bar == FALSE) { ylabels1 <- unlist(yat[1]); ylabels2 <- unlist(yat[2]); if (yaxis.position == 'both') { - if (is.null(yaxis2.label)) { - warning('Missing second y-axis label'); - yaxis2.label <- ''; - } + if (is.null(yaxis2.label)) { + warning('Missing second y-axis label'); + yaxis2.label <- ''; + } conversion.factor <- scale1 / scale2 - ymax1 <- add.yaxis( - clone.out, - yaxis.position = 'left', - axis1.label = yaxis1.label, - no.ccf = no.ccf, - axis.label.cex = axis.label.cex[['y']], - axis.cex = axis.cex[['y']], - ylabels = ylabels1 - ); - - add.yaxis( - clone.out, - yaxis.position = 'right', - conversion.factor = conversion.factor, - axis1.label = yaxis2.label, - no.ccf = no.ccf, - axis.label.cex = axis.label.cex[['y']], - axis.cex = axis.cex[['y']], - ylabels = ylabels2 - ); + ymax1 <- add.yaxis( + clone.out, + yaxis.position = 'left', + axis1.label = yaxis1.label, + no.ccf = no.ccf, + axis.label.cex = axis.label.cex[['y']], + axis.cex = axis.cex[['y']], + ylabels = ylabels1 + ); + + add.yaxis( + clone.out, + yaxis.position = 'right', + conversion.factor = conversion.factor, + axis1.label = yaxis2.label, + no.ccf = no.ccf, + axis.label.cex = axis.label.cex[['y']], + axis.cex = axis.cex[['y']], + ylabels = ylabels2 + ); } else { - add.yaxis( - clone.out, - yaxis.position = yaxis.position, - axis1.label = yaxis1.label, - no.ccf = no.ccf, - axis.label.cex = axis.label.cex[['y']], - axis.cex = axis.cex[['y']], - ylabels = ylabels1 - ); + add.yaxis( + clone.out, + yaxis.position = yaxis.position, + axis1.label = yaxis1.label, + no.ccf = no.ccf, + axis.label.cex = axis.label.cex[['y']], + axis.cex = axis.cex[['y']], + ylabels = ylabels1 + ); } } } @@ -243,48 +237,48 @@ add.yaxis <- function( # Necessary to get the right positioning vp.unclipped <- make.plot.viewport(clone.out, clip = 'off'); - ymax <- clone.out$ymax; + ymax <- clone.out$ymax; # Set up tick labels - if (is.null(ylabels)) { - ylabels <- get.default.yat(ymax, conversion.factor); - } + if (is.null(ylabels)) { + ylabels <- get.default.yat(ymax, conversion.factor); + } - y.ticks.at <- ylabels / conversion.factor; + y.ticks.at <- ylabels / conversion.factor; - if (length(ylabels) == 0 || length(y.ticks.at) == 0) { + if (length(ylabels) == 0 || length(y.ticks.at) == 0) { warning('No y-axis ticks to draw. Skipping axis rendering.'); return(ymax); } - yaxis1 <- yaxisGrob( - name = 'axis.content', - at = y.ticks.at, - label = ylabels, - gp = gpar(cex = axis.cex), - main = yaxis.position == 'left' - ); + yaxis1 <- yaxisGrob( + name = 'axis.content', + at = y.ticks.at, + label = ylabels, + gp = gpar(cex = axis.cex), + main = yaxis.position == 'left' + ); if (max(y.ticks.at) / conversion.factor != ymax && !no.ccf) { # Extend the axis line beyond the last tick - yaxis1 <- extend.axis( - yaxis1, - limits = unit(c(0, ymax), 'native'), - type = 'y' - ); + yaxis1 <- extend.axis( + yaxis1, + limits = unit(c(0, ymax), 'native'), + type = 'y' + ); } - yaxis.gTree <- add.axis.label( - yaxis1, - axis1.label, - axis.position = yaxis.position, - axis.label.cex, - vp = vp.unclipped - ); + yaxis.gTree <- add.axis.label( + yaxis1, + axis1.label, + axis.position = yaxis.position, + axis.label.cex, + vp = vp.unclipped + ); - clone.out$grobs <- c(clone.out$grobs, list(yaxis.gTree)); + clone.out$grobs <- c(clone.out$grobs, list(yaxis.gTree)); - return(ymax) + return(ymax) } add.xaxis <- function( @@ -293,57 +287,62 @@ add.xaxis <- function( axis.label = 'CCF', no.ccf = FALSE, axis.label.cex = 1.55, - axis.cex = 1 + axis.cex = 1, + plotting.direction = 'down' ) { + # Determine axis position based on plotting direction + # down -> bottom, up -> top, left -> left, right -> right + axis.position <- switch( + as.character(plotting.direction), + 'down' = 'bottom', + 'up' = 'top', + 'left' = 'left', + 'right' = 'right', + 'bottom' # default + ); + # Necessary to get the right positioning - vp.unclipped <- make.plot.viewport(clone.out, clip = 'off'); - - # Set up tick labels - clone.widths <- as.numeric(as.matrix(clone.out$v[, c('x1', 'x2')])); - xat <- c(min(clone.widths), max(clone.widths)); - xlabels <- c(0, paste0(round(max(clone.out$v$ccf) * 100, 0), '%')); - - xaxis <- xaxisGrob( - name = 'axis.content', - at = xat, - label = xlabels, - gp = gpar(cex = axis.cex, vjust = 2), - main = TRUE - ); - - # Move the labels up slightly - xaxis.labels <- editGrob( - getGrob(xaxis, 'labels'), - y = getGrob(xaxis, 'ticks')$y1 * 1.5, - vjust = 1 - ); - - xaxis <- setGrob( - xaxis, - 'labels', - xaxis.labels - ); - - if (diff(xat) / scale1 != clone.out$width) { - # Extending the axis line beyond the clone limits - xaxis <- extend.axis( - xaxis, - limits = unit(clone.out$xlims,'native'), - type = 'x' - ); - } - - # Add the axis label - xaxis.gTree <- add.axis.label( - xaxis, - axis.label, - axis.position = 'bottom', - axis.label.cex, - vp = vp.unclipped - ); - - clone.out$grobs <- c(clone.out$grobs, list(xaxis.gTree)); + vp.unclipped <- make.plot.viewport(clone.out, clip = 'off'); + + # Set up tick labels + clone.widths <- as.numeric(as.matrix(clone.out$v[, c('x1', 'x2')])); + xat <- c(min(clone.widths), max(clone.widths)); + xlabels <- c(0, paste0(round(max(clone.out$v$ccf) * 100, 0), '%')); + + # Create appropriate axis grob based on position + if (axis.position %in% c('left', 'right')) { + # For horizontal plots, use yaxisGrob + xaxis <- yaxisGrob( + name = 'axis.content', + at = xat, + label = xlabels, + gp = gpar(cex = axis.label.cex), + main = (axis.position == 'left') + ); + # clone.out$ylims <- unit(clone.out$ylims * 1.5, 'native') + xaxis <- extend.axis(xaxis, unit(clone.out$ylims * 1.5, 'native'), type = 'y'); + } else { + # For vertical plots, use xaxisGrob + xaxis <- xaxisGrob( + name = 'axis.content', + at = xat, + label = xlabels, + gp = gpar(cex = axis.label.cex), + main = (axis.position == 'bottom') + ); + xaxis <- extend.axis(xaxis, unit(clone.out$xlims * 1.5, 'native'), type = 'x'); + } + # Add the axis label + xaxis.gTree <- add.axis.label( + xaxis, + axis.label, + axis.position = axis.position, + axis.label.cex, + vp = vp.unclipped + ); + + clone.out$grobs <- c(clone.out$grobs, list(xaxis.gTree)); } add.main <- function( @@ -354,43 +353,43 @@ add.main <- function( size.units = 'npc' ) { - # y.pos <- unit(1.08,'npc'); - y.pos <- unit(0.5,'npc'); - - if (!is.null(main.y)) { - pushViewport(clone.out$vp); - plot.top <- convertY(unit(1,'npc'), size.units, valueOnly = TRUE); - popViewport(); - y.pos <- plot.top + main.y; - } - - main.label <- textGrob( - main, - just = 'center', - gp = gpar( - col = 'black', - cex = main.cex - )); - - main.grob <- gTree( - children = gList(main.label), - name = 'main.gtree', - cl = 'main.label', - vp = vpStack( - make.plot.viewport( - clone.out, - clip = 'off', - just = c('centre', 'centre') - ), - viewport( - y = unit(y.pos, size.units), - x = unit(0, 'native'), - height = grobHeight(main.label), - width = grobWidth(main.label), - just = c('centre', 'bottom') - ) - ) - ); - - clone.out$grobs <- c(clone.out$grobs, list(main.grob)); + # y.pos <- unit(1.08,'npc'); + y.pos <- unit(0.5,'npc'); + + if (!is.null(main.y)) { + pushViewport(clone.out$vp); + plot.top <- convertY(unit(1,'npc'), size.units, valueOnly = TRUE); + popViewport(); + y.pos <- plot.top + main.y; + } + + main.label <- textGrob( + main, + just = 'center', + gp = gpar( + col = 'black', + cex = main.cex + )); + + main.grob <- gTree( + children = gList(main.label), + name = 'main.gtree', + cl = 'main.label', + vp = vpStack( + make.plot.viewport( + clone.out, + clip = 'off', + just = c('centre', 'centre') + ), + viewport( + y = unit(y.pos, size.units), + x = unit(0, 'native'), + height = grobHeight(main.label), + width = grobWidth(main.label), + just = c('centre', 'bottom') + ) + ) + ); + + clone.out$grobs <- c(clone.out$grobs, list(main.grob)); } diff --git a/tests/testthat/data/branching.dendrogram.plots.Rda b/tests/testthat/data/branching.dendrogram.plots.Rda index 38f25e0..5ffcae2 100644 Binary files a/tests/testthat/data/branching.dendrogram.plots.Rda and b/tests/testthat/data/branching.dendrogram.plots.Rda differ diff --git a/tests/testthat/data/branching.fixed.plots.Rda b/tests/testthat/data/branching.fixed.plots.Rda index 8774cf5..9adde81 100644 Binary files a/tests/testthat/data/branching.fixed.plots.Rda and b/tests/testthat/data/branching.fixed.plots.Rda differ diff --git a/tests/testthat/data/branching.radial.plots.Rda b/tests/testthat/data/branching.radial.plots.Rda index 1addead..a42ad71 100644 Binary files a/tests/testthat/data/branching.radial.plots.Rda and b/tests/testthat/data/branching.radial.plots.Rda differ diff --git a/tests/testthat/data/complex.plots.Rda b/tests/testthat/data/complex.plots.Rda index 2399db9..7b122ca 100644 Binary files a/tests/testthat/data/complex.plots.Rda and b/tests/testthat/data/complex.plots.Rda differ diff --git a/tests/testthat/data/fish.plots.Rda b/tests/testthat/data/fish.plots.Rda index fb4524b..a0d641c 100644 Binary files a/tests/testthat/data/fish.plots.Rda and b/tests/testthat/data/fish.plots.Rda differ diff --git a/tests/testthat/data/linear.plots.Rda b/tests/testthat/data/linear.plots.Rda index a1ec3d0..86c5a8d 100644 Binary files a/tests/testthat/data/linear.plots.Rda and b/tests/testthat/data/linear.plots.Rda differ diff --git a/tests/update-snapshots.R b/tests/update-snapshots.R new file mode 100644 index 0000000..81a9999 --- /dev/null +++ b/tests/update-snapshots.R @@ -0,0 +1,181 @@ +#!/usr/bin/env Rscript +# update-snapshots.R +# +# Regenerates all reference .Rda snapshot files used by the testthat snapshot +# tests. Run this whenever the rendering output of SRCGrob changes intentionally +# (e.g. after a bug fix or layout change). +# +# Usage (from the package root): +# Rscript tests/update-snapshots.R +# +# The script re-installs the package from source first so the snapshots are +# always built against the current code. + +pkg.root <- normalizePath(file.path(dirname(sys.frame(1)$ofile), '..'), mustWork = FALSE) +if (!nzchar(pkg.root) || pkg.root == '.') { + pkg.root <- getwd() + } + +message('Package root: ', pkg.root) +message('Installing package from source...') +devtools::load_all(pkg.root, quiet = TRUE) + +data.dir <- file.path(pkg.root, 'tests', 'testthat', 'data') + +# --------------------------------------------------------------------------- +# Helper +# --------------------------------------------------------------------------- + +update.rda <- function(filename, ...) { + path <- file.path(data.dir, filename) + vars <- list(...) + list2env(vars, envir = environment()) + save(list = names(vars), file = path, envir = environment()) + message(' Saved ', filename) + } + +# --------------------------------------------------------------------------- +# 1. Fixed branching +# --------------------------------------------------------------------------- + +message('\n[1/6] Fixed branching...') +local({ + load(file.path(data.dir, 'branching.fixed.data.Rda')) + + branching.fixed.example <- SRCGrob(branching.fixed.test.data$tree) + + branching.fixed.30deg.example <- SRCGrob( + branching.fixed.test.data$tree, + plotting.direction = 30 + ) + + update.rda( + 'branching.fixed.plots.Rda', + branching.fixed.example = branching.fixed.example, + branching.fixed.30deg.example = branching.fixed.30deg.example + ) + }) + +# --------------------------------------------------------------------------- +# 2. Radial branching +# --------------------------------------------------------------------------- + +message('[2/6] Radial branching...') +local({ + load(file.path(data.dir, 'branching.radial.data.Rda')) + + branching.radial.example <- SRCGrob(branching.radial.test.data$tree) + + branching.radial.right.example <- SRCGrob( + branching.radial.test.data$tree, + plotting.direction = 'right' + ) + + branching.radial.30deg.example <- SRCGrob( + branching.radial.test.data$tree, + plotting.direction = 30 + ) + + update.rda( + 'branching.radial.plots.Rda', + branching.radial.example = branching.radial.example, + branching.radial.right.example = branching.radial.right.example, + branching.radial.30deg.example = branching.radial.30deg.example + ) + }) + +# --------------------------------------------------------------------------- +# 3. Dendrogram +# --------------------------------------------------------------------------- + +message('[3/6] Dendrogram...') +local({ + load(file.path(data.dir, 'branching.dendrogram.data.Rda')) + + branching.dendrogram.example <- SRCGrob(branching.dendrogram.test.data$tree) + + update.rda( + 'branching.dendrogram.plots.Rda', + branching.dendrogram.example = branching.dendrogram.example + ) + }) + +# --------------------------------------------------------------------------- +# 4. Fish plot +# --------------------------------------------------------------------------- + +message('[4/6] Fish plot...') +local({ + load(file.path(data.dir, 'fish.data.Rda')) + + fish.example <- SRCGrob( + fish.test.data$tree, + polygon.colour.scheme = fish.test.data$colour.scheme + ) + + update.rda( + 'fish.plots.Rda', + fish.example = fish.example + ) + }) + +# --------------------------------------------------------------------------- +# 5. Linear +# --------------------------------------------------------------------------- + +message('[5/6] Linear...') +local({ + load(file.path(data.dir, 'linear.data.Rda')) + + linear.example <- SRCGrob( + linear.test.data$tree, + linear.test.data$node.text, + main = 'WHO003', + node.text.cex = 0.85, + scale1 = 0.9, + yaxis1.label = 'PGA', + yaxis2.label = 'SNV', + xaxis.label = 'CP', + main.cex = 1.55, + main.y = 0.3, + size.units = 'inches', + horizontal.padding = -1, + add.normal = TRUE + ) + + linear.30deg.example <- SRCGrob( + linear.test.data$tree[, c('parent', 'length.1', 'length.2')], + yaxis2.label = '', + plotting.direction = 30 + ) + + update.rda( + 'linear.plots.Rda', + linear.example = linear.example, + linear.30deg.example = linear.30deg.example + ) + }) + +# --------------------------------------------------------------------------- +# 6. Complex (spread) +# --------------------------------------------------------------------------- + +message('[6/6] Complex spread...') +local({ + load(file.path(data.dir, 'complex.data.Rda')) + + spread.example <- SRCGrob(spread.test.data$tree) + + spread.30deg.example <- SRCGrob( + spread.test.data$tree, + plotting.direction = 30 + ) + + update.rda( + 'complex.plots.Rda', + spread.example = spread.example, + spread.30deg.example = spread.30deg.example + ) + }) + +message('\nAll snapshots updated successfully.')