r - ggplot2:填充两条射线之间的区域

时间:2014-07-06 01:17:09

标签: r ggplot2

我的情节有来自原点的两条光线。我想以逆时针方向遮蔽从光线1到光线2的区域。我想我可能不得不使用geom_polygon。我希望能够为 任意两条任意光线 做到这一点,但我似乎无法弄明白。

*我想坚持使用笛卡尔坐标。

这是我的意思的一个例子:

d <- data.frame()

base <- ggplot(d) + xlim(-5, 5) + ylim(-5, 5) + geom_blank()

ray1 <- geom_segment(aes(x=0,y=0,xend=5,yend=4))
ray2 <- geom_segment(aes(x=0,y=0,xend=0,yend=5))

shading <- geom_polygon(data=data.frame(x=c(0,5,5,0), y=c(0,4,5,5)), 
  aes(x,y), fill="blue", alpha=0.2)

base + ray1 + ray2 + shading

对于这个例子,我能够通过检查获得多边形的顶点,但是我将生成几个随机的光线对,我不想每次都手动完成这个过程。

enter image description here

有什么建议吗?

4 个答案:

答案 0 :(得分:6)

这可能有点儿马车和笨重。我希望从更聪明的人那里看到更优雅的解决方案

但是,我不清楚你想如何处理边缘。见例子

shade_segs <- function(ray1, ray2, xlim = c(-5, 5), ylim = xlim) {
  ray <- data.frame(x = c(ray1[1], pmin(ray1[3], xlim[2]),
                          pmin(ray1[3], ylim[2]), ray2[3]),
                    y = c(ray2[1], ray1[4],
                          ## how to handle the edges? :
                          pmin(ray2[4], xlim[2]), pmin(ray2[4], ylim[2])))
                          # pmin(ray1[4], xlim[2]), pmin(ray2[4], ylim[2])))
  # print(ray)
  require(ggplot2)

  ggplot() + xlim(xlim[1], xlim[2]) + ylim(ylim[1], ylim[2]) +
    geom_segment(aes_string(x = ray1[1], y = ray1[2],
                     xend = ray1[3], yend = ray1[4])) +
    geom_segment(aes_string(x = ray2[1], y = ray2[2],
                     xend = ray2[3], yend = ray2[4])) +
    geom_polygon(data = ray, aes(x = x, y = y), fill = "blue", alpha = 0.2) + 
    theme_bw()
}

library(gridExtra)
## c(x0, y0, x1, y1)
l <- list(shade_segs(ray1 = c(0, 0, 5, 4), 
                     ray2 = c(0, 0, 0, 5)),
          shade_segs(ray1 = c(0, 0, 2.5, 2.5), 
                     ray2 = c(0, 0, 1, 5)),
          shade_segs(ray1 = c(0, 0, -1, 5), 
                     ray2 = c(0, 0, -.5, 5)),
          shade_segs(ray1 = c(0, 0, -3, 5), 
                     ray2 = c(0, 0, -.5, -5)))
(do.call(arrangeGrob, c(l, list(nrow = 2, ncol = 2))))

enter image description here

或者像这样:

shade_segs <- function(ray1, ray2, xlim = c(-5, 5), ylim = xlim) {
  ray <- data.frame(x = c(ray1[1], pmin(ray1[3], xlim[2]),
                          pmin(ray1[3], ylim[2]), ray2[3]),
                    y = c(ray2[1], ray1[4],
                          ## how to handle the edges? :
                          # pmin(ray2[4], xlim[2]), pmin(ray2[4], ylim[2])))
                          pmin(ray1[4], xlim[2]), pmin(ray2[4], ylim[2])))
  # print(ray)
  require(ggplot2)

  ggplot() + xlim(xlim[1], xlim[2]) + ylim(ylim[1], ylim[2]) +
    geom_segment(aes_string(x = ray1[1], y = ray1[2],
                     xend = ray1[3], yend = ray1[4])) +
    geom_segment(aes_string(x = ray2[1], y = ray2[2],
                     xend = ray2[3], yend = ray2[4])) +
    geom_polygon(data = ray, aes(x = x, y = y), fill = "blue", alpha = 0.2) + 
    theme_bw()
}

