How to apply a clipping mask to geom in a ggplot? How to apply a clipping mask to geom in a ggplot? r r

How to apply a clipping mask to geom in a ggplot?


The following is a grid solution, but very much a work-around. It shows how to apply a non-rectangular clipping region to a ggplot, so that one set of points in your plot is clipped. You weren't too far wrong in your attempt. A couple of points to note:

  1. You need to grid.force() the ggplotGrob object so the grid can see the grobs.
  2. Do not define the ggplot grob as a clipping path - the clipping path is the polygon.
  3. The clipping path is applied to the points grob within the plot panel of the ggplot. This means that other objects in the plot panel, the panel background and grid lines, do not get clipped. Only the data points are clipped.

I've added a blue line to the plot to show that the line too does not need to be clipped; but can be clipped if desired.

There are also commented lines of code that, when uncommented, will draw the clipping region, and move the grid lines and points to the front (that is, in front of the darker grey clipping region).

library(ggplot2)library(gridSVG)library(grid)# Open the graphics devicegridsvg(name = "test.svg")# Create a plotp <- ggplot(diamonds[1:300, ], aes(carat, price)) +        geom_point(aes(colour = cut)) +       geom_line(data = data.frame(x = c(.3, .9), y = c(500, 2500)), aes(x,y), col = "skyblue", size = 2)g <- ggplotGrob(p) # Store the plot as a grobg = grid.force(g)  # So that grid sees all grobsgrid.draw(g)       # Draw the plot# Define the clipping pathpg <- polygonGrob(c(.7, 0, 0, 1, 1),                  c(0, .7, 1, 1, 0))# The clipping path can be nearly any shape you desire. # Try this for a circular region# pg = circleGrob(x = .5, y = .6, r = .5)cp <- clipPath(pg)# Add the clipping path to the points grob.# That is, only the points inside the polygon will be visible,# but the background and grid lines will not be clipped. # Nor will the blue line be clipped.# grid.ls(g)     # names of the grobsseekViewport(grep("panel.[0-9]", grid.ls(g)$name, value = TRUE))grid.clipPath("points", cp, grep = TRUE)   # To clip the blue line, uncomment the next line# grid.clipPath("GRID.polyline", cp, grep = TRUE)       # To show the clipping region,    # uncomment the next two lines.# showcp = editGrob(pg, gp = gpar(fill = rgb(0, 0, 0, 0.05), col = "transparent"))# grid.draw(showcp)# And to move the grid lines, remaining data points, and blue line in front of the clipping region,# uncomment the next five lines# panel = grid.get("panel", grep = TRUE)   # Get the panel, and remove the background grob# panel = removeGrob(panel, "background", grep = TRUE)# grid.remove("points", grep = TRUE)     # Remove points and grid lines from the rendered plot# grid.remove("line", grep = TRUE, global = TRUE)# grid.draw(panel)     # Draw the edited panel - on top of the clipping region # Turn off the graphics devicedev.off()# Find text.svg in your working directory




Edit Defining the clipping region using the coordinate system in which the data points were drawn.

library(ggplot2)library(gridSVG)library(grid)# Open the graphics devicegridsvg(name = "test.svg")# Create a plotp <- ggplot(diamonds[1:300, ], aes(carat, price)) +        geom_point(aes(colour = cut)) +       geom_line(data = data.frame(x = c(.3, .9), y = c(500, 2500)), aes(x,y), col = "skyblue", size = 2)g <- ggplotGrob(p) # Store the plot as a grobg = grid.force(g)  # So that grid sees all grobsgrid.draw(g)       # Draw the plot# Get axis limits (including any expansion)axis.limits = summarise_layout(ggplot_build(p))[1, c('xmin', 'xmax', 'ymin', 'ymax')]# Find the 'panel' viewport,# then push to a new viewport, # one that exactly overlaps the 'panel' viewport,# but with limits on the x and y scales that are the same# as the limits for the original ggplot. seekViewport(grep("panel.[0-9]", grid.ls(g)$name, value = TRUE))pushViewport(dataViewport(xscale = axis.limits[1, 1:2],                          yscale = axis.limits[1, 3:4]))# Define the clipping path pg <- polygonGrob(x = c(.6,   0.3, .3,   .8,   1.2),                    y = c(500, 1500, 2900, 2900, 1500),                    default.units="native")cp <- clipPath(pg)# Add the clipping path to the points grob.# That is, only the points inside the polygon will be visible,# but the background and grid lines will not be clipped. # Nor will the blue line be clipped.# grid.ls(g)     # names of the grobsgrid.clipPath("points", cp, grep = TRUE)   # To clip the blue line, uncomment the next line grid.clipPath("GRID.polyline", cp, grep = TRUE)       # To show the clipping region.  showcp = editGrob(pg, gp = gpar(fill = rgb(0, 0, 0, 0.05), col = "transparent")) grid.draw(showcp)# And to move the grid lines and remaining data points in front of the clipping region. panel = grid.get("panel", grep = TRUE)   # Get the panel, and remove the background grob panel = removeGrob(panel, "background", grep = TRUE) grid.remove("points", grep = TRUE)     # Remove points and grid lines from the rendered plot grid.remove("line", grep = TRUE, global = TRUE) grid.draw(panel)     # Draw the edited panel - on top of the clipping region # Turn off the graphics devicedev.off()# Find text.svg in your working directory


Since you are starting out with a ggplot object, it may be simpler to create the mask itself as a geom layer, rather than convert everything to grob and work in the grid system there.

The geom_polypath() function from the ggpolypath package can be used here. Unlike the standard geom_polygon in ggplot2, it is able to handle polygons with holes (see vignette):

# sample data frame for clipping. The first four x & y coordinates are for the outer ends;# the next four are for the hole in the polygon.clipping.df <- data.frame(x = c(0, 1.5, 1.5, 0, 0.2, 1, 0.7, 0.3),                          y = c(0, 0, 3000, 3000, 250, 2000, 2800, 1500),                          hole = rep(c(FALSE, TRUE), each = 4),                          group = rep(c("1", "2"), each = 4))library(ggpolypath)p +  geom_polypath(data = clipping.df,                aes(x = x, y = y, group = group),                colour = NA, fill = "black", alpha = 0.5,                inherit.aes = FALSE) +  scale_x_continuous(expand = c(0, 0)) + # don't show edges beyond the extent  scale_y_continuous(expand = c(0, 0))   # of the polygon

plot