R: Нарисовать многоугольник с условным цветом

Я хочу раскрасить область под кривой. Область с y > 0 должна быть красной, область с y ‹ 0 должна быть зеленой.

x <- c(1:4)
y <- c(0,1,-1,2,rep(0,4))
plot(y[1:4],type="l")
abline(h=0)

Использование ifelse() не работает:

polygon(c(x,rev(x)),y,col=ifelse(y>0,"red","green"))

На данный момент я достиг следующего:

polygon(c(x,rev(x)),y,col="green")
polygon(c(x,rev(x)),ifelse(y>0,y,0),col="red")

Но тогда красная область слишком велика. У вас есть идеи, как получить желаемый результат?


person mrub    schedule 02.07.2014    source источник


Ответы (2)


Если вам нужны два разных цвета, вам нужны два разных полигона. Вы можете либо вызвать полигон несколько раз, либо добавить значения NA в вектора x и y, чтобы указать новый полигон. R не будет автоматически вычислять пересечение для вас. Вы должны сделать это сами. Вот как вы можете нарисовать это разными цветами.

x <- c(1,2,2.5,NA,2.5,3,4)
y <- c(0,1,0,NA,0,-1,0)

#calculate color based on most extreme y value
g <- cumsum(is.na(x))
gc <- ifelse(tapply(y, g, 
    function(x) x[which.max(abs(x))])>0, 
    "red","green")

plot(c(1, 4),c(-1,1), type = "n")
polygon(x, y, col = gc)
abline(h=0)

введите здесь описание изображения

В более общем случае разделить многоугольник на разные области может быть не так просто. Кажется, есть некоторая поддержка этого типа операций в пакетах ГИС, где этот тип вещей более распространен. Тем не менее, я собрал несколько общий случай, который может работать для простых многоугольников.

Во-первых, я определяю замыкание, которое будет определять линию разреза. Функция возьмет наклон и точку пересечения по оси Y для линии и вернет функции, необходимые для разрезания многоугольника.

getSplitLine <- function(m=1, b=0) {
    force(m); force(b)
    classify <- function(x,y) {
        y >= m*x + b
    }
    intercepts <- function(x,y, class=classify(x,y)) {
        w <- which(diff(class)!=0)
        m2 <- (y[w+1]-y[w])/(x[w+1]-x[w])
        b2 <- y[w] - m2*x[w]

        ix <- (b2-b)/(m-m2)
        iy <- ix*m + b
        data.frame(x=ix,y=iy,idx=w+.5, dir=((rank(ix, ties="first")+1) %/% 2) %% 2 +1)
    }
    plot <- function(...) {
    abline(b,m,...)
    }
    list(
        intercepts=intercepts,
        classify=classify,
        plot=plot
    )
}

Теперь мы определим функцию для фактического разделения многоугольника с помощью только что определенного разделителя.

splitPolygon <- function(x, y, splitter) {
    addnullrow <- function(x) if (!all(is.na(x[nrow(x),]))) rbind(x, NA) else x
    rollup <- function(x,i=1) rbind(x[(i+1):nrow(x),], x[1:i,])
    idx <- cumsum(is.na(x) | is.na(y))
    polys <- split(data.frame(x=x,y=y)[!is.na(x),], idx[!is.na(x)])
    r <- lapply(polys, function(P) {
        x <- P$x; y<-P$y
        side <- splitter$classify(x, y)
        if(side[1] != side[length(side)]) {
            ints <- splitter$intercepts(c(x,x[1]), c(y, y[1]), c(side, side[1]))
        } else {
            ints <- splitter$intercepts(x, y, side)
        }
        sideps <- lapply(unique(side), function(ss) {
            pts <- data.frame(x=x[side==ss], y=y[side==ss], 
                idx=seq_along(x)[side==ss], dir=0)
            mm <- rbind(pts, ints)
            mm <- mm[order(mm$idx), ]
            br <- cumsum(mm$dir!=0 & c(0,head(mm$dir,-1))!=0 & 
                c(0,diff(mm$idx))>1)
            if (length(unique(br))>1) {
                mm<-rollup(mm, sum(br==br[1]))
            }
            br <- cumsum(c(FALSE,abs(diff(mm$dir*mm$dir))==3))
            do.call(rbind, lapply(split(mm, br), addnullrow))
        })
        pss<-rep(unique(side), sapply(sideps, nrow))
        ps<-do.call(rbind, lapply(sideps, addnullrow))[,c("x","y")]
        attr(ps, "side")<-pss
        ps
   })
   pss<-unname(unlist(lapply(r, attr, "side")))
   src <- rep(seq_along(r), sapply(r, nrow))
   r <- do.call(rbind, r)
   attr(r, "source")<-src
   attr(r, "side")<-pss
   r
}