library(gridExtra)
## c(x0, y0, x1, y1)
l <- list(shade_segs(ray1 = c(0, 0, 5, 4), 
                     ray2 = c(0, 0, 0, 5)),
          shade_segs(ray1 = c(0, 0, 2.5, 2.5), 
                     ray2 = c(0, 0, 1, 5)),
          shade_segs(ray1 = c(0, 0, -1, 5), 
                     ray2 = c(0, 0, -.5, 5)),
          shade_segs(ray1 = c(0, 0, -3, 5), 
                     ray2 = c(0, 0, -.5, -5)))
(do.call(arrangeGrob, c(l, list(nrow = 2, ncol = 2))))

enter image description here

第二个对我更有意义。

答案 1 :(得分:6)

我认为这会更简单;但我已经到了这个丑陋的解决方案。这是一个辅助函数,用于计算base

中定义的所有交点。
findslice<-function(seg1, seg2, base=NULL, lim=getlims(base)) {
    getlims<-function(x) {
        list(y=x$scales$get_scales("y")$limits,
        x=x$scales$get_scales("x")$limits)
    }
    gethit<-function(seg, lim) {
        with(seg$mapping, {
            x<-eval(x); y<-eval(y);
            xend<-eval(xend); yend<-eval(yend);
            dx<-(xend-x); dy<-(yend-y)
            bx<-ifelse(dx>0,max(lim$x), min(lim$x))
            by<-ifelse(dy>0,max(lim$y), min(lim$y))
            sx<-ifelse(dx>0,1, 3)
            sy<-ifelse(dy>0,2, 4)
            if(identical(dx,0)) {
                return(list(x=x,y=by, side=sy))
            }
            if (identical(dy,0)) {
                return(list(x=bx,y=y, side=sx))
            }
            nx<-bx
            ny<-(y+dy)*(nx-x)/dx
            side<-sx
            if (abs(ny)>abs(by)) {
                ny<-by
                nx<-(x+dx)*(ny-y)/dy
                side<-sy
            }
            return(list(x=nx, y=ny, side=side))
        })
    }
    p1<-gethit(seg1, lim)
    p2<-gethit(seg2, lim)
    side<-p1$side
    corners<-data.frame(x=lim$x[c(2,1,1,2)], y=lim$y[c(2,2,1,1)])
    r<-data.frame(x=c(seg1$mapping$x, p1$x), y=c(seg1$mapping$y, p1$y))
    while(side != p2$side) {
        r<-rbind(r, corners[side, ])
        side <- (side %% 4) +1
    }
    r<-rbind(r, data.frame(x=p2$x, y=p2$y))
    r
}

这将创建多边形绘图所需的data.frame。例如

base <- ggplot(d) + xlim(-5, 5) + ylim(-5, 5) + geom_blank()

ray1 <- geom_segment(aes(x=0,y=0,xend=5,yend=4))
ray2 <- geom_segment(aes(x=0,y=0,xend=0,yend=5))

shading <- geom_polygon(data=findslice(ray1, ray2, base),
  aes(x,y), fill="blue", alpha=0.2)

base + ray1 + ray2 + shading + ggtitle("Take 1")

这个想法是它将超出限制,然后开始环绕边缘。另一个例子是

ray1 <- geom_segment(aes(x=0,y=0,xend=5,yend=4))
ray2 <- geom_segment(aes(x=0,y=0,xend=0,yend=5))

shading <- geom_polygon(data=findslice(ray1, ray2, base),
  aes(x,y), fill="blue", alpha=0.2)

base + ray1 + ray2 + shading + ggtitle("Take 2")

enter image description here

答案 2 :(得分:6)

所以这里有两种方法可以做到这一点。第一种使用蛮力方法,这种方法虽然速度很慢但最简单。第二个使用包sprgeos中的功能集来操作地图几何。这在概念上类似于使用凸壳的解决方案。

第一种方法:

r1 <- data.frame(x=c(0,5),y=c(0,4))
r2 <- data.frame(x=c(0,0),y=c(0,5))

th1  <- with(r1,atan2(y[2],x[2]))
th2  <- with(r2,atan2(y[2],x[2]))

is.between <- function(x,lo,hi) {
  if (lo<=hi) return(x>=lo & x<=hi)
  return(!(x<lo & x>hi))
}

