R/gradients.R

Defines functions forceGrob.gradientFilled.grob drawDef.gradientDef primToDev.gradientFilled.grob svgRadialGradient svgLinearGradient registerGradientFill flattenRadialGradient flattenLinearGradient print.gradient radialGradient linearGradient gradientFillGrob grid.gradientFill

Documented in gradientFillGrob grid.gradientFill linearGradient radialGradient registerGradientFill

# High level functions for applying gradients as fills to grobs
grid.gradientFill <- function(path, gradient = NULL, label = NULL,
                              alpha = 1, group = TRUE, redraw = FALSE,
                              strict = FALSE, grep = FALSE, global = FALSE) {
    if (is.null(gradient) & is.null(label)) {
        stop("At least one of 'gradient' or 'label' must be supplied")
    } else if (is.null(label)) {
        label <- getNewLabel("gridSVG.gradientFill")
        registerGradientFill(label, gradient)
        gradient <- NULL # use the ref from now on
    } else if (is.null(gradient)) {
        checkForDefinition(label)
    } else {
        checkExistingDefinition(label)
        registerGradientFill(label, gradient)
        gradient <- NULL # use the ref from now on
    }

    grobApply(path, function(path) {
        grid.set(path, gradientFillGrob(grid.get(path), gradient = gradient,
                                        label = label, alpha = alpha,
                                        group = group),
                 redraw = redraw)
    }, strict = strict, grep = grep, global = global)

    invisible()
}

gradientFillGrob <- function(x, gradient = NULL, label = NULL,
                             alpha = 1, group = TRUE) {
    if (is.null(gradient) & is.null(label)) {
        stop("At least one of 'gradient' or 'label' must be supplied")
    } else if (is.null(label)) {
        label <- getNewLabel("gridSVG.gradientFill")
        registerGradientFill(label, gradient)
    } else if (is.null(gradient)) {
        checkForDefinition(label)
    } else {
        checkExistingDefinition(label)
        registerGradientFill(label, gradient)
    }

    if (length(alpha) != length(label))
        alpha <- rep(alpha, length.out = length(label))

    x$referenceLabel <- c(x$referenceLabel, label)
    x$gradientFillLabel <- label
    x$gradientFillAlpha <- alpha
    x$gradientFillGroup <- group
    class(x) <- unique(c("gradientFilled.grob", class(x)))
    x
}

linearGradient <- function(col = c("black", "white"),
                           stops = seq(0, 1, length.out = length(col)),
                           gradientUnits = c("bbox", "coords"),
                           x0 = unit(0, "npc"), x1 = unit(1, "npc"),
                           y0 = unit(0, "npc"), y1 = unit(1, "npc"),
                           default.units = "npc",
                           spreadMethod = c("pad", "reflect", "repeat")) {
    # Vectorising colours & stops
    nstops <- max(length(col), length(stops))
    col <- rep(col, length.out = nstops)
    stops <- rep(stops, length.out = nstops)

    offset <- round(stops, 2)
    stopCol <- sapply(col, function(x) devColToSVG(x), USE.NAMES = FALSE)
    stopOpacity <- devColAlphaToSVG(col2rgb(col, alpha = TRUE)[4, ])

    gradientUnits <- match.arg(gradientUnits)
    spreadMethod <- match.arg(spreadMethod)
    
    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)

    # Convert gradientUnits to SVG values
    gradientUnits <- switch(gradientUnits,
                            bbox = "objectBoundingBox",
                            coords = "userSpaceOnUse")

    # Need to get npc-like values from units
    if (gradientUnits == "objectBoundingBox") {
        # Convert to npc 
        x0 <- convertX(x0, "npc", valueOnly = TRUE)
        x1 <- convertX(x1, "npc", valueOnly = TRUE)
        y0 <- convertY(y0, "npc", valueOnly = TRUE)
        y1 <- convertY(y1, "npc", valueOnly = TRUE)
    }

    grad <- list(element = "linearGradient",
                 gradientUnits = gradientUnits,
                 x1 = x0, x2 = x1,
                 y1 = y0, y2 = y1,
                 spreadMethod = spreadMethod,
                 offset = offset, stopCol = stopCol,
                 stopOpacity = stopOpacity)
    class(grad) <- c("linear.gradient", "gradient")
    grad
}

