# File src/library/grid/R/primitives.R # Part of the R package, https://www.R-project.org # # Copyright (C) 1995-2015 The R Core Team # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # 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. # # A copy of the GNU General Public License is available at # https://www.R-project.org/Licenses/ # Function that creates a description of an arrow head # to add to a line arrow <- function(angle=30, length=unit(0.25, "inches"), ends="last", type="open") { angle <- as.numeric(angle) if (!is.unit(length)) stop("'length' must be a 'unit' object") ends <- as.integer(match(ends, c("first", "last", "both"))) type <- as.integer(match(type, c("open", "closed"))) if (anyNA(ends) || anyNA(type) || length(ends) == 0 || length(type) == 0) stop("invalid 'ends' or 'type' argument") a <- list(angle=angle, length=length, ends=ends, type=type) class(a) <- "arrow" a } length.arrow <- function(x) { max(do.call("max", lapply(x, length)), length(x$length)) } rep.arrow <- function(x, ...) { maxn <- length(x) newa <- list(angle=rep(x$angle, length.out=maxn), length=rep(x$length, length.out=maxn), ends=rep(x$ends, length.out=maxn), type=rep(x$type, length.out=maxn)) newa <- lapply(newa, rep, ...) class(newa) <- "arrow" newa } # Method for subsetting "arrow" objects `[.arrow` <- function(x, index, ...) { if (length(index) == 0) return(NULL) maxn <- length(x) newa <- list(angle=rep(x$angle, length.out=maxn), length=rep(x$length, length.out=maxn), ends=rep(x$ends, length.out=maxn), type=rep(x$type, length.out=maxn)) newa <- lapply(X = newa, FUN = "[", index, ...) class(newa) <- "arrow" newa } ###################################### # move-to and line-to primitives ###################################### validDetails.move.to <- function(x) { if (!is.unit(x$x) || !is.unit(x$y)) stop("'x' and 'y' must be units") # Make sure that x and y are of length 1 if (length(x$x) > 1 | length(x$y) > 1) stop("'x' and 'y' must have length 1") x } drawDetails.move.to <- function(x, recording=TRUE) { grid.Call.graphics(L_moveTo, x$x, x$y) } moveToGrob <- function(x=0, y=0, default.units="npc", name=NULL, vp=NULL) { if (!is.unit(x)) x <- unit(x, default.units) if (!is.unit(y)) y <- unit(y, default.units) grob(x=x, y=y, name=name, vp=vp, cl="move.to") } grid.move.to <- function(x=0, y=0, default.units="npc", name=NULL, draw=TRUE, vp=NULL) { mtg <- moveToGrob(x=x, y=y, default.units=default.units, name=name, vp=vp) if (draw) grid.draw(mtg) invisible(mtg) } validDetails.line.to <- function(x) { if (!is.unit(x$x) || !is.unit(x$y)) stop("'x' and 'y' must be units") # Make sure that x and y are of length 1 if (length(x$x) > 1 | length(x$y) > 1) stop("'x' and 'y' must have length 1") if (!(is.null(x$arrow) || inherits(x$arrow, "arrow"))) stop("invalid 'arrow' argument") x } drawDetails.line.to <- function(x, recording=TRUE) { grid.Call.graphics(L_lineTo, x$x, x$y, x$arrow) } lineToGrob <- function(x=1, y=1, default.units="npc", arrow=NULL, name=NULL, gp=gpar(), vp=NULL) { if (!is.unit(x)) x <- unit(x, default.units) if (!is.unit(y)) y <- unit(y, default.units) grob(x=x, y=y, arrow=arrow, name=name, gp=gp, vp=vp, cl="line.to") } grid.line.to <- function(x=1, y=1, default.units="npc", arrow=NULL, name=NULL, gp=gpar(), draw=TRUE, vp=NULL) { ltg <- lineToGrob(x=x, y=y, default.units=default.units, arrow=arrow, name=name, gp=gp, vp=vp) if (draw) grid.draw(ltg) invisible(ltg) } ###################################### # LINES primitive ###################################### validDetails.lines <- function(x) { if (!is.unit(x$x) || !is.unit(x$y)) stop("'x' and 'y' must be units") if (!(is.null(x$arrow) || inherits(x$arrow, "arrow"))) stop("invalid 'arrow' argument") x } drawDetails.lines <- function(x, recording=TRUE) { grid.Call.graphics(L_lines, x$x, x$y, list(as.integer(1L:max(length(x$x), length(x$y)))), x$arrow) } xDetails.lines <- function(x, theta) { bounds <- grid.Call(L_locnBounds, x$x, x$y, theta) if (is.null(bounds)) unit(0.5, "npc") else unit(bounds[1L], "inches") } yDetails.lines <- function(x, theta) { bounds <- grid.Call(L_locnBounds, x$x, x$y, theta) if (is.null(bounds)) unit(0.5, "npc") else unit(bounds[2L], "inches") } widthDetails.lines <- function(x) { bounds <- grid.Call(L_locnBounds, x$x, x$y, 0) if (is.null(bounds)) unit(0, "inches") else unit(bounds[3L], "inches") } heightDetails.lines <- function(x) { bounds <- grid.Call(L_locnBounds, x$x, x$y, 0) if (is.null(bounds)) unit(0, "inches") else unit(bounds[4L], "inches") } linesGrob <- function(x=unit(c(0, 1), "npc"), y=unit(c(0, 1), "npc"), default.units="npc", arrow=NULL, name=NULL, gp=gpar(), vp=NULL) { # Allow user to specify unitless vector; add default units if (!is.unit(x)) x <- unit(x, default.units) if (!is.unit(y)) y <- unit(y, default.units) grob(x=x, y=y, arrow=arrow, name=name, gp=gp, vp=vp, cl="lines") } grid.lines <- function(x=unit(c(0, 1), "npc"), y=unit(c(0, 1), "npc"), default.units="npc", arrow=NULL, name=NULL, gp=gpar(), draw=TRUE, vp=NULL) { lg <- linesGrob(x=x, y=y, default.units=default.units, arrow=arrow, name=name, gp=gp, vp=vp) if (draw) grid.draw(lg) invisible(lg) } ###################################### # POLYLINES primitive ###################################### # Very similar to LINES primitive, but allows # multiple polylines via 'id' and 'id.lengths' args # as per POLYGON primitive validDetails.polyline <- function(x) { if (!is.unit(x$x) || !is.unit(x$y)) stop("'x' and 'y' must be units") if (!is.null(x$id) && !is.null(x$id.lengths)) stop("it is invalid to specify both 'id' and 'id.lengths'") if (length(x$x) != length(x$y)) stop("'x' and 'y' must be same length") if (!is.null(x$id) && (length(x$id) != length(x$x))) stop("'x' and 'y' and 'id' must all be same length") if (!is.null(x$id)) x$id <- as.integer(x$id) if (!is.null(x$id.lengths) && (sum(x$id.lengths) != length(x$x))) stop("'x' and 'y' and 'id.lengths' must specify same overall length") if (!is.null(x$id.lengths)) x$id.lengths <- as.integer(x$id.lengths) if (!(is.null(x$arrow) || inherits(x$arrow, "arrow"))) stop("invalid 'arrow' argument") x } drawDetails.polyline <- function(x, recording=TRUE) { if (is.null(x$id) && is.null(x$id.lengths)) grid.Call.graphics(L_lines, x$x, x$y, list(as.integer(seq_along(x$x))), x$arrow) else { if (is.null(x$id)) { n <- length(x$id.lengths) id <- rep(1L:n, x$id.lengths) } else { n <- length(unique(x$id)) id <- x$id } index <- split(as.integer(seq_along(x$x)), id) grid.Call.graphics(L_lines, x$x, x$y, index, x$arrow) } } xDetails.polyline <- function(x, theta) { bounds <- grid.Call(L_locnBounds, x$x, x$y, theta) if (is.null(bounds)) unit(0.5, "npc") else unit(bounds[1L], "inches") } yDetails.polyline <- function(x, theta) { bounds <- grid.Call(L_locnBounds, x$x, x$y, theta) if (is.null(bounds)) unit(0.5, "npc") else unit(bounds[2L], "inches") } widthDetails.polyline <- function(x) { bounds <- grid.Call(L_locnBounds, x$x, x$y, 0) if (is.null(bounds)) unit(0, "inches") else unit(bounds[3L], "inches") } heightDetails.polyline <- function(x) { bounds <- grid.Call(L_locnBounds, x$x, x$y, 0) if (is.null(bounds)) unit(0, "inches") else unit(bounds[4L], "inches") } polylineGrob <- function(x=unit(c(0, 1), "npc"), y=unit(c(0, 1), "npc"), id=NULL, id.lengths=NULL, default.units="npc", arrow=NULL, name=NULL, gp=gpar(), vp=NULL) { # Allow user to specify unitless vector; add default units if (!is.unit(x)) x <- unit(x, default.units) if (!is.unit(y)) y <- unit(y, default.units) grob(x=x, y=y, id=id, id.lengths=id.lengths, arrow=arrow, name=name, gp=gp, vp=vp, cl="polyline") } grid.polyline <- function(...) { grid.draw(polylineGrob(...)) } ###################################### # SEGMENTS primitive ###################################### validDetails.segments <- function(x) { if (!is.unit(x$x0) || !is.unit(x$x1) || !is.unit(x$y0) || !is.unit(x$y1)) stop("'x0', 'y0', 'x1', and 'y1' must be units") if (!(is.null(x$arrow) || inherits(x$arrow, "arrow"))) stop("invalid 'arrow' argument") x } drawDetails.segments <- function(x, recording=TRUE) { grid.Call.graphics(L_segments, x$x0, x$y0, x$x1, x$y1, x$arrow) } segmentBounds <- function(x, theta) { n <- max(length(x$x0), length(x$x1), length(x$y0), length(x$y1)) x0 <- rep(x$x0, length.out=n) x1 <- rep(x$x1, length.out=n) y0 <- rep(x$y0, length.out=n) y1 <- rep(x$y1, length.out=n) grid.Call(L_locnBounds, unit.c(x0, x1), unit.c(y0, y1), theta) } xDetails.segments <- function(x, theta) { bounds <- segmentBounds(x, theta) if (is.null(bounds)) unit(0.5, "npc") else unit(bounds[1L], "inches") } yDetails.segments <- function(x, theta) { bounds <- segmentBounds(x, theta) if (is.null(bounds)) unit(0.5, "npc") else unit(bounds[2L], "inches") } widthDetails.segments <- function(x) { bounds <- segmentBounds(x, 0) if (is.null(bounds)) unit(0, "inches") else unit(bounds[3L], "inches") } heightDetails.segments <- function(x) { bounds <- segmentBounds(x, 0) if (is.null(bounds)) unit(0, "inches") else unit(bounds[4L], "inches") } segmentsGrob <- function(x0=unit(0, "npc"), y0=unit(0, "npc"), x1=unit(1, "npc"), y1=unit(1, "npc"), default.units="npc", arrow=NULL, name=NULL, gp=gpar(), vp=NULL) { # Allow user to specify unitless vector; add default units if (!is.unit(x0)) x0 <- unit(x0, default.units) if (!is.unit(x1)) x1 <- unit(x1, default.units) if (!is.unit(y0)) y0 <- unit(y0, default.units) if (!is.unit(y1)) y1 <- unit(y1, default.units) grob(x0=x0, y0=y0, x1=x1, y1=y1, arrow=arrow, name=name, gp=gp, vp=vp, cl="segments") } grid.segments <- function(x0=unit(0, "npc"), y0=unit(0, "npc"), x1=unit(1, "npc"), y1=unit(1, "npc"), default.units="npc", arrow=NULL, name=NULL, gp=gpar(), draw=TRUE, vp=NULL) { sg <- segmentsGrob(x0=x0, y0=y0, x1=x1, y1=y1, default.units=default.units, arrow=arrow, name=name, gp=gp, vp=vp) if (draw) grid.draw(sg) invisible(sg) } ###################################### # ARROWS primitive ###################################### # Superceded by 'arrow' arg to line-drawing primitives # which contains an "arrow" object validDetails.arrows <- function(x) { if ((!is.null(x$x) && !is.unit(x$x)) || (!is.null(x$y) && !is.unit(x$y))) stop("'x' and 'y' must be units or NULL") if (!is.unit(x$length)) stop("'length' must be a 'unit' object") x$ends <- as.integer(match(x$ends, c("first", "last", "both"))) x$type <- as.integer(match(x$type, c("open", "closed"))) if (any(is.na(x$ends)) || any(is.na(x$type))) stop("invalid 'ends' or 'type' argument") x } drawDetails.arrows <- function(x, recording=TRUE) { if (is.null(x$x)) { # y should be null too if (!is.null(x$y)) stop("corrupt 'arrows' object") lineThing <- getGrob(x, childNames(x)) # This could be done via method dispatch, but that really # seemed like overkill # OTOH, this is NOT user-extensible # AND the code for, e.g., "lines" is not located with # the other grid.lines code so changes there are unlikely # to propagate to here (e.g., add an id arg to grid.lines? if (inherits(lineThing, "line.to")) { x1 <- NULL x2 <- lineThing$x y1 <- NULL y2 <- lineThing$y xnm1 <- NULL xn <- lineThing$x ynm1 <- NULL yn <- lineThing$y } else if (inherits(lineThing, "lines")) { # x or y may be recycled n <- max(length(lineThing$x), length(lineThing$y)) xx <- rep(lineThing$x, length.out=2) x1 <- xx[1L] x2 <- xx[2L] xx <- rep(lineThing$x, length.out=n) xnm1 <- xx[n - 1] xn <- xx[n] yy <- rep(lineThing$y, length.out=2) y1 <- yy[1L] y2 <- yy[2L] yy <- rep(lineThing$y, length.out=n) ynm1 <- yy[n - 1] yn <- yy[n] } else { # inherits(lineThing, "segments") x1 <- lineThing$x0 x2 <- lineThing$x1 xnm1 <- lineThing$x0 xn <- lineThing$x1 y1 <- lineThing$y0 y2 <- lineThing$y1 ynm1 <- lineThing$y0 yn <- lineThing$y1 } } else { # x or y may be recycled n <- max(length(x$x), length(x$y)) xx <- rep(x$x, length.out=2) x1 <- xx[1L] x2 <- xx[2L] xx <- rep(x$x, length.out=n) xnm1 <- xx[n - 1] xn <- xx[n] yy <- rep(x$y, length.out=2) y1 <- yy[1L] y2 <- yy[2L] yy <- rep(x$y, length.out=n) ynm1 <- yy[n - 1] yn <- yy[n] grid.Call.graphics(L_lines, x$x, x$y, list(as.integer(1L:n)), NULL) } grid.Call.graphics(L_arrows, x1, x2, xnm1, xn, y1, y2, ynm1, yn, x$angle, x$length, x$ends, x$type) } widthDetails.arrows <- function(x) { if (is.null(x$x)) { # y should be null too if (!is.null(x$y)) stop("corrupt 'arrows' object") lineThing <- getGrob(x, childNames(x)) widthDetails(lineThing) } else { bounds <- grid.Call(L_locnBounds, x$x, x$y, 0) if (is.null(bounds)) unit(0, "inches") else unit(bounds[3L], "inches") } } heightDetails.arrows <- function(x) { if (is.null(x$x)) { # y should be null too if (!is.null(x$y)) stop("corrupt 'arrows' object") lineThing <- getGrob(x, childNames(x)) heightDetails(lineThing) } else { bounds <- grid.Call(L_locnBounds, x$x, x$y, 0) if (is.null(bounds)) unit(0, "inches") else unit(bounds[4L], "inches") } } arrowsGrob <- function(x=c(0.25, 0.75), y=0.5, default.units="npc", grob=NULL, angle=30, length=unit(0.25, "inches"), ends="last", type="open", name=NULL, gp=gpar(), vp=NULL) { .Defunct(msg="'arrowsGrob' is defunct; use 'arrow' arguments to line drawing functions") } grid.arrows <- function(x=c(0.25, 0.75), y=0.5, default.units="npc", grob=NULL, angle=30, length=unit(0.25, "inches"), ends="last", type="open", name=NULL, gp=gpar(), draw=TRUE, vp=NULL) { .Defunct(msg="'grid.arrows' is defunct; use 'arrow' arguments to line drawing functions") } ###################################### # POLYGON primitive ###################################### validDetails.polygon <- function(x) { if (!is.unit(x$x) || !is.unit(x$y)) stop("'x' and 'y' must be units") if (!is.null(x$id) && !is.null(x$id.lengths)) stop("it is invalid to specify both 'id' and 'id.lengths'") if (length(x$x) != length(x$y)) stop("'x' and 'y' must be same length") if (!is.null(x$id) && (length(x$id) != length(x$x))) stop("'x' and 'y' and 'id' must all be same length") if (!is.null(x$id)) x$id <- as.integer(x$id) if (!is.null(x$id.lengths) && (sum(x$id.lengths) != length(x$x))) stop("'x' and 'y' and 'id.lengths' must specify same overall length") if (!is.null(x$id.lengths)) x$id.lengths <- as.integer(x$id.lengths) x } drawDetails.polygon <- function(x, recording=TRUE) { if (is.null(x$id) && is.null(x$id.lengths)) grid.Call.graphics(L_polygon, x$x, x$y, list(as.integer(seq_along(x$x)))) else { if (is.null(x$id)) { n <- length(x$id.lengths) id <- rep(1L:n, x$id.lengths) } else { n <- length(unique(x$id)) id <- x$id } index <- split(as.integer(seq_along(x$x)), id) grid.Call.graphics(L_polygon, x$x, x$y, index) } } xDetails.polygon <- function(x, theta) { bounds <- grid.Call(L_locnBounds, x$x, x$y, theta) if (is.null(bounds)) unit(0.5, "npc") else unit(bounds[1L], "inches") } yDetails.polygon <- function(x, theta) { bounds <- grid.Call(L_locnBounds, x$x, x$y, theta) if (is.null(bounds)) unit(0.5, "npc") else unit(bounds[2L], "inches") } widthDetails.polygon <- function(x) { bounds <- grid.Call(L_locnBounds, x$x, x$y, 0) if (is.null(bounds)) unit(0, "inches") else unit(bounds[3L], "inches") } heightDetails.polygon <- function(x) { bounds <- grid.Call(L_locnBounds, x$x, x$y, 0) if (is.null(bounds)) unit(0, "inches") else unit(bounds[4L], "inches") } polygonGrob <- function(x=c(0, 0.5, 1, 0.5), y=c(0.5, 1, 0.5, 0), id=NULL, id.lengths=NULL, default.units="npc", name=NULL, gp=gpar(), vp=NULL) { if (!is.unit(x)) x <- unit(x, default.units) if (!is.unit(y)) y <- unit(y, default.units) grob(x=x, y=y, id=id, id.lengths=id.lengths, name=name, gp=gp, vp=vp, cl="polygon") } grid.polygon <- function(x=c(0, 0.5, 1, 0.5), y=c(0.5, 1, 0.5, 0), id=NULL, id.lengths=NULL, default.units="npc", name=NULL, gp=gpar(), draw=TRUE, vp=NULL) { pg <- polygonGrob(x=x, y=y, id=id, id.lengths=id.lengths, default.units=default.units, name=name, gp=gp, vp=vp) if (draw) grid.draw(pg) invisible(pg) } ###################################### # PATH primitive ###################################### validDetails.pathgrob <- function(x) { if (!is.unit(x$x) || !is.unit(x$y)) stop("'x' and 'y' must be units") if (!is.null(x$id) && !is.null(x$id.lengths)) stop("it is invalid to specify both 'id' and 'id.lengths'") if (length(x$x) != length(x$y)) stop("'x' and 'y' must be same length") if (!is.null(x$id) && (length(x$id) != length(x$x))) stop("'x' and 'y' and 'id' must all be same length") if (!is.null(x$id)) x$id <- as.integer(x$id) if (!is.null(x$id.lengths) && (sum(x$id.lengths) != length(x$x))) stop("'x' and 'y' and 'id.lengths' must specify same overall length") if (!is.null(x$id.lengths)) x$id.lengths <- as.integer(x$id.lengths) x } xDetails.pathgrob <- function(x, theta) { bounds <- grid.Call(L_locnBounds, x$x, x$y, theta) if (is.null(bounds)) unit(0.5, "npc") else unit(bounds[1L], "inches") } yDetails.pathgrob <- function(x, theta) { bounds <- grid.Call(L_locnBounds, x$x, x$y, theta) if (is.null(bounds)) unit(0.5, "npc") else unit(bounds[2L], "inches") } widthDetails.pathgrob <- function(x) { bounds <- grid.Call(L_locnBounds, x$x, x$y, 0) if (is.null(bounds)) unit(0, "inches") else unit(bounds[3L], "inches") } heightDetails.pathgrob <- function(x) { bounds <- grid.Call(L_locnBounds, x$x, x$y, 0) if (is.null(bounds)) unit(0, "inches") else unit(bounds[4L], "inches") } drawDetails.pathgrob <- function(x, recording=TRUE) { if (is.null(x$id) && is.null(x$id.lengths)) grid.Call.graphics(L_polygon, x$x, x$y, list(as.integer(seq_along(x$x)))) else { if (is.null(x$id)) { n <- length(x$id.lengths) id <- rep(1L:n, x$id.lengths) } else { n <- length(unique(x$id)) id <- x$id } index <- split(as.integer(seq_along(x$x)), id) grid.Call.graphics(L_path, x$x, x$y, index, switch(x$rule, winding=1L, evenodd=0L)) } } pathGrob <- function(x, y, id=NULL, id.lengths=NULL, rule="winding", default.units="npc", name=NULL, gp=gpar(), vp=NULL) { if (!is.unit(x)) x <- unit(x, default.units) if (!is.unit(y)) y <- unit(y, default.units) grob(x=x, y=y, id=id, id.lengths=id.lengths, rule=rule, name=name, gp=gp, vp=vp, cl="pathgrob") } grid.path <- function(...) { grid.draw(pathGrob(...)) } ###################################### # XSPLINE primitive ###################################### validDetails.xspline <- function(x) { if (!is.unit(x$x) || !is.unit(x$y)) stop("x and y must be units") if (!is.null(x$id) && !is.null(x$id.lengths)) stop("it is invalid to specify both 'id' and 'id.lengths'") nx <- length(x$x) ny <- length(x$y) if (nx != ny) stop("'x' and 'y' must be same length") if (!is.null(x$id) && (length(x$id) != nx)) stop("'x' and 'y' and 'id' must all be same length") if (!is.null(x$id)) x$id <- as.integer(x$id) if (!is.null(x$id.lengths) && (sum(x$id.lengths) != nx)) stop("'x' and 'y' and 'id.lengths' must specify same overall length") if (!is.null(x$id.lengths)) x$id.lengths <- as.integer(x$id.lengths) if (!(is.null(x$arrow) || inherits(x$arrow, "arrow"))) stop("invalid 'arrow' argument") if (any(x$shape < -1 || x$shape > 1)) stop("'shape' must be between -1 and 1") x$open <- as.logical(x$open) # Force all first and last shapes to be 0 for open xsplines if (x$open) { x$shape <- rep(x$shape, length.out=nx) # Watch out for id or id.length! index <- xsplineIndex(x) first <- sapply(index, min) last <- sapply(index, max) x$shape[c(first, last)] <- 0 } x } xsplineIndex <- function(x) { if (is.null(x$id) && is.null(x$id.lengths)) list(as.integer(seq_along(x$x))) else { if (is.null(x$id)) { n <- length(x$id.lengths) id <- rep(1L:n, x$id.lengths) } else { n <- length(unique(x$id)) id <- x$id } split(as.integer(seq_along(x$x)), id) } } drawDetails.xspline <- function(x, recording=TRUE) { grid.Call.graphics(L_xspline, x$x, x$y, x$shape, x$open, x$arrow, x$repEnds, xsplineIndex(x)) } xDetails.xspline <- function(x, theta) { bounds <- grid.Call(L_xsplineBounds, x$x, x$y, x$shape, x$open, x$arrow, x$repEnds, xsplineIndex(x), theta) if (is.null(bounds)) unit(0.5, "npc") else unit(bounds[1L], "inches") } yDetails.xspline <- function(x, theta) { bounds <- grid.Call(L_xsplineBounds, x$x, x$y, x$shape, x$open, x$arrow, x$repEnds, xsplineIndex(x), theta) if (is.null(bounds)) unit(0.5, "npc") else unit(bounds[2L], "inches") } widthDetails.xspline <- function(x) { bounds <- grid.Call(L_xsplineBounds, x$x, x$y, x$shape, x$open, x$arrow, x$repEnds, list(as.integer(seq_along(x$x))), 0) if (is.null(bounds)) unit(0, "inches") else unit(bounds[3L], "inches") } heightDetails.xspline <- function(x) { bounds <- grid.Call(L_xsplineBounds, x$x, x$y, x$shape, x$open, x$arrow, x$repEnds, list(as.integer(seq_along(x$x))), 0) if (is.null(bounds)) unit(0, "inches") else unit(bounds[4L], "inches") } xsplineGrob <- function(x=c(0, 0.5, 1, 0.5), y=c(0.5, 1, 0.5, 0), id=NULL, id.lengths=NULL, default.units="npc", shape=0, open=TRUE, arrow=NULL, repEnds=TRUE, name=NULL, gp=gpar(), vp=NULL) { if (!is.unit(x)) x <- unit(x, default.units) if (!is.unit(y)) y <- unit(y, default.units) grob(x=x, y=y, shape=shape, open=open, id=id, id.lengths=id.lengths, arrow=arrow, repEnds=repEnds, name=name, gp=gp, vp=vp, cl="xspline") } grid.xspline <- function(...) { grid.draw(xsplineGrob(...)) } xsplinePoints <- function(x) { # Mimic drawGrob() to ensure x$vp and x$gp enforced dlon <- grid.Call(L_setDLon, FALSE) on.exit(grid.Call(L_setDLon, dlon)) tempgpar <- grid.Call(L_getGPar) on.exit(grid.Call(L_setGPar, tempgpar), add=TRUE) preDraw(x) # Raw pts in dev coords devPoints <- grid.Call(L_xsplinePoints, x$x, x$y, x$shape, x$open, x$arrow, x$repEnds, xsplineIndex(x), 0) postDraw(x) # Convert to units in inches unitPoints <- lapply(devPoints, function(x) { names(x) <- c("x", "y") x$x <- unit(x$x, "inches") x$y <- unit(x$y, "inches") x }) if (length(unitPoints) == 1) unitPoints <- unitPoints[[1]] unitPoints } ###################################### # BEZIER primitive ###################################### # A bezier grob that works of a (not-100% accurate) approximation # using X-splines # X-Spline approx to Bezier Ms <- 1/6*rbind(c(1, 4, 1, 0), c(-3, 0, 3, 0), c(3, -6, 3, 0), c(-1, 3, -3, 1)) Msinv <- solve(Ms) # Bezier control matrix Mb <- rbind(c(1, 0, 0, 0), c(-3, 3, 0, 0), c(3, -6, 3, 0), c(-1, 3, -3, 1)) splinePoints <- function(xb, yb, idIndex) { xs <- unlist(lapply(idIndex, function(i) { Msinv %*% Mb %*% xb[i] })) ys <- unlist(lapply(idIndex, function(i) { Msinv %*% Mb %*% yb[i] })) list(x=xs, y=ys) } splinegrob <- function(x) { xx <- convertX(x$x, "inches", valueOnly=TRUE) yy <- convertY(x$y, "inches", valueOnly=TRUE) sp <- splinePoints(xx, yy, xsplineIndex(x)) xsplineGrob(sp$x, sp$y, default.units="inches", id=x$id, id.lengths=x$id.lengths, shape=1, repEnds=FALSE, arrow=x$arrow, name=x$name, gp=x$gp, vp=x$vp) } validDetails.beziergrob <- function(x) { if (!is.unit(x$x) || !is.unit(x$y)) stop("x and y must be units") if (!is.null(x$id) && !is.null(x$id.lengths)) stop("it is invalid to specify both 'id' and 'id.lengths'") nx <- length(x$x) ny <- length(x$y) if (nx != ny) stop("'x' and 'y' must be same length") if (!is.null(x$id) && (length(x$id) != nx)) stop("'x' and 'y' and 'id' must all be same length") if (!is.null(x$id)) x$id <- as.integer(x$id) if (!is.null(x$id.lengths) && (sum(x$id.lengths) != nx)) stop("'x' and 'y' and 'id.lengths' must specify same overall length") if (!is.null(x$id.lengths)) x$id.lengths <- as.integer(x$id.lengths) if (is.null(x$id) && is.null(x$id.lengths)) { if (length(x$x) != 4L) stop("must have exactly 4 control points") } else { if (is.null(x$id)) { n <- length(x$id.lengths) id <- rep(1L:n, x$id.lengths) } else { id <- x$id } xper <- split(x$x, id) if (any(lengths(xper) != 4L)) stop("must have exactly 4 control points per Bezier curve") } if (!(is.null(x$arrow) || inherits(x$arrow, "arrow"))) stop("invalid 'arrow' argument") x } makeContent.beziergrob <- function(x) { splinegrob(x) } xDetails.beziergrob <- function(x, theta) { xDetails(splinegrob(x), theta) } yDetails.beziergrob <- function(x, theta) { yDetails(splinegrob(x), theta) } widthDetails.beziergrob <- function(x) { widthDetails(splinegrob(x)) } heightDetails.beziergrob <- function(x) { heightDetails(splinegrob(x)) } bezierGrob <- function(x=c(0, 0.5, 1, 0.5), y=c(0.5, 1, 0.5, 0), id=NULL, id.lengths=NULL, default.units="npc", arrow=NULL, name=NULL, gp=gpar(), vp=NULL) { if (!is.unit(x)) x <- unit(x, default.units) if (!is.unit(y)) y <- unit(y, default.units) grob(x=x, y=y, id=id, id.lengths=id.lengths, arrow=arrow, name=name, gp=gp, vp=vp, cl="beziergrob") } grid.bezier <- function(...) { grid.draw(bezierGrob(...)) } bezierPoints <- function(x) { sg <- splinegrob(x) # splinegrob() does not make use of x$vp sg$vp <- x$vp xsplinePoints(sg) } ###################################### # CIRCLE primitive ###################################### validDetails.circle <- function(x) { if (!is.unit(x$x) || !is.unit(x$y) || !is.unit(x$r)) stop("'x', 'y', and 'r' must be units") x } drawDetails.circle <- function(x, recording=TRUE) { grid.Call.graphics(L_circle, x$x, x$y, x$r) } xDetails.circle <- function(x, theta) { bounds <- grid.Call(L_circleBounds, x$x, x$y, x$r, theta) if (is.null(bounds)) unit(0.5, "npc") else unit(bounds[1L], "inches") } yDetails.circle <- function(x, theta) { bounds <- grid.Call(L_circleBounds, x$x, x$y, x$r, theta) if (is.null(bounds)) unit(0.5, "npc") else unit(bounds[2L], "inches") } widthDetails.circle <- function(x) { bounds <- grid.Call(L_circleBounds, x$x, x$y, x$r, 0) if (is.null(bounds)) unit(0, "inches") else unit(bounds[3L], "inches") } heightDetails.circle <- function(x) { bounds <- grid.Call(L_circleBounds, x$x, x$y, x$r, 0) if (is.null(bounds)) unit(0, "inches") else unit(bounds[4L], "inches") } circleGrob <- function(x=0.5, y=0.5, r=0.5, default.units="npc", name=NULL, gp=gpar(), vp=NULL) { if (!is.unit(x)) x <- unit(x, default.units) if (!is.unit(y)) y <- unit(y, default.units) if (!is.unit(r)) r <- unit(r, default.units) grob(x=x, y=y, r=r, name=name, gp=gp, vp=vp, cl="circle") } grid.circle <- function(x=0.5, y=0.5, r=0.5, default.units="npc", name=NULL, gp=gpar(), draw=TRUE, vp=NULL) { cg <- circleGrob(x=x, y=y, r=r, default.units=default.units, name=name, gp=gp, vp=vp) if (draw) grid.draw(cg) invisible(cg) } ###################################### # RECT primitive ###################################### validDetails.rect <- function(x) { if (!is.unit(x$x) || !is.unit(x$y) || !is.unit(x$width) || !is.unit(x$height)) stop("'x', 'y', 'width', and 'height' must be units") valid.just(x$just) if (!is.null(x$hjust)) x$hjust <- as.numeric(x$hjust) if (!is.null(x$vjust)) x$vjust <- as.numeric(x$vjust) x } drawDetails.rect <- function(x, recording=TRUE) { grid.Call.graphics(L_rect, x$x, x$y, x$width, x$height, resolveHJust(x$just, x$hjust), resolveVJust(x$just, x$vjust)) } xDetails.rect <- function(x, theta) { bounds <- grid.Call(L_rectBounds, x$x, x$y, x$width, x$height, resolveHJust(x$just, x$hjust), resolveVJust(x$just, x$vjust), theta) if (is.null(bounds)) unit(0.5, "npc") else unit(bounds[1L], "inches") } yDetails.rect <- function(x, theta) { bounds <- grid.Call(L_rectBounds, x$x, x$y, x$width, x$height, resolveHJust(x$just, x$hjust), resolveVJust(x$just, x$vjust), theta) if (is.null(bounds)) unit(0.5, "npc") else unit(bounds[2L], "inches") } widthDetails.rect <- function(x) { bounds <- grid.Call(L_rectBounds, x$x, x$y, x$width, x$height, resolveHJust(x$just, x$hjust), resolveVJust(x$just, x$vjust), 0) if (is.null(bounds)) unit(0, "inches") else unit(bounds[3L], "inches") } heightDetails.rect <- function(x) { bounds <- grid.Call(L_rectBounds, x$x, x$y, x$width, x$height, resolveHJust(x$just, x$hjust), resolveVJust(x$just, x$vjust), 0) if (is.null(bounds)) unit(0, "inches") else unit(bounds[4L], "inches") } rectGrob <- function(x=unit(0.5, "npc"), y=unit(0.5, "npc"), width=unit(1, "npc"), height=unit(1, "npc"), just="centre", hjust=NULL, vjust=NULL, default.units="npc", name=NULL, gp=gpar(), vp=NULL) { if (!is.unit(x)) x <- unit(x, default.units) if (!is.unit(y)) y <- unit(y, default.units) if (!is.unit(width)) width <- unit(width, default.units) if (!is.unit(height)) height <- unit(height, default.units) grob(x=x, y=y, width=width, height=height, just=just, hjust=hjust, vjust=vjust, name=name, gp=gp, vp=vp, cl="rect") } grid.rect <- function(x=unit(0.5, "npc"), y=unit(0.5, "npc"), width=unit(1, "npc"), height=unit(1, "npc"), just="centre", hjust=NULL, vjust=NULL, default.units="npc", name=NULL, gp=gpar(), draw=TRUE, vp=NULL) { rg <- rectGrob(x=x, y=y, width=width, height=height, just=just, hjust=hjust, vjust=vjust, default.units=default.units, name=name, gp=gp, vp=vp) if (draw) grid.draw(rg) invisible(rg) } ###################################### # RASTER primitive ###################################### validDetails.rastergrob <- function(x) { if (!(is.raster(x$raster) || inherits(x$raster, "nativeRaster"))) x$raster <- as.raster(x$raster) if (!is.unit(x$x) || !is.unit(x$y) || (!is.null(x$width) && !is.unit(x$width)) || (!is.null(x$height) && !is.unit(x$height))) stop("'x', 'y', 'width', and 'height' must be units") valid.just(x$just) if (!is.null(x$hjust)) x$hjust <- as.numeric(x$hjust) if (!is.null(x$vjust)) x$vjust <- as.numeric(x$vjust) x } resolveRasterSize <- function(x) { if (is.null(x$width)) { if (is.null(x$height)) { rasterRatio <- dim(x$raster)[1]/dim(x$raster)[2] vpWidth <- convertWidth(unit(1, "npc"), "inches", valueOnly=TRUE) vpHeight <- convertHeight(unit(1, "npc"), "inches", valueOnly=TRUE) vpRatio <- vpHeight/vpWidth if (rasterRatio > vpRatio) { x$height <- unit(vpHeight, "inches") x$width <- unit(vpHeight*dim(x$raster)[2]/dim(x$raster)[1], "inches") } else { x$width <- unit(vpWidth, "inches") x$height <- unit(vpWidth*dim(x$raster)[1]/dim(x$raster)[2], "inches") } } else { h <- convertHeight(x$height, "inches", valueOnly=TRUE) x$width <- unit(h*dim(x$raster)[2]/dim(x$raster)[1], "inches") } } else { if (is.null(x$height)) { w <- convertWidth(x$width, "inches", valueOnly=TRUE) x$height <- unit(w*dim(x$raster)[1]/dim(x$raster)[2], "inches") } } x } drawDetails.rastergrob <- function(x, recording=TRUE) { # At this point resolve NULL width/height based on # image dimensions x <- resolveRasterSize(x) if (is.null(x$width)) { if (is.null(x$height)) { rasterRatio <- dim(x$raster)[1]/dim(x$raster)[2] vpWidth <- convertWidth(unit(1, "npc"), "inches", valueOnly=TRUE) vpHeight <- convertHeight(unit(1, "npc"), "inches", valueOnly=TRUE) vpRatio <- vpHeight/vpWidth if (rasterRatio > vpRatio) { x$height <- unit(vpHeight, "inches") x$width <- unit(vpHeight*dim(x$raster)[2]/dim(x$raster)[1], "inches") } else { x$width <- unit(vpWidth, "inches") x$height <- unit(vpWidth*dim(x$raster)[1]/dim(x$raster)[2], "inches") } } else { h <- convertHeight(x$height, "inches", valueOnly=TRUE) x$width <- unit(h*dim(x$raster)[2]/dim(x$raster)[1], "inches") } } else { if (is.null(x$height)) { w <- convertWidth(x$width, "inches", valueOnly=TRUE) x$height <- unit(w*dim(x$raster)[1]/dim(x$raster)[2], "inches") } } grid.Call.graphics(L_raster, x$raster, x$x, x$y, x$width, x$height, resolveHJust(x$just, x$hjust), resolveVJust(x$just, x$vjust), x$interpolate) } xDetails.rastergrob <- function(x, theta) { x <- resolveRasterSize(x) bounds <- grid.Call(L_rectBounds, x$x, x$y, x$width, x$height, resolveHJust(x$just, x$hjust), resolveVJust(x$just, x$vjust), theta) if (is.null(bounds)) unit(0.5, "npc") else unit(bounds[1L], "inches") } yDetails.rastergrob <- function(x, theta) { x <- resolveRasterSize(x) bounds <- grid.Call(L_rectBounds, x$x, x$y, x$width, x$height, resolveHJust(x$just, x$hjust), resolveVJust(x$just, x$vjust), theta) if (is.null(bounds)) unit(0.5, "npc") else unit(bounds[2L], "inches") } widthDetails.rastergrob <- function(x) { x <- resolveRasterSize(x) bounds <- grid.Call(L_rectBounds, x$x, x$y, x$width, x$height, resolveHJust(x$just, x$hjust), resolveVJust(x$just, x$vjust), 0) if (is.null(bounds)) unit(0, "inches") else unit(bounds[3L], "inches") } heightDetails.rastergrob <- function(x) { x <- resolveRasterSize(x) bounds <- grid.Call(L_rectBounds, x$x, x$y, x$width, x$height, resolveHJust(x$just, x$hjust), resolveVJust(x$just, x$vjust), 0) if (is.null(bounds)) unit(0, "inches") else unit(bounds[4L], "inches") } rasterGrob <- function(image, x=unit(0.5, "npc"), y=unit(0.5, "npc"), width=NULL, height=NULL, just="centre", hjust=NULL, vjust=NULL, interpolate=TRUE, default.units="npc", name=NULL, gp=gpar(), vp=NULL) { if (inherits(image, "nativeRaster")) raster <- image else raster <- as.raster(image) if (!is.unit(x)) x <- unit(x, default.units) if (!is.unit(y)) y <- unit(y, default.units) if (!is.null(width) && !is.unit(width)) width <- unit(width, default.units) if (!is.null(height) && !is.unit(height)) height <- unit(height, default.units) grob(raster=raster, x=x, y=y, width=width, height=height, just=just, hjust=hjust, vjust=vjust, interpolate=interpolate, name=name, gp=gp, vp=vp, cl="rastergrob") } grid.raster <- function(image, x=unit(0.5, "npc"), y=unit(0.5, "npc"), width=NULL, height=NULL, just="centre", hjust=NULL, vjust=NULL, interpolate=TRUE, default.units="npc", name=NULL, gp=gpar(), vp=NULL) { rg <- rasterGrob(image, x=x, y=y, width=width, height=height, just=just, hjust=hjust, vjust=vjust, interpolate=interpolate, default.units=default.units, name=name, gp=gp, vp=vp) grid.draw(rg) } ###################################### # TEXT primitive ###################################### validDetails.text <- function(x) { if (!is.language(x$label)) x$label <- as.character(x$label) if (!is.unit(x$x) || !is.unit(x$y)) stop("'x' and 'y' must be units") x$rot <- as.numeric(x$rot) if (!all(is.finite(x$rot)) || length(x$rot) == 0) stop("invalid 'rot' value") valid.just(x$just) if (!is.null(x$hjust)) x$hjust <- as.numeric(x$hjust) if (!is.null(x$vjust)) x$vjust <- as.numeric(x$vjust) x$check.overlap <- as.logical(x$check.overlap) x } drawDetails.text <- function(x, recording=TRUE) { grid.Call.graphics(L_text, as.graphicsAnnot(x$label), x$x, x$y, resolveHJust(x$just, x$hjust), resolveVJust(x$just, x$vjust), x$rot, x$check.overlap) } xDetails.text <- function(x, theta) { bounds <- grid.Call(L_textBounds, as.graphicsAnnot(x$label), x$x, x$y, resolveHJust(x$just, x$hjust), resolveVJust(x$just, x$vjust), x$rot, theta) if (is.null(bounds)) unit(0.5, "npc") else unit(bounds[1L], "inches") } yDetails.text <- function(x, theta) { bounds <- grid.Call(L_textBounds, as.graphicsAnnot(x$label), x$x, x$y, resolveHJust(x$just, x$hjust), resolveVJust(x$just, x$vjust), x$rot, theta) if (is.null(bounds)) unit(0.5, "npc") else unit(bounds[2L], "inches") } widthDetails.text <- function(x) { bounds <- grid.Call(L_textBounds, as.graphicsAnnot(x$label), x$x, x$y, resolveHJust(x$just, x$hjust), resolveVJust(x$just, x$vjust), x$rot, 0) if (is.null(bounds)) unit(0, "inches") else unit(bounds[3L], "inches") } heightDetails.text <- function(x) { bounds <- grid.Call(L_textBounds, as.graphicsAnnot(x$label), x$x, x$y, resolveHJust(x$just, x$hjust), resolveVJust(x$just, x$vjust), x$rot, 0) if (is.null(bounds)) unit(0, "inches") else unit(bounds[4L], "inches") } ascentDetails.text <- function(x) { if (length(x$label) == 1) { metrics <- grid.Call(L_stringMetric, as.graphicsAnnot(x$label)) unit(metrics[[1]], "inches") } else { heightDetails(x) } } descentDetails.text <- function(x) { if (length(x$label) == 1) { metrics <- grid.Call(L_stringMetric, as.graphicsAnnot(x$label)) unit(metrics[[2]], "inches") } else { unit(0, "inches") } } textGrob <- function(label, x=unit(0.5, "npc"), y=unit(0.5, "npc"), just="centre", hjust=NULL, vjust=NULL, rot=0, check.overlap=FALSE, default.units="npc", name=NULL, gp=gpar(), vp=NULL) { if (!is.unit(x)) x <- unit(x, default.units) if (!is.unit(y)) y <- unit(y, default.units) grob(label=label, x=x, y=y, just=just, hjust=hjust, vjust=vjust, rot=rot, check.overlap=check.overlap, name=name, gp=gp, vp=vp, cl="text") } grid.text <- function(label, x=unit(0.5, "npc"), y=unit(0.5, "npc"), just="centre", hjust=NULL, vjust=NULL, rot=0, check.overlap=FALSE, default.units="npc", name=NULL, gp=gpar(), draw=TRUE, vp=NULL) { tg <- textGrob(label=label, x=x, y=y, just=just, hjust=hjust, vjust=vjust, rot=rot, check.overlap=check.overlap, default.units=default.units, name=name, gp=gp, vp=vp) if (draw) grid.draw(tg) invisible(tg) } ###################################### # POINTS primitive ###################################### valid.pch <- function(pch) { if (length(pch) == 0L) stop("zero-length 'pch'") if (is.null(pch)) pch <- 1L else if (!is.character(pch)) pch <- as.integer(pch) pch } validDetails.points <- function(x) { if (!is.unit(x$x) || !is.unit(x$y) || !is.unit(x$size)) stop("'x', 'y' and 'size' must be units") if (length(x$x) != length(x$y)) stop("'x' and 'y' must be 'unit' objects and have the same length") x$pch <- valid.pch(x$pch) x } drawDetails.points <- function(x, recording=TRUE) { grid.Call.graphics(L_points, x$x, x$y, x$pch, x$size) } # FIXME: does not take into account the size of the symbols xDetails.points <- function(x, theta) { bounds <- grid.Call(L_locnBounds, x$x, x$y, theta) if (is.null(bounds)) unit(0.5, "npc") else unit(bounds[1L], "inches") } yDetails.points <- function(x, theta) { bounds <- grid.Call(L_locnBounds, x$x, x$y, theta) if (is.null(bounds)) unit(0.5, "npc") else unit(bounds[2L], "inches") } widthDetails.points <- function(x) { bounds <- grid.Call(L_locnBounds, x$x, x$y, 0) if (is.null(bounds)) unit(0, "inches") else unit(bounds[3L], "inches") } heightDetails.points <- function(x) { bounds <- grid.Call(L_locnBounds, x$x, x$y, 0) if (is.null(bounds)) unit(0, "inches") else unit(bounds[4L], "inches") } pointsGrob <- function(x=stats::runif(10), y=stats::runif(10), pch=1, size=unit(1, "char"), default.units="native", name=NULL, gp=gpar(), vp=NULL) { if (!is.unit(x)) x <- unit(x, default.units) if (!is.unit(y)) y <- unit(y, default.units) grob(x=x, y=y, pch=pch, size=size, name=name, gp=gp, vp=vp, cl="points") } grid.points <- function(x=stats::runif(10), y=stats::runif(10), pch=1, size=unit(1, "char"), default.units="native", name=NULL, gp=gpar(), draw=TRUE, vp=NULL) { pg <- pointsGrob(x=x, y=y, pch=pch, size=size, default.units=default.units, name=name, gp=gp, vp=vp) if (draw) grid.draw(pg) invisible(pg) } ###################################### # CLIP primitive ###################################### validDetails.clip <- function(x) { if (!is.unit(x$x) || !is.unit(x$y) || !is.unit(x$width) || !is.unit(x$height)) stop("'x', 'y', 'width', and 'height' must be units") if (length(x$x) > 1 || length(x$y) > 1 || length(x$width) > 1 || length(x$height) > 1) stop("'x', 'y', 'width', and 'height' must all be units of length 1") valid.just(x$just) if (!is.null(x$hjust)) x$hjust <- as.numeric(x$hjust) if (!is.null(x$vjust)) x$vjust <- as.numeric(x$vjust) x } drawDetails.clip <- function(x, recording=TRUE) { grid.Call.graphics(L_clip, x$x, x$y, x$width, x$height, resolveHJust(x$just, x$hjust), resolveVJust(x$just, x$vjust)) } clipGrob <- function(x=unit(0.5, "npc"), y=unit(0.5, "npc"), width=unit(1, "npc"), height=unit(1, "npc"), just="centre", hjust=NULL, vjust=NULL, default.units="npc", name=NULL, vp=NULL) { if (!is.unit(x)) x <- unit(x, default.units) if (!is.unit(y)) y <- unit(y, default.units) if (!is.unit(width)) width <- unit(width, default.units) if (!is.unit(height)) height <- unit(height, default.units) grob(x=x, y=y, width=width, height=height, just=just, hjust=hjust, vjust=vjust, name=name, vp=vp, cl="clip") } grid.clip <- function(...) { grid.draw(clipGrob(...)) } ###################################### # NULL primitive ###################################### validDetails.null <- function(x) { if (!is.unit(x$x) || !is.unit(x$y)) stop("'x' and 'y' must be units") if (length(x$x) > 1 || length(x$y) > 1) stop("'x' and 'y' must all be units of length 1") x } drawDetails.null <- function(x, recording=TRUE) { # Deliberate null op. # NOTE: nothing will go on the graphics engine DL # This is ok I think because these grobs are only # useful on the grid DL (for other grid code to query # their size or location). } xDetails.null <- function(x, theta) { bounds <- grid.Call(L_locnBounds, x$x, x$y, theta) if (is.null(bounds)) unit(0.5, "npc") else unit(bounds[1L], "inches") } yDetails.null <- function(x, theta) { bounds <- grid.Call( L_locnBounds, x$x, x$y, theta) if (is.null(bounds)) unit(0.5, "npc") else unit(bounds[2L], "inches") } # Deliberately ZERO widthDetails.null <- function(x) { unit(0, "inches") } heightDetails.null <- function(x) { unit(0, "inches") } # A grob with GUARANTEED zero-width # also GUARANTEED NOT to draw anything nullGrob <- function(x=unit(0.5, "npc"), y=unit(0.5, "npc"), default.units="npc", name=NULL, vp=NULL) { if (!is.unit(x)) x <- unit(x, default.units) if (!is.unit(y)) y <- unit(y, default.units) grob(x=x, y=y, name=name, vp=vp, cl="null") } # Convenient way to get nullGrob on the grid display list grid.null <- function(...) { grid.draw(nullGrob(...)) }