Входные данные — это просто значения x и y, которые вы бы передали polygon вместе с резаком. Он вернет data.frame со значениями x и y, которые можно использовать с polygon.

Например

x <- c(1,2,2.5,NA,2.5,3,4)
y <- c(1,-2,2,NA,-1,2,-2)

sl<-getSplitLine(0,0)

plot(range(x, na.rm=T),range(y, na.rm=T), type = "n")
p <- splitPolygon(x,y,sl)
g <- cumsum(c(F, is.na(head(p$y,-1))))
gc <- ifelse(attr(p,"side")[is.na(p$y)],  
    "red","green")
polygon(p, col=gc)
sl$plot(lty=2, col="grey")

введите здесь описание изображения

Это должно работать как для простых вогнутых многоугольников, так и для наклонных линий. Вот еще один пример

x <- c(1,2,3,4,5,4,3,2)
y <- c(-2,2,1,2,-2,.5,-.5,.5)

sl<-getSplitLine(.5,-1.25)

plot(range(x, na.rm=T),range(y, na.rm=T), type = "n")
p <- splitPolygon(x,y,sl)
g <- cumsum(c(F, is.na(head(p$y,-1))))
gc <- ifelse(attr(p,"side")[is.na(p$y)],  
    "red","green")
polygon(p, col=gc)
sl$plot(lty=2, col="grey")

введите здесь описание изображения

Прямо сейчас все может стать немного запутанным, когда вершина многоугольника попадает прямо на линию разделения. Я могу попытаться исправить это в будущем.

person MrFlick    schedule 02.07.2014
comment
А, я только что понял, что привел плохой пример. Из-за 0 в y одна сторона многоугольника уже находится на линии раздела. Как насчет данных без 0, например. y <- c(1,-2,2,NA,-1,2,-2)? Я не вижу, какие корректировки я должен был бы сделать. - person mrub; 02.07.2014
comment
Вы на самом деле все выпуклые многоугольники? Или они тоже вогнутые. У меня почти работает более общее решение, но есть несколько раздражающих крайних случаев. - person MrFlick; 03.07.2014
comment
Я думаю, что у меня будут только выпуклые многоугольники. Обычно мне приходится делить данные на графике, которые будут состоять только из выпуклых многоугольников. - person mrub; 03.07.2014

Более быстрое, но не очень точное решение состоит в том, чтобы разделить фрейм данных на список в соответствии с группирующей переменной (например, вверху = красный и внизу = синий). Это довольно хороший обходной путь для довольно больших (я бы сказал,> 100 элементов) наборов данных. Для меньших фрагментов могут быть видны неоднородности:

x <- 1:100
y1 <- sin(1:100/10)*0.8
y2 <- sin(1:100/10)*1.2
plot(x, y2, type='l')
lines(x, y1, col='red')

df <- data.frame(x=x, y1=y1, y2=y2)

df$pos_neg <- ifelse(df$y2-df$y1>0,1,-1) # above (1) or below (-1) average

# create the number for chunks to be split into lists:
df$chunk <- c(1,cumsum(abs(diff(df$pos_neg)))/2+1) # first element needs to be added`
df$colors <- ifelse(df$pos_neg>0, "red","blue") # colors to be used for filling the polygons
# create lists to be plotted:
l <- split(df, df$chunk) # we should get 4 sub-lists
lapply(l, function(x) polygon(c(x$x,rev(x$x)),c(x$y2,rev(x$y1)),col=x$colors))

Как я уже сказал, для меньшего набора данных может быть виден некоторый разрыв, если между положительными и отрицательными областями происходят резкие изменения, но если горизонтальная линия различает эти два или наносится больше элементов, то этот эффект игнорируется:

результаты

person speleo    schedule 31.01.2019