blob: 37acd1d5575b7f76520573927c17f1092d9dd0f5 [file] [log] [blame]
# 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, ...)
}