如何在ggplot中将剪贴蒙版应用于geom?

时间:2022-06-09 23:12:33

I am trying to apply a clipping mask to a geom from a ggplot to mask part of the data, but keep the axis, the grid, other geoms and the legend visible. I do not want to create a specific plot, and therefore I am not looking for a work-around with polygons masking some parts of the plot.

我正在尝试将剪贴蒙版应用于ggplot中的geom以遮盖部分数据,但保持轴,网格,其他geom和图例可见。我不想创建一个特定的情节,因此我不是在寻找一个处理阴影部分图的多边形。

This is the kind of design I would like to emulate (the mask, not necessarily theme, I now how to do that):

这是我想要模仿的设计(面具,不一定是主题,我现在该怎么做):

如何在ggplot中将剪贴蒙版应用于geom?

(source)

See also this example

另请参见此示例

One could argue that I could filter the data that is not contained in the polygon that defines the mask. But, while it works for point, and can work for polygon/line-like objects, it does for rasters (the border would not exactly follow non-vertical or non-horizontal lines). So I tried the following:

有人可能会说我可以过滤未定义掩码的多边形中包含的数据。但是,虽然它适用于点,并且可以用于多边形/线状对象,但它适用于栅格(边框不会完全遵循非垂直或非水平线)。所以我尝试了以下内容:

library(ggplot2)
library(gridSVG)
library(grImport)

# Create a plot
p <- ggplot(diamonds[1:300,], aes(carat, price)) + geom_point(aes(colour = cut))

# And a clipping mask
pg <- polygonGrob(c(.7, 0, 0, 1, 1),
              c(0, .7, 1, 1, 0))
cp <- clipPath(pg)

I was able to use the packages gridSVG to define a clipping mask, but I have difficulties applying it on a ggplot object, even after extracting the grob (see resource here) with ggplotGrob(). I was not able to apply the clipping mask to the grob:

我能够使用包gridSVG来定义剪贴蒙版,但是我很难在ggplot对象上应用它,即使在使用ggplotGrob()提取grob(参见资源)之后也是如此。我无法将剪贴蒙版应用于grob:

g <-  ggplotGrob(p) # store the plot as a grob

registerClipPath("mask", cp)
g_clipped <- clipPath(g)

gridsvg(name = "test_c2.svg")
grid.draw(clipPathGrob(g_clipped, cp)$grob)
dev.off()

My intuition was that the g_clipped should be plotted, but I couldn't grid.draw() it, since its a clipPath object. And the grid.draw() line written here show the plot not masked. I think I don't quite grasp how the clipPath objects function.

我的直觉是应该绘制g_clipped,但我不能grid.draw()它,因为它是一个clipPath对象。并且此处写入的grid.draw()行显示未隐藏的图。我想我不太了解clipPath对象的功能。

The function grobify() sounds like it could help for an alternative appraoch without gridSVG, see details here, but I do not understand the quite minimalistic documentation.

函数grobify()听起来像没有gridSVG可以帮助替代appraoch,请参阅此处的详细信息,但我不明白相当简约的文档。

Since I cannot even apply the clipping mask to the whole plot, I am far for my objective.

由于我甚至无法将剪贴蒙版应用于整个情节,因此我的目标很明显。

If you can help me understand how to apply the clipping mask from gridSVGor have an alternative solution to apply a clipping mask to specific geoms, please let me know.

如果你可以帮我理解如何应用gridSVGor的剪贴蒙版有一个替代解决方案,将剪贴蒙版应用于特定的geoms,请告诉我。

2 个解决方案

#1


2  

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:

以下是网格解决方案,但非常适合解决方案。它显示了如何将非矩形剪切区域应用于ggplot,以便剪切绘图中的一组点。你的尝试并没有太大的错误。有几点需要注意:

  1. You need to grid.force() the ggplotGrob object so the grid can see the grobs.
  2. 你需要grid.force()ggplotGrob对象,以便网格可以看到grobs。

  3. Do not define the ggplot grob as a clipping path - the clipping path is the polygon.
  4. 不要将ggplot grob定义为剪切路径 - 剪切路径是多边形。

  5. 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.
  6. 剪切路径应用于ggplot的绘图面板内的点grob。这意味着绘图面板中的其他对象,面板背景和网格线不会被剪裁。仅剪切数据点。

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 device
gridsvg(name = "test.svg")

# Create a plot
p <- 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 grob


g = grid.force(g)  # So that grid sees all grobs
grid.draw(g)       # Draw the plot

# Define the clipping path
pg <- 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 grobs
seekViewport(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 device
dev.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 device
gridsvg(name = "test.svg")

# Create a plot
p <- 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 grob


g = grid.force(g)  # So that grid sees all grobs
grid.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 grobs

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. 
 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 device
dev.off()

# Find text.svg in your working directory

#2


2  

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.

由于您开始使用ggplot对象,因此将掩码本身创建为geom层可能更简单,而不是将所有内容转换为grob并在那里的网格系统中工作。

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):

可以在此处使用ggpolypath包中的geom_polypath()函数。与ggplot2中的标准geom_polygon不同,它能够处理带孔的多边形(参见晕影):

# 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

如何在ggplot中将剪贴蒙版应用于geom?

#1


2  

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:

以下是网格解决方案,但非常适合解决方案。它显示了如何将非矩形剪切区域应用于ggplot,以便剪切绘图中的一组点。你的尝试并没有太大的错误。有几点需要注意:

  1. You need to grid.force() the ggplotGrob object so the grid can see the grobs.
  2. 你需要grid.force()ggplotGrob对象,以便网格可以看到grobs。

  3. Do not define the ggplot grob as a clipping path - the clipping path is the polygon.
  4. 不要将ggplot grob定义为剪切路径 - 剪切路径是多边形。

  5. 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.
  6. 剪切路径应用于ggplot的绘图面板内的点grob。这意味着绘图面板中的其他对象,面板背景和网格线不会被剪裁。仅剪切数据点。

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 device
gridsvg(name = "test.svg")

# Create a plot
p <- 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 grob


g = grid.force(g)  # So that grid sees all grobs
grid.draw(g)       # Draw the plot

# Define the clipping path
pg <- 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 grobs
seekViewport(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 device
dev.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 device
gridsvg(name = "test.svg")

# Create a plot
p <- 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 grob


g = grid.force(g)  # So that grid sees all grobs
grid.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 grobs

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. 
 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 device
dev.off()

# Find text.svg in your working directory

#2


2  

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.

由于您开始使用ggplot对象,因此将掩码本身创建为geom层可能更简单,而不是将所有内容转换为grob并在那里的网格系统中工作。

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):

可以在此处使用ggpolypath包中的geom_polypath()函数。与ggplot2中的标准geom_polygon不同,它能够处理带孔的多边形(参见晕影):

# 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

如何在ggplot中将剪贴蒙版应用于geom?