radialGradient <- function(col = c("black", "white"),
                           stops = seq(0, 1, length.out = length(col)),
                           gradientUnits = c("bbox", "coords"),
                           x = unit(0.5, "npc"), y = unit(0.5, "npc"),
                           r = unit(0.5, "npc"),
                           fx = unit(0.5, "npc"), fy = unit(0.5, "npc"),
                           default.units = "npc",
                           spreadMethod = c("pad", "reflect", "repeat")) {
    # Vectorising colours & stops
    nstops <- max(length(col), length(stops))
    col <- rep(col, length.out = nstops)
    stops <- rep(stops, length.out = nstops)

    offset <- round(stops, 2)
    stopCol <- sapply(col, function(x) devColToSVG(x), USE.NAMES = FALSE)
    stopOpacity <- devColAlphaToSVG(col2rgb(col, alpha = TRUE)[4, ])

    gradientUnits <- match.arg(gradientUnits)
    spreadMethod <- match.arg(spreadMethod)
    if (is.null(stops))
        stops <- list()
    
    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)
    if (! is.unit(fx))
        fx <- unit(fx, default.units)
    if (! is.unit(fy))
        fy <- unit(fy, default.units)

    # Convert gradientUnits to SVG values
    gradientUnits <- switch(gradientUnits,
                            bbox = "objectBoundingBox",
                            coords = "userSpaceOnUse")

    # Need to get npc-like values from units
    if (gradientUnits == "objectBoundingBox") {
        x <- convertX(x, "npc", valueOnly = TRUE)
        y <- convertY(y, "npc", valueOnly = TRUE)

        rw <- convertWidth(r, "npc", valueOnly = TRUE)
        rh <- convertHeight(r, "npc", valueOnly = TRUE)
        r <- pmin(abs(rw), abs(rh))

        fx <- convertX(fx, "npc", valueOnly = TRUE)
        fy <- convertY(fy, "npc", valueOnly = TRUE)
    }

    grad <- list(element = "radialGradient",
                 gradientUnits = gradientUnits,
                 cx = x, cy = y, r = r,
                 fx = fx, fy = fy,
                 spreadMethod = spreadMethod,
                 offset = offset, stopCol = stopCol,
                 stopOpacity = stopOpacity)
    class(grad) <- c("radial.gradient", "gradient")
    grad
}

print.gradient <- function(x, ...) {
    prln <- function(label, value) {
        cat(sprintf(paste0(label, ": %s\n"), value))
    }
    prln("Type", x$element)
    n <- length(x$offset)
    prln("Number of stops", n)
    cat("\n")
    prln("Gradient stops", "")
    for (i in 1:n) {
        cat("  ")
        cat("Offset:", x$offset[i])
        cat("  ")
        cat("Colour:", x$stopCol[i])
        cat("  ")
        cat("Opacity:", x$stopOpacity[i])
        cat("\n")
    }
    invisible(x)
}

flattenLinearGradient <- function(gradient) {
    # Flatten all locations here
    if (gradient$gradientUnits == "userSpaceOnUse") {
        offsets <- getAbsoluteOffset()
        width <- convertWidth(gradient$x2 - gradient$x1, "inches",
                              valueOnly = TRUE)
        height <- convertHeight(gradient$y2 - gradient$y1, "inches",
                                valueOnly = TRUE)
        gradient$x1 <- convertX(gradient$x1, "inches") + offsets[1]
        gradient$x2 <- convertX(gradient$x2, "inches") + offsets[1]
        gradient$y1 <- convertY(gradient$y1, "inches") + offsets[2]
        gradient$y2 <- convertY(gradient$y2, "inches") + offsets[2]
    }
    gradient
}

flattenRadialGradient <- function(gradient) {
    # Flatten all locations here
    if (gradient$gradientUnits == "userSpaceOnUse") {
        offsets <- getAbsoluteOffset()
        gradient$cx <- convertX(gradient$cx, "inches") + offsets[1]
        gradient$cy <- convertY(gradient$cy, "inches") + offsets[2]
        gradient$r <- abs(dToInches(gradient$r, NULL))
        gradient$fx <- convertX(gradient$fx, "inches") + offsets[1]
        gradient$fy <- convertY(gradient$fy, "inches") + offsets[2]
    }
    gradient
}

