blob: dda7bdb442769a9022a0b88269fa86837f4c9370 [file] [log] [blame]
# File src/library/grid/R/debug.R
# Part of the R package, https://www.R-project.org
#
# Copyright (C) 1995-2016 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/
### Label grobs in a scene
labelGrob <- function(grob, recurse, curdepth, depth, labelfun, ...) {
UseMethod("labelGrob")
}
# The default grob label needs to do some calculations
# on sizes so need a drawDetails method to get the
# calculations right
drawDetails.groblabel <- function(x, ...) {
gw <- convertWidth(grobWidth(x$grob), "inches", valueOnly=TRUE)
gh <- convertHeight(grobHeight(x$grob), "inches", valueOnly=TRUE)
grid.rect(grobX(x$grob, "west"), grobY(x$grob, "south"),
unit(gw, "inches"), unit(gh, "inches"),
just=c("left", "bottom"), gp=x$gp)
tw <- convertWidth(stringWidth(x$grob$name), "inches", valueOnly=TRUE)
th <- convertHeight(stringHeight(x$grob$name), "inches", valueOnly=TRUE)
eps <- .01
# If grob is REALLY short, draw horiz at normal cex
if (gh < eps) {
rot <- 0
cex <- 1
# If grob is REALLY thin, draw vertical at normal cex
} else if (gw < eps) {
rot <- 90
cex <- 1
} else {
gratio <- gh/gw
if (gratio > 1 && tw > gw) {
rot <- 90
wratio <- th/gw
hratio <- tw/gh
} else {
rot <- 0
wratio <- tw/gw
hratio <- th/gh
}
if (wratio > 1 || hratio > 1) {
cex <- 1/max(wratio, hratio)
} else {
cex <- 1
}
}
if (is.null(x$gp)) {
x$gp <- gpar(cex=cex)
} else {
if (is.null(x$gp$cex))
x$gp$cex <- cex
}
if (is.null(x$otherArgs$rot))
x$otherArgs$rot <- rot
do.call("grid.text", c(list(label=x$grob$name,
x=grobX(x$grob, "north"),
y=grobY(x$grob, "west"),
gp=x$gp),
x$otherArgs))
}
grobLabel <- function(grob,
gp=gpar(col=rgb(1, 0, 0, .5),
fill=rgb(1, 0, 0, .2)),
...) {
grob(grob=grob, gp=gp, otherArgs=list(...),
cl="groblabel")
}
labelGrob.grob <- function(grob, recurse, curdepth, depth, labelfun, ...) {
if (is.null(depth) || curdepth %in% depth) {
gTree(children=gList(grob,
labelfun(grob, ...)),
# Name new gTree same as old grob so that
# setGrob() approach works below
# (when 'gPath' is specified)
name=grob$name)
} else {
grob
}
}
labelGrob.gTree <- function(grob, recurse, curdepth, depth, labelfun, ...) {
if (recurse) {
newChildren <- do.call("gList",
lapply(grob$children,
labelGrob,
recurse, curdepth + 1, depth,
labelfun, ...))
grob <- setChildren(grob, newChildren)
}
if (is.null(depth) || curdepth %in% depth) {
gTree(children=gList(grob,
labelfun(grob, ...)),
name=grob$name)
} else {
grob
}
}
showGrob <- function(x=NULL,
gPath=NULL, strict=FALSE, grep=FALSE,
recurse=TRUE, depth=NULL,
labelfun=grobLabel, ...) {
if (is.null(x)) {
# Label all or part of current scene
# The grid display list is NOT affected
# To remove labels use grid.redraw()
if (is.null(gPath)) {
# Show the current scene
dl <- grid.Call(C_getDisplayList)[1L : grid.Call(C_getDLindex)]
grid.newpage(recording=FALSE)
# -1 because first element on DL is ROOT viewport
lapply(dl[-1],
function(y) {
# Modify the grob to add a label
if (is.grob(y))
y <- labelGrob(y, recurse, 1, depth, labelfun, ...)
# Draw either the original object or the modified grob
grid.draw(y, recording=FALSE)
})
} else {
# Only label the bit of the current scene specified by gPath
grobToLabel <- grid.get(gPath, strict=strict, grep=grep)
# NOTE: have to 'wrap' because otherwise the grobs in the
# captured scene have been altered
scene <- grid.grab(wrap=TRUE)
modScene <- setGrob(scene, gPath,
labelGrob(grobToLabel, recurse, 1, depth,
labelfun, ...),
strict=strict, grep=grep)
grid.newpage(recording=FALSE)
grid.draw(modScene, recording=FALSE)
}
} else {
# Assume grob is not current scene so start a new page
grid.newpage()
grid.draw(x)
showGrob(NULL, gPath, strict, grep, recurse, depth, labelfun, ...)
}
invisible()
}
#############
# Labelling viewports in a scene
#############
# FIXME: some of this code for vpLists and vpStacks and vpTrees
# assumes that the components of a vpList or vpStack or the
# vpTree parent can ONLY be a viewport (when in fact they can
# also be a vpList, vpStack, or vpTree!)
# Label a viewport
# Get physical aspect ratio of vp to determine whether to rotate
# Shrink text to fit in vp
# (Assumes that we are currently occupying 'vp'
# so that conversions are correct)
labelVP <- function(vp, col) {
vw <- convertWidth(unit(1, "npc"), "inches", valueOnly=TRUE)
vh <- convertHeight(unit(1, "npc"), "inches", valueOnly=TRUE)
tw <- convertWidth(stringWidth(vp$name), "inches", valueOnly=TRUE)
th <- convertHeight(stringHeight(vp$name), "inches", valueOnly=TRUE)
eps <- .01
# If viewport is REALLY short, draw horiz at normal cex
if (vh < eps) {
rot <- 0
cex <- 1
# If viewport is REALLY thin, draw vertical at normal cex
} else if (vw < eps) {
rot <- 90
cex <- 1
} else {
vratio <- vh/vw
if (vratio > 1 && tw > vw) {
rot <- 90
wratio <- th/vw
hratio <- tw/vh
} else {
rot <- 0
wratio <- tw/vw
hratio <- th/vh
}
if (wratio > 1 || hratio > 1) {
cex <- 1/max(wratio, hratio)
} else {
cex <- 1
}
}
# Violate any clipping that is in effect
pushViewport(viewport(clip="off"))
grid.text(vp$name, rot=rot, gp=gpar(col=col, cex=cex))
upViewport()
}
# Draw a "viewport"
drawVP <- function(vp, curDepth, depth, col, fill, label) {
UseMethod("drawVP")
}
drawVP.viewport <- function(vp, curDepth, depth, col, fill, label) {
if (vp$name != "ROOT" &&
(is.null(depth) || curDepth %in% depth)) {
pushViewport(vp)
colIndex <- (curDepth - 1) %% length(col) + 1
fillIndex <- (curDepth - 1) %% length(fill) + 1
grid.rect(gp=gpar(col=col[colIndex], fill=fill[fillIndex]))
if (label)
labelVP(vp, col[colIndex])
upViewport()
}
}
drawVP.vpPath <- function(vp, curDepth, depth, col, fill, label) {
if (is.null(depth) || curDepth %in% depth) {
downViewport(vp)
colIndex <- (curDepth - 1) %% length(col) + 1
fillIndex <- (curDepth - 1) %% length(fill) + 1
grid.rect(gp=gpar(col=col[colIndex], fill=fill[fillIndex]))
if (label)
labelVP(vp, col[colIndex])
upViewport(depth(vp))
}
}
drawVP.vpList <- function(vp, curDepth, depth, col, fill, label) {
lapply(vp, drawVP, curDepth, depth, col, fill, label)
}
drawVP.vpStack <- function(vp, curDepth, depth, col, fill, label) {
d <- depth(vp)
for (i in 1:length(vp)) {
this <- vp[[i]]
drawVP(this, curDepth, depth, col, fill, label)
curDepth <- curDepth + depth(this)
pushViewport(this)
}
upViewport(d)
}
drawVP.vpTree <- function(vp, curDepth, depth, col, fill, label) {
if (vp$parent$name == "ROOT") {
lapply(vp$children, drawVP, curDepth, depth, col, fill, label)
} else {
pushViewport(vp$parent)
if (is.null(depth) || curDepth %in% depth) {
colIndex <- (curDepth - 1) %% length(col) + 1
fillIndex <- (curDepth - 1) %% length(fill) + 1
grid.rect(gp=gpar(col=col[colIndex], fill=fill[fillIndex]))
if (label) {
drawLabel <- is.null(vp$children) ||
(!is.null(depth) &&
curDepth == max(depth))
if (drawLabel)
labelVP(vp$parent, col[colIndex])
}
}
lapply(vp$children, drawVP, curDepth + 1, depth, col, fill, label)
upViewport()
}
}
# Draw all viewports in same viewport
showVP <- function(vp, newpage, cvpt, depth, col, fill,
label) {
# If we've started a new page, we'll need the old
# viewport tree to navigate within
if (newpage) {
pushViewport(cvpt)
# "-1" for "ROOT"
upViewport(depth(cvpt) - 1)
}
# Work off a vpTree, so convert vp if it's a vpPath
showingPath <- inherits(vp, "vpPath")
if (showingPath) {
path <- vp
downViewport(path)
vp <- current.vpTree(all=FALSE)
upViewport(1)
}
drawVP(vp, 1, depth, col, fill, label)
if (showingPath)
# "-1" because we went down the path then back up 1 originally
upViewport(depth(path) - 1)
invisible()
}
# Convert a "viewport" to a set of vpPaths
leafPaths <- function(vp) {
UseMethod("leafPaths")
}
leafPaths.viewport <- function(vp) {
if (vp$name == "ROOT")
NULL
else
vp$name
}
leafPaths.vpList <- function(vp) {
unlist(lapply(vp, leafPaths))
}
leafPaths.vpStack <- function(vp) {
pathList <- lapply(vp, leafPaths)
for (i in 1:length(pathList)) {
if (i > 1) {
pathList[[i]] <- paste(pathList[[i - 1]],
pathList[[i]],
sep=.grid.pathSep)
}
}
unlist(pathList)
}
leafPaths.vpTree <- function(vp) {
if (is.null(vp$children)) {
if (vp$parent$name == "ROOT")
NULL
else
vp$parent$name
} else {
pathList <- lapply(vp$children, leafPaths)
if (vp$parent$name == "ROOT") {
unlist(pathList)
} else {
paste(vp$parent$name,
unlist(pathList),
sep=.grid.pathSep)
}
}
}
leafPaths.vpPath <- function(vp) {
as.character(vp)
}
# Draw a vpPath
drawPath <- function(path, depth, col, fill, label) {
n <- depth(path)
for (i in 1:n) {
downViewport(path[i])
if (is.null(depth) || i %in% depth) {
colIndex <- (i - 1) %% length(col) + 1
fillIndex <- (i - 1) %% length(fill) + 1
grid.rect(gp=gpar(col=col[colIndex], fill=fill[fillIndex]))
if (label) {
if (is.null(depth))
drawLabel <- i == n
else
drawLabel <- i == min(n, max(depth))
if (drawLabel)
labelVP(current.viewport(), col[colIndex])
}
}
}
upViewport(n)
}
# Draw each leaf in separate viewports
# FIXME: allow control over number of rows and cols
# NOTE: this does NOT leave its viewports hanging around after
showVPmatrix <- function(vp, cvpt, depth, col, fill,
label, # Only the leaf viewports are labelled
nrow, ncol) {
# Work off a vpPath, so convert vp if it's a "viewport"
if (is.viewport(vp)) {
paths <- leafPaths(vp)
} else {
# Should not happen
stop("how did we get here?")
}
firstPath <- 0
while (length(paths) - firstPath > 0) {
if (firstPath > 0)
grid.newpage()
pushViewport(viewport(layout=grid.layout(nrow, ncol)))
for (i in 1:nrow) {
for (j in 1:ncol) {
theLeaf <- firstPath + (i - 1)*nrow + j
if (theLeaf <= length(paths)) {
thePath <- vpPath(paths[theLeaf])
pushViewport(viewport(layout.pos.row=i,
layout.pos.col=j))
grid.rect(gp=gpar(col="grey80"))
# We may need the old vpTree to navigate within
# if 'vp' is a vpStack, or something similar, that
# contains a vpPath
if (!is.null(cvpt$children)) {
pushViewport(cvpt$children)
upViewport(depth(cvpt) - 1)
}
# Now push the viewport we are showing
pushViewport(vp)
upViewport(depth(vp))
# Now go to the particular viewport we
# are going to show
drawPath(thePath, depth, col, fill, label)
# Pop our placement within the layout
popViewport()
}
}
}
popViewport()
firstPath <- firstPath + nrow*ncol
}
}
showViewport <- function(vp=NULL, recurse=TRUE, depth=NULL,
newpage=FALSE, leaves=FALSE,
col=rgb(0, 0, 1, .2), fill=rgb(0, 0, 1, .1),
label=TRUE, nrow=3, ncol=nrow) {
cvpt <- current.vpTree()
if (is.null(vp))
vp <- cvpt
if (newpage == FALSE && leaves == TRUE)
stop("must start new page if showing leaves separately")
if (newpage) {
grid.newpage()
}
if (!recurse)
depth <- 1
if (leaves) {
# Special case of showing vpPath (i.e., only one viewport)
# Ignores nrow & ncol
if (inherits(vp, "vpPath"))
showVP(vp, TRUE, cvpt, depth, col, fill, label)
else
showVPmatrix(vp, cvpt, depth, col, fill, label, nrow, ncol)
} else {
showVP(vp, newpage, cvpt, depth, col, fill, label)
}
invisible()
}