# High level functions for escaping clipping paths and masks
popContext <- function(n = 1) {
if (n < 1)
stop("Must pop at least one level of context")
# Not even giving the option of configuring a name because
# it should not be used in any serious manner
grid.draw(grob(n = n, cl = "popContext"))
}
# We have nothing to draw here, just rip out the SVG device to
# start unwinding the tree
primToDev.popContext <- function(x, dev) {
svgPopContext(x$n, dev@dev)
}
svgPopContext <- function(n, svgdev) {
# IMPORTANT - clipGrobs are left alone!
# In the case where we have reached something we know
# is not a reference, then we don't need to unwind further.
# This is because viewports and grobs (in particular clipGrobs)
# will be treated separately to clipping paths and masks.
parentIsPushContext <- function() {
id <- xmlGetAttr(svgDevParent(svgdev), "id")
cids <- get("contextNames", envir = .gridSVGEnv)
id %in% cids
}
contextLevels <- get("contextLevels", envir = .gridSVGEnv)
cl <- tail(contextLevels, 1)
if (n > cl) {
warning("An attempt was made to pop more contexts than possible, ignoring extras")
n <- cl
}
# In the case where a gTree has a popContext, don't do anything because
# it would affect any remaining children that are yet to be drawn.
# An example:
# pushClipPath()
# -> draw(gTree)
# -> *draw*, *draw*, *popClipPath*, *draw* <- pop will be ignored here
# -> leave(gTree)
while (parentIsPushContext() && n > 0) {
svgDevChangeParent(xmlParent(svgDevParent(svgdev)), svgdev)
cl <- cl - 1
n <- n - 1
}
contextLevels[length(contextLevels)] <- cl
assign("contextLevels", contextLevels, envir = .gridSVGEnv)
}
###
###
### CLIPPING PATHS
###
###
# Alias for convenient popping of a clipping path
popClipPath <- function() {
popContext()
}
pushClipPath <- function(clippath = NULL, label = NULL,
name = NULL, draw = TRUE) {
if (is.null(label) & is.null(clippath)) {
stop("At least one of 'label' or 'clippath' must be supplied")
} else if (is.null(label)) {
label <- getNewLabel("gridSVG.clipPath")
registerClipPath(label, clippath)
} else if (is.null(clippath)) {
checkForDefinition(label)
} else {
checkExistingDefinition(label)
registerClipPath(label, clippath)
}
cp <- grid::grob(referenceLabel = label, name = name, cl = "clipPath")
class(cp) <- unique(c("pushClipPath", class(cp)))
if (draw)
grid.draw(cp)
invisible(cp)
}
# High level functions for applying clipping paths to existing grobs
grid.clipPath <- function(path, clippath = NULL, label = NULL,
group = TRUE, redraw = FALSE,
strict = FALSE, grep = FALSE, global = FALSE) {
if (is.null(label) & is.null(clippath)) {
stop("At least one of 'label' or 'clippath' must be supplied")
} else if (is.null(label)) {
label <- getNewLabel("gridSVG.clipPath")
registerClipPath(label, clippath)
clippath <- NULL # use the ref from now on
} else if (is.null(clippath)) {
checkForDefinition(label)
} else {
checkExistingDefinition(label)
registerClipPath(label, clippath)
clippath <- NULL # use the ref from now on
}
grobApply(path, function(path) {
grid.set(path, clipPathGrob(grid.get(path), clippath = clippath,
label = label, group = group),
redraw = redraw)
}, strict = strict, grep = grep, global = global)
invisible()
}
clipPathGrob <- function(x, clippath = NULL, label = NULL, group = TRUE) {
if (is.null(label) & is.null(clippath)) {
stop("At least one of 'label' or 'clippath' must be supplied")
} else if (is.null(label)) {
label <- getNewLabel("gridSVG.clipPath")
registerClipPath(label, clippath)
} else if (is.null(clippath)) {
checkForDefinition(label)
} else {
checkExistingDefinition(label)
registerClipPath(label, clippath)
}
x$referenceLabel <- c(x$referenceLabel, label)
x$clipPathLabel <- label
x$clipPathGroup <- group
class(x) <- unique(c("pathClipped.grob", class(x)))
x
}
clipPath <- function(grob) {
if (! is.grob(grob))
stop("'grob' must be a grid grob")
cp <- list(grob = grob)
class(cp) <- "clipPath"
cp
}
registerClipPath <- function(label, clippath) {
checkExistingDefinition(label)
refDefinitions <- get("refDefinitions", envir = .gridSVGEnv)
if (! inherits(clippath, "clipPath"))
stop("'clippath' must be a 'clipPath' object")
# Note: grob must be forced to fix the definition of the grob
# at the time of registration
defList <- list(label = label,
id = getID(label, "ref"),
grob = grid.force(clippath$grob),
vp = getAbsoluteVp())
class(defList) <- "clipPathDef"
refDefinitions[[label]] <- defList
assign("refDefinitions", refDefinitions, envir = .gridSVGEnv)
assign("refUsageTable",
rbind(get("refUsageTable", envir = .gridSVGEnv),
data.frame(label = label, used = FALSE,
stringsAsFactors = FALSE)),
envir = .gridSVGEnv)
# Return NULL invisibly because we don't actually care what the
# definition looks like until gridSVG tries to draw it.
invisible()
}
primToDev.pathClipped.grob <- function(x, dev) {
setLabelUsed(x$referenceLabel)
label <- getLabelID(x$clipPathLabel)
cpg <- garnishGrob(x, "clip-path" = paste0("url(#", label, ")"),
group = x$clipPathGroup)
# Now need to remove all clip path appearances in the class list.
# This is safe because repeated clipping just clobbers existing
# attributes.
cl <- class(cpg)
class(cpg) <- cl[cl != "pathClipped.grob"]
primToDev(cpg, dev)
}
drawDef.clipPathDef <- function(x, dev) {
grob <- x$grob
# This is always going to be true because we basically assume that
# referenced content is fixed and therefore the names don't really
# matter.
if (get("use.gPaths", envir = .gridSVGEnv))
grob$name <- paste(x$label, grob$name,
sep = getSVGoption("gPath.sep"))
# Start clipPath
devStartClipPath(list(name = x$id), NULL, dev)
# Draw grob
grobToDev(grid.force(grob), dev)
# Close clipPath, open group
devEndClipPath(list(name = x$id), NULL, dev)
}
primToDev.clipPath <- function(x, dev) {
setLabelUsed(x$referenceLabel)
devStartClipPathGroup(devGrob(x, dev), NULL, dev)
}
devGrob.clipPath <- function(x, dev) {
list(name = getID(x$name, "grob"),
cp = x$referenceLabel,
classes = x$classes)
}
svgStartGrobClipPathGroup <- function(id = NULL, cp = NULL,
classes = NULL,
svgdev = svgDevice()) {
clipPathID <- paste0("url(#", getLabelID(cp), ")")
attrs <- list(id = prefixName(id),
svgClassList(classes),
"clip-path" = clipPathID)
attrs <- attrList(attrs)
cp <- newXMLNode("g", attrs = attrs,
parent = svgDevParent(svgdev))
svgDevChangeParent(cp, svgdev)
}
svgStartGrobClipPath <- function(id = NULL, svgdev = svgDevice()) {
cp <- newXMLNode("clipPath", attrs = list(id = id),
parent = svgDevParent(svgdev))
svgDevChangeParent(cp, svgdev)
}
svgEndGrobClipPath <- function(svgdev = svgDevice()) {
# First need to collect all children and filter out unwanted content
clippath <- svgDevParent(svgdev)
nodelist <- flattenClippedSVG(clippath)
# Wipe out all children, then add in the ones we want
removeChildren(clippath, kids = xmlChildren(clippath))
xmlChildren(clippath) <- nodelist
# Go up one level from clipPath to defs
svgDevChangeParent(xmlParent(svgDevParent(svgdev)), svgdev)
}
flattenClippedSVG <- function(node) {
# Mostly taken from spec, only adding in what we use though
# Omitted - animation elements, 'use', 'ellipse', 'line'
validElements <- c("animate", "animateTransform", "circle", "path",
"polygon", "polyline", "rect", "text")
clipPathID <- xmlGetAttr(node, "id")
subset <- getNodeSet(node,
paste0("//svg:clipPath[@id = '", clipPathID, "']",
"/descendant-or-self::*/svg:",
validElements, collapse = " | "),
c(svg = "http://www.w3.org/2000/svg"))
for (i in 1:length(subset)) {
el <- subset[[i]]
name <- xmlName(el)
if (name == "text") {
# We know that the structure is:
# <g ....>
# <g scale>
# <text ...>
p <- xmlParent(el)
gp <- xmlParent(p)
gpattrs <- xmlAttrs(gp)
gpattrs["transform"] <- paste(gpattrs["transform"],
xmlAttrs(p)["transform"])
# There might also be a rotation present on the text itself
if ("transform" %in% names(xmlAttrs(el)))
gpattrs["transform"] <- paste(gpattrs["transform"],
xmlAttrs(el)["transform"])
xmlAttrs(el) <- gpattrs
}
}
subset
}
###
###
### MASKING
###
###
# Alias for popping out of a masking context
popMask <- function() {
popContext()
}
pushMask <- function(mask = NULL, label = NULL, name = NULL, draw = TRUE) {
if (is.null(label) & is.null(mask)) {
stop("At least one of 'label' or 'mask' must be supplied")
} else if (is.null(label)) {
label <- getNewLabel("gridSVG.mask")
registerMask(label, mask)
} else if (is.null(mask)) {
checkForDefinition(label)
} else {
checkExistingDefinition(label)
registerMask(label, mask)
}
m <- grid::grob(referenceLabel = label, name = name, cl = "mask")
class(m) <- unique(c("pushMask", class(m)))
if (draw)
grid.draw(m)
invisible(m)
}
# High level functions for applying opacity masks to grobs
grid.mask <- function(path, mask = NULL, label = NULL,
group = TRUE, redraw = FALSE,
strict = FALSE, grep = FALSE, global = FALSE) {
if (is.null(label) & is.null(mask)) {
stop("At least one of 'label' or 'mask' must be supplied")
} else if (is.null(label)) {
label <- getNewLabel("gridSVG.mask")
registerMask(label, mask)
mask <- NULL # use the ref from now on
} else if (is.null(mask)) {
checkForDefinition(label)
} else {
checkExistingDefinition(label)
registerMask(label, mask)
mask <- NULL # use the ref from now on
}
grobApply(path, function(path) {
grid.set(path, maskGrob(grid.get(path), mask = mask,
label = label, group = group),
redraw = redraw)
}, strict = strict, grep = grep, global = global)
invisible()
}
maskGrob <- function(x, mask = NULL, label = NULL, group = TRUE) {
if (is.null(label) & is.null(mask)) {
stop("At least one of 'label' or 'mask' must be supplied")
} else if (is.null(label)) {
label <- getNewLabel("gridSVG.mask")
registerMask(label, mask)
} else if (is.null(mask)) {
checkForDefinition(label)
} else {
checkExistingDefinition(label)
registerMask(label, mask)
}
x$referenceLabel <- c(x$referenceLabel, label)
# Attribs to be garnished *at draw time*. In particular needs to be
# done because the label ID is not known until then, because of things
# like prefixes and separators.
x$maskLabel <- label
x$maskGroup <- group
class(x) <- unique(c("masked.grob", class(x)))
x
}
mask <- function(grob,
x = unit(0.5, "npc"), y = unit(0.5, "npc"),
width = unit(1, "npc"), height = unit(1, "npc"),
default.units = "npc",
just = "centre", hjust = NULL, vjust = 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)
mask <- list(grob = grob,
x = x, y = y,
width = width, height = height,
just = just, hjust = hjust, vjust = vjust)
class(mask) <- "mask"
mask
}
registerMask <- function(label, mask = NULL, ...) {
checkExistingDefinition(label)
refDefinitions <- get("refDefinitions", envir = .gridSVGEnv)
if (is.null(mask)) {
mask <- gridSVG::mask(...)
} else if (! inherits(mask, "mask")) {
stop("'mask' must be a 'mask' object")
}
if (is.null(mask$grob))
stop("A grob must be given for a mask definition")
# Now convert *at time of definition* to absolute units (inches)
loc <- leftbottom(mask$x, mask$y, mask$width, mask$height,
mask$just, mask$hjust, mask$vjust, NULL)
x <- loc$x
y <- loc$y
width <- convertWidth(mask$width, "inches")
height <- convertHeight(mask$height, "inches")
# Note: grob must be forced to fix the definition of the grob
# at the time of registration
defList <- list(label = label,
id = getID(label, "ref"),
x = x,
y = y,
width = width,
height = height,
grob = grid.force(mask$grob),
vp = getAbsoluteVp())
class(defList) <- "maskDef"
refDefinitions[[label]] <- defList
assign("refDefinitions", refDefinitions, envir = .gridSVGEnv)
assign("refUsageTable",
rbind(get("refUsageTable", envir = .gridSVGEnv),
data.frame(label = label, used = FALSE,
stringsAsFactors = FALSE)),
envir = .gridSVGEnv)
# Return NULL invisibly because we don't actually care what the
# definition looks like until gridSVG tries to draw it.
invisible()
}
primToDev.masked.grob <- function(x, dev) {
setLabelUsed(x$referenceLabel)
label <- getLabelID(x$maskLabel)
mg <- garnishGrob(x, "mask" = paste0("url(#", label, ")"),
group = x$maskGroup)
# Now need to remove all mask appearances in the class list.
# This is safe because repeated masking just clobbers existing
# attributes.
cl <- class(mg)
class(mg) <- cl[cl != "masked.grob"]
primToDev(mg, dev)
}
primToDev.mask <- function(x, dev) {
setLabelUsed(x$referenceLabel)
devStartMaskGroup(list(name = getID(x$name, "grob"),
mask = x$referenceLabel,
classes = x$classes), NULL, dev)
}
drawDef.maskDef <- function(x, dev) {
grob <- x$grob
# This is always going to be true because we basically assume that
# referenced content is fixed and therefore the names don't really
# matter.
if (get("use.gPaths", envir = .gridSVGEnv))
grob$name <- paste(x$label, grob$name,
sep = getSVGoption("gPath.sep"))
# Start mask
devStartMask(devGrob(x, dev), NULL, dev)
# Draw grob
grobToDev(grid.force(grob), dev)
# Close mask
devEndMask(devGrob(x, dev), NULL, dev)
}
devGrob.maskDef <- function(x, dev) {
list(x=cx(x$x, dev),
y=cy(x$y, dev),
width=cw(x$width, dev),
height=ch(x$height, dev),
name=x$id)
}
svgStartMaskGroup <- function(id = NULL, mask = NULL,
classes = NULL,
svgdev = svgDevice()) {
maskID <- paste0("url(#", getLabelID(mask), ")")
attrs <- attrList(list(id = prefixName(id),
svgClassList(classes),
mask = maskID))
m <- newXMLNode("g", attrs = attrs,
parent = svgDevParent(svgdev))
svgDevChangeParent(m, svgdev)
}
svgStartMask <- function(id = NULL, x=0, y=0, width=0, height=0,
svgdev = svgDevice()) {
mask <- newXMLNode("mask", attrs = list(id = id,
x = round(x, 2), y = round(y, 2),
width = round(width, 2),
height = round(height, 2),
maskUnits = "userSpaceOnUse"),
parent = svgDevParent(svgdev))
svgDevChangeParent(mask, svgdev)
}
svgEndMask <- function(svgdev = svgDevice()) {
# Go up one levels from mask to defs
svgDevChangeParent(xmlParent(svgDevParent(svgdev)), svgdev)
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.