| # File src/library/grid/R/viewport.R |
| # Part of the R package, https://www.R-project.org |
| # |
| # Copyright (C) 1995-2013 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/ |
| |
| |
| initvpAutoName <- function() { |
| index <- 0 |
| function() { |
| index <<- index + 1 |
| paste0("GRID.VP.", index) |
| } |
| } |
| |
| vpAutoName <- initvpAutoName() |
| |
| # NOTE: The order of the elements in viewports and pushedvps are |
| # VERY IMPORTANT because the C code accesses them using constant |
| # indices (i.e., if you change the order here the world will end! |
| valid.viewport <- function(x, y, width, height, just, |
| gp, clip, |
| xscale, yscale, angle, |
| layout, layout.pos.row, layout.pos.col, |
| name) { |
| if (length(x) > 1 || length(y) > 1 || |
| length(width) > 1 || length(height) > 1) |
| stop("'x', 'y', 'width', and 'height' must all be units of length 1") |
| if (!is.gpar(gp)) |
| stop("invalid 'gp' value") |
| if (!is.logical(clip)) |
| clip <- switch(as.character(clip), |
| on=TRUE, |
| off=NA, |
| inherit=FALSE, |
| stop("invalid 'clip' value")) |
| # Ensure both 'xscale' and 'yscale' are numeric (brute force defense) |
| xscale <- as.numeric(xscale) |
| yscale <- as.numeric(yscale) |
| if (!is.numeric(xscale) || length(xscale) != 2 || |
| any(!is.finite(xscale)) || diff(xscale) == 0) |
| stop("invalid 'xscale' in viewport") |
| if (!is.numeric(yscale) || length(yscale) != 2 || |
| any(!is.finite(yscale)) || diff(yscale) == 0) |
| stop("invalid 'yscale' in viewport") |
| if (!is.numeric(angle) || length(angle) != 1 || |
| !is.finite(angle)) |
| stop("invalid 'angle' in viewport") |
| if (!(is.null(layout) || is.layout(layout))) |
| stop("invalid 'layout' in viewport") |
| if (!is.null(layout.pos.row)) { |
| layout.pos.row <- as.integer(range(layout.pos.row)) |
| if (any(!is.finite(layout.pos.row))) |
| stop("invalid 'layout.pos.row' in viewport") |
| } |
| if (!is.null(layout.pos.col)) { |
| layout.pos.col <- as.integer(range(layout.pos.col)) |
| if (any(!is.finite(layout.pos.col))) |
| stop("invalid 'layout.pos.col' in viewport") |
| } |
| # If name is NULL then we give it a default |
| # Otherwise it should be a valid R name |
| if (is.null(name)) |
| name <- vpAutoName() |
| # Put all the valid things first so that are found quicker |
| vp <- list(x = x, y = y, width = width, height = height, |
| justification = just, |
| gp = gp, |
| clip = clip, |
| xscale = xscale, |
| yscale = yscale, |
| angle = angle, |
| layout = layout, |
| layout.pos.row = layout.pos.row, |
| layout.pos.col = layout.pos.col, |
| valid.just = valid.just(just), |
| valid.pos.row = layout.pos.row, |
| valid.pos.col = layout.pos.col, |
| name=name) |
| class(vp) <- "viewport" |
| vp |
| } |
| |
| # When a viewport is pushed, an internal copy is stored along |
| # with plenty of additional information relevant to the state |
| # at the time of being pushed (this is all used to return to this |
| # viewport without having to repush it) |
| pushedvp <- function(vp) { |
| # NOTE that this function is only called from C code: |
| # either directly from L_setviewport() or indirectly from initVP() |
| # via grid.top.level.vp() |
| # vp$gpar and vp$parentgpar are both set previously in push.vp.viewport() |
| pvp <- c(vp, list(trans = NULL, |
| widths = NULL, |
| heights = NULL, |
| width.cm = NULL, |
| height.cm = NULL, |
| rotation = NULL, |
| cliprect = NULL, |
| parent = NULL, |
| # Children of this pushedvp will be stored |
| # in an environment |
| children = new.env(hash=TRUE, parent=baseenv()), |
| # Initial value of 0 means that the viewport will |
| # be pushed "properly" the first time, calculating |
| # transformations, etc ... |
| devwidthcm = 0, |
| devheightcm = 0)) |
| class(pvp) <- c("pushedvp", class(vp)) |
| pvp |
| } |
| |
| vpFromPushedvp <- function(pvp) { |
| vp <- pvp[c("x", "y", "width", "height", |
| "justification", "gp", "clip", |
| "xscale", "yscale", "angle", |
| "layout", "layout.pos.row", "layout.pos.col", |
| "valid.just", "valid.pos.row", "valid.pos.col", |
| "name")] |
| class(vp) <- "viewport" |
| vp |
| } |
| |
| as.character.viewport <- function(x, ...) { |
| paste0("viewport[", x$name, "]") |
| } |
| |
| as.character.vpList <- function(x, ...) { |
| paste0("(", paste(vapply(x, as.character, ""), collapse=", "), ")") |
| } |
| |
| as.character.vpStack <- function(x, ...) { |
| paste(vapply(x, as.character, ""), collapse="->") |
| } |
| |
| as.character.vpTree <- function(x, ...) { |
| paste(x$parent, x$children, sep="->") |
| } |
| |
| print.viewport <- function(x, ...) { |
| cat(as.character(x), "\n") |
| invisible(x) |
| } |
| |
| width.details.viewport <- function(x) { |
| absolute.size(x$width) |
| } |
| |
| height.details.viewport <- function(x) { |
| absolute.size(x$height) |
| } |
| |
| # How many "levels" in viewport object |
| depth <- function(x, ...) { |
| UseMethod("depth") |
| } |
| |
| depth.viewport <- function(x, ...) { |
| 1 |
| } |
| |
| depth.vpList <- function(x, ...) { |
| # When pushed, the last element of the vpList is pushed last |
| # so we are left whereever that leaves us |
| depth(x[[length(x)]], ...) |
| } |
| |
| depth.vpStack <- function(x, ...) { |
| # Elements in the stack may be vpStacks or vpLists or vpTrees |
| # so need to sum all the depths |
| sum(sapply(x, depth, ..., simplify=TRUE)) |
| } |
| |
| depth.vpTree <- function(x, ...) { |
| # When pushed, the last element of the vpTree$children is |
| # pushed last so we are left wherever that leaves us |
| depth(x$parent, ...) + depth(x$children[[length(x$children)]], ...) |
| } |
| |
| depth.path <- function(x, ...) { |
| x$n |
| } |
| |
| #################### |
| # Accessors |
| #################### |
| |
| viewport.layout <- function(vp) { |
| vp$layout |
| } |
| |
| viewport.transform <- function(vp) { |
| .Defunct("current.transform") |
| } |
| |
| #################### |
| # Public Constructor |
| #################### |
| viewport <- function(x = unit(0.5, "npc"), |
| y = unit(0.5, "npc"), |
| width = unit(1, "npc"), |
| height = unit(1, "npc"), |
| default.units = "npc", |
| just = "centre", |
| gp = gpar(), |
| clip = "inherit", |
| # FIXME: scales are only linear at the moment |
| xscale = c(0, 1), |
| yscale = c(0, 1), |
| angle = 0, |
| # Layout for arranging children of this viewport |
| layout = NULL, |
| # Position of this viewport in parent's layout |
| layout.pos.row = NULL, |
| layout.pos.col = NULL, |
| # This is down here to avoid breaking |
| # existing code |
| name=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) |
| valid.viewport(x, y, width, height, just, |
| gp, clip, xscale, yscale, angle, |
| layout, layout.pos.row, layout.pos.col, name) |
| } |
| |
| is.viewport <- function(vp) { |
| inherits(vp, "viewport") |
| } |
| |
| ############# |
| # Some classes derived from viewport |
| ############# |
| |
| viewportorpath <- function(x) { |
| is.viewport(x) || inherits(x, "vpPath") |
| } |
| |
| vpListFromList <- function(vps) { |
| if (all(sapply(vps, viewportorpath, simplify=TRUE))) { |
| class(vps) <- c("vpList", "viewport") |
| vps |
| } else { |
| stop("only viewports allowed in 'vpList'") |
| } |
| } |
| |
| # Viewports will be pushed in parallel |
| vpList <- function(...) { |
| vps <- list(...) |
| vpListFromList(vps) |
| } |
| |
| # Viewports will be pushed in series |
| vpStack <- function(...) { |
| vps <- list(...) |
| if (all(sapply(vps, viewportorpath, simplify=TRUE))) { |
| class(vps) <- c("vpStack", "viewport") |
| vps |
| } else { |
| stop("only viewports allowed in 'vpStack'") |
| } |
| } |
| |
| # Viewports will be pushed as a tree |
| vpTree <- function(parent, children) { |
| if (viewportorpath(parent) && inherits(children, "vpList")) { |
| tree <- list(parent=parent, children=children) |
| class(tree) <- c("vpTree", "viewport") |
| tree |
| } else { |
| stop("'parent' must be a viewport and 'children' must be a 'vpList' in 'vpTree'") |
| } |
| } |
| |
| # A function for setting all gpars for vpStack/List/Tree |
| # Used in size.R |
| setvpgpar <- function(vp) { |
| UseMethod("setvpgpar") |
| } |
| |
| setvpgpar.viewport <- function(vp) { |
| if (!is.null(vp$gp)) |
| set.gpar(vp$gp) |
| } |
| |
| setvpgpar.vpStack <- function(vp) { |
| lapply(vp, setvpgpar) |
| } |
| |
| setvpgpar.vpList <- function(vp) { |
| setvpgpar(vp[[length(vp)]]) |
| } |
| |
| setvpgpar.vpTree <- function(vp) { |
| setvpgpar(vp$parent) |
| setvpgpar(vp$children) |
| } |
| |
| ############# |
| # Functions for creating "paths" of viewport names |
| ############# |
| .grid.pathSep <- "::" |
| |
| vpPathFromVector <- function(names) { |
| if (any(bad <- !is.character(names))) |
| stop(ngettext(sum(bad), |
| "invalid viewport name", |
| "invalid viewport names"), |
| domain = NA) |
| names <- unlist(strsplit(names, .grid.pathSep)) |
| n <- length(names) |
| if (n < 1) |
| stop("a viewport path must contain at least one viewport name") |
| path <- list(path=if (n==1) NULL else |
| paste(names[seq_len(n-1L)], collapse=.grid.pathSep), |
| name=names[n], |
| n=n) |
| class(path) <- c("vpPath", "path") |
| path |
| } |
| |
| vpPath <- function(...) { |
| names <- c(...) |
| vpPathFromVector(names) |
| } |
| |
| as.character.path <- function(x, ...) { |
| if (x$n == 1) |
| x$name |
| else |
| paste(x$path, x$name, sep=.grid.pathSep) |
| } |
| |
| print.path <- function(x, ...) { |
| cat(as.character(x), "\n") |
| invisible(x) |
| } |
| |
| `[.vpPath` <- function(x, index, ...) { |
| names <- unlist(strsplit(as.character(x), .grid.pathSep))[index] |
| vpPathFromVector(names) |
| } |
| |
| # Explode path$path |
| explode <- function(x) { |
| UseMethod("explode") |
| } |
| |
| explode.character <- function(x) { |
| unlist(strsplit(x, .grid.pathSep)) |
| } |
| |
| explode.path <- function(x) { |
| if (x$n == 1) |
| x$name |
| else |
| c(explode(x$path), x$name) |
| } |
| |
| |
| ############# |
| # Some handy viewport functions |
| ############# |
| |
| # Create a viewport with margins given in number of lines |
| plotViewport <- function(margins=c(5.1, 4.1, 4.1, 2.1), ...) { |
| margins <- rep(as.numeric(margins), length.out=4) |
| viewport(x=unit(margins[2L], "lines"), |
| width=unit(1, "npc") - unit(sum(margins[c(2,4)]), "lines"), |
| y=unit(margins[1L], "lines"), |
| height=unit(1, "npc") - unit(sum(margins[c(1,3)]), "lines"), |
| just=c("left", "bottom"), |
| ...) |
| } |
| |
| # Create a viewport from data |
| # If xscale not specified then determine from x |
| # If yscale not specified then determine from y |
| dataViewport <- function(xData = NULL, yData = NULL, |
| xscale = NULL, yscale = NULL, extension = 0.05, ...) |
| { |
| extension <- rep(extension, length.out = 2) |
| if (is.null(xscale)) { |
| if (is.null(xData)) |
| stop("must specify at least one of 'x' or 'xscale'") |
| xscale <- extendrange(xData, f = extension[1L]) |
| } |
| if (is.null(yscale)) { |
| if (is.null(yData)) |
| stop("must specify at least one of 'y' or 'yscale'") |
| yscale <- extendrange(yData, f = extension[2L]) |
| } |
| viewport(xscale = xscale, yscale = yscale, ...) |
| } |