df    <- expand.grid(x=seq(-5,5,len=200),y=seq(-5,5,len=200))
df$th <- atan2(df$y,df$x)
library(ggplot2)
ggplot(mapping=aes(x,y))+
  xlim(-5,5) + ylim(-5,5)+
  geom_tile(data=df[is.between(df$th,th1,th2),],fill="blue",alpha=.2)+
  geom_line(data=r1)+
  geom_line(data=r2)

这种方法认识到,由于你的光线从原点开始,我们可以创建一个覆盖整个空间的瓷砖网格,然后只是在两个角度扫过两个角度之间与x轴形成一个角度的颜色。射线。以下代码以逆时针方式对“r1r2之间的区域进行着色。因此,如果r2首先出现,它将遮蔽外部角度(尝试交换r1r2的定义)。请注意,我们使用geom_tile(...)进行着色。

使用你的形式主义:

base <- ggplot() + xlim(-5,5) + ylim(-5,5) + geom_blank()
ray1 <- geom_line(data=r1, aes(x,y))
ray2 <- geom_line(data=r2, aes(x,y))
shading <- geom_tile(data=df[is.between(df$th,th1,th2),],aes(x,y),
                     fill="blue",alpha=.2)
base + ray1 + ray2 + shading

第二种方法:

library(sp)      # for SpatialPolygons(...), etc.
library(rgeos)   # for gIntersection(...)
th1  <- with(r1,atan2(y[2],x[2]))
th1  <- ifelse(th1>0, th1, 2*pi+th1)
th2  <- with(r2,atan2(y[2],x[2]))
th2  <- ifelse(th2>0, th2, 2*pi+th2)

th    <- if(th1<th2){seq(th1,th2,length=100)} else {c(seq(th1,2*pi,length=100),seq(0,th2,length=100))}
df    <- data.frame(x=c(0,5*sqrt(2)*cos(th),0),y=c(0,5*sqrt(2)*sin(th),0))
slice <- SpatialPolygons(list(Polygons(list(Polygon(df)),"1")))
box   <- readWKT("POLYGON((-5 -5,-5 5,5 5,5 -5,-5 -5))")
shade <- gIntersection(box,slice)
sh.df <- as.data.frame(shade@polygons[[1]]@Polygons[[1]]@coords)

base <- ggplot() + xlim(-5,5) + ylim(-5,5) + geom_blank()
ray1 <- geom_line(data=r1, aes(x,y))
ray2 <- geom_line(data=r2, aes(x,y))
shading <- geom_polygon(data=sh.df,aes(x,y),fill="blue",alpha=.2)
base + ray1 + ray2 + shading

这种方法利用了rgeos中的特征集,特别是计算SpatialPolygons交集的能力。它创建了两个SpatialPolygons:一个名为box,仅覆盖从(-5,-5)到(5,5)的区域,另一个名为slice,它是两条光线之间的饼图。我们必须使用5*sqrt(2)饼图的半径来保证切片至少延伸到box的角落。然后我们使用gIntersection(...)包中的rgeos计算这两个多边形的交点,并使用ggplot将其绘制为多边形

虽然创建多边形的语法是曲折的,但这种方法要快得多。

答案 3 :(得分:4)

在光线总是碰巧形成小于 180度的角度的情况下的局部解决方案是采用形成多边形的凸包的点并绘制该点。

rayX <- c(0,5,0,0)
rayY <- c(0,4,0,5)
rays <- data.frame(cbind(rayX,rayY))
Mypoly <- rays[chull(rays),]

shading2 <- geom_polygon(data=Mypoly, aes(rayX,rayY), fill="blue", alpha=0.2)

base + ray1 + ray2 + shading2

enter image description here

如果您希望区域延伸到绘图区域之外,则很简单,因为您指定了光线来自原点。只需将凸包乘以一个大于绘图限制范围的常数(然后使用coord_cartesian,这样多边形就不会从图中抛出)。

MypolyExt <- Mypoly * 10

base2 <- ggplot(d) + coord_cartesian(xlim=c(-5, 5),ylim=c(-5, 5)) +geom_blank()
shading3 <- geom_polygon(data=MypolyExt, aes(rayX,rayY), fill="blue", alpha=0.2)
base2 + ray1 + ray2 + shading3

enter image description here

为了使角度为180度或更大的情况(例如MrFlick的答案中的Take 2),需要稍微多做一些工作。一种方法是在图的外部极限中分配一组形成正方形的点。然后,如果射线的角度大于180度,则保持这些点然后抓住凸包。