registerGradientFill <- function(label, gradient) {
    checkExistingDefinition(label)
    
    # Flattening all locations
    gradient <-
        if (inherits(gradient, "radial.gradient"))
            flattenRadialGradient(gradient)
        else
            flattenLinearGradient(gradient)

    gradient$label <- label
    gradient$id <- getID(label, "ref")
    class(gradient) <- "gradientDef"

    refDefinitions <- get("refDefinitions", envir = .gridSVGEnv)
    refDefinitions[[label]] <- gradient
    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()
}

svgLinearGradient <- function(def, dev) {
    svgdev <- dev@dev

    # Convert grid coords to SVG coords if we are using coordinates
    # rather than the bounding box of the referring object
    if (def$gradientUnits == "userSpaceOnUse") {
        def$x1 <- cx(def$x1, dev)
        def$x2 <- cx(def$x2, dev)
        def$y1 <- cy(def$y1, dev)
        def$y2 <- cy(def$y2, dev)
    }

    gradient <- newXMLNode("linearGradient",
        parent = svgDevParent(svgdev),
        attrs = list(id = def$id,
                     x1 = round(def$x1, 2), x2 = round(def$x2, 2),
                     y1 = round(def$y1, 2), y2 = round(def$y2, 2),
                     gradientUnits = def$gradientUnits,
                     spreadMethod = def$spreadMethod))

    svgDevChangeParent(gradient, svgdev)
}

svgRadialGradient <- function(def, dev) {
    svgdev <- dev@dev

    # Convert grid coords to SVG coords if we are using coordinates
    # rather than the bounding box of the referring object
    if (def$gradientUnits == "userSpaceOnUse") {
        def$cx <- cx(def$cx, dev)
        def$cy <- cy(def$cy, dev)
        def$r <- cd(def$r, dev)
        def$fx <- cx(def$fx, dev)
        def$fy <- cy(def$fy, dev)
    }

    gradient <- newXMLNode("radialGradient",
        parent = svgDevParent(svgdev),
        attrs = list(id = def$id,
                     cx = round(def$cx, 2), cy = round(def$cy, 2),
                     r = round(def$r, 2),
                     fx = round(def$fx, 2), fy = round(def$fy, 2),
                     gradientUnits = def$gradientUnits,
                     spreadMethod = def$spreadMethod))

    svgDevChangeParent(gradient, svgdev)
}

primToDev.gradientFilled.grob <- function(x, dev) {
    setLabelUsed(x$referenceLabel)
    label <- getLabelID(x$gradientFillLabel)
    # Allowing fill-opacity to be set by a garnish because
    # grid only knows about a colour and its opacity. If we use a
    # reference instead of a then nothing is known about the opacity.
    # We want to ensure that we can still set it, so use the garnish
    # to overwrite it.
    gf <- garnishGrob(x, fill = paste0("url(#", label, ")"),
                      "fill-opacity" = x$gradientFillAlpha,
                      group = x$gradientFillGroup)
    # Now need to remove all gradient fill appearances in the class list.
    # This is safe because repeated gradient filling just clobbers existing
    # attributes.
    cl <- class(gf)
    class(gf) <- cl[cl != "gradientFilled.grob"]
    primToDev(gf, dev)
}

drawDef.gradientDef <- function(def, dev) {
    svgdev <- dev@dev

    if (def$element == "linearGradient")
        svgLinearGradient(def, dev)
    else
        svgRadialGradient(def, dev)

    # Adding the gradient stops
    for (i in 1:length(def$offset)) {
        newXMLNode("stop",
                   attrs = list(offset = def$offset[i],
                                "stop-color" = def$stopCol[i],
                                "stop-opacity" = def$stopOpacity[i]),
                   parent = svgDevParent(svgdev))
    }

    # Going back up from the stops to the parent of the gradient
    svgDevChangeParent(xmlParent(svgDevParent(svgdev)), svgdev)
}

# Ensure the gradient fill is retained on a forced grob
forceGrob.gradientFilled.grob <- function(x) {
    y <- NextMethod()
    if (inherits(y, "forcedgrob")) {
        y$referenceLabel <- x$referenceLabel
        y$gradientFillLabel <- x$gradientFillLabel
        y$gradientFillAlpha <- x$gradientFillAlpha
        y$gradientFillGroup <- x$gradientFillGroup
        class(y) <- unique(c("gradientFilled.grob", class(y)))
    }
    y
}

Try the gridSVG package in your browser

Any scripts or data that you put into this service are public.

gridSVG documentation built on March 31, 2023, 11:17 